Auto merge of #101195 - Dylan-DPC:rollup-rhjaz6r, r=Dylan-DPC

Rollup of 5 pull requests

Successful merges:

 - #99517 (Display raw pointer as *{mut,const} T instead of *-ptr in errors)
 - #99928 (Do not leak type variables from opaque type relation)
 - #100473 (Attempt to normalize `FnDef` signature in `InferCtxt::cmp`)
 - #100653 (Move the cast_float_to_int fallback code to GCC)
 - #100941 (Point at the string inside literal and mention if we need string inte…)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2022-08-30 14:52:02 +00:00
commit fce6a7d66e
24 changed files with 501 additions and 206 deletions

View File

@ -3370,7 +3370,6 @@ dependencies = [
"object 0.29.0",
"pathdiff",
"regex",
"rustc_apfloat",
"rustc_arena",
"rustc_ast",
"rustc_attr",

View File

@ -15,8 +15,11 @@ use gccjit::{
Type,
UnaryOp,
};
use rustc_apfloat::{ieee, Float, Round, Status};
use rustc_codegen_ssa::MemFlags;
use rustc_codegen_ssa::common::{AtomicOrdering, AtomicRmwBinOp, IntPredicate, RealPredicate, SynchronizationScope};
use rustc_codegen_ssa::common::{
AtomicOrdering, AtomicRmwBinOp, IntPredicate, RealPredicate, SynchronizationScope, TypeKind,
};
use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue};
use rustc_codegen_ssa::mir::place::PlaceRef;
use rustc_codegen_ssa::traits::{
@ -31,6 +34,7 @@ use rustc_codegen_ssa::traits::{
StaticBuilderMethods,
};
use rustc_data_structures::fx::FxHashSet;
use rustc_middle::bug;
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
use rustc_middle::ty::layout::{FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasParamEnv, HasTyCtxt, LayoutError, LayoutOfHelpers, TyAndLayout};
use rustc_span::Span;
@ -1271,12 +1275,12 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
val
}
fn fptoui_sat(&mut self, _val: RValue<'gcc>, _dest_ty: Type<'gcc>) -> Option<RValue<'gcc>> {
None
fn fptoui_sat(&mut self, val: RValue<'gcc>, dest_ty: Type<'gcc>) -> RValue<'gcc> {
self.fptoint_sat(false, val, dest_ty)
}
fn fptosi_sat(&mut self, _val: RValue<'gcc>, _dest_ty: Type<'gcc>) -> Option<RValue<'gcc>> {
None
fn fptosi_sat(&mut self, val: RValue<'gcc>, dest_ty: Type<'gcc>) -> RValue<'gcc> {
self.fptoint_sat(true, val, dest_ty)
}
fn instrprof_increment(&mut self, _fn_name: RValue<'gcc>, _hash: RValue<'gcc>, _num_counters: RValue<'gcc>, _index: RValue<'gcc>) {
@ -1285,6 +1289,166 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
}
impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
fn fptoint_sat(&mut self, signed: bool, val: RValue<'gcc>, dest_ty: Type<'gcc>) -> RValue<'gcc> {
let src_ty = self.cx.val_ty(val);
let (float_ty, int_ty) = if self.cx.type_kind(src_ty) == TypeKind::Vector {
assert_eq!(self.cx.vector_length(src_ty), self.cx.vector_length(dest_ty));
(self.cx.element_type(src_ty), self.cx.element_type(dest_ty))
} else {
(src_ty, dest_ty)
};
// FIXME(jistone): the following was originally the fallback SSA implementation, before LLVM 13
// added native `fptosi.sat` and `fptoui.sat` conversions, but it was used by GCC as well.
// Now that LLVM always relies on its own, the code has been moved to GCC, but the comments are
// still LLVM-specific. This should be updated, and use better GCC specifics if possible.
let int_width = self.cx.int_width(int_ty);
let float_width = self.cx.float_width(float_ty);
// LLVM's fpto[su]i returns undef when the input val is infinite, NaN, or does not fit into the
// destination integer type after rounding towards zero. This `undef` value can cause UB in
// safe code (see issue #10184), so we implement a saturating conversion on top of it:
// Semantically, the mathematical value of the input is rounded towards zero to the next
// mathematical integer, and then the result is clamped into the range of the destination
// integer type. Positive and negative infinity are mapped to the maximum and minimum value of
// the destination integer type. NaN is mapped to 0.
//
// Define f_min and f_max as the largest and smallest (finite) floats that are exactly equal to
// a value representable in int_ty.
// They are exactly equal to int_ty::{MIN,MAX} if float_ty has enough significand bits.
// Otherwise, int_ty::MAX must be rounded towards zero, as it is one less than a power of two.
// int_ty::MIN, however, is either zero or a negative power of two and is thus exactly
// representable. Note that this only works if float_ty's exponent range is sufficiently large.
// f16 or 256 bit integers would break this property. Right now the smallest float type is f32
// with exponents ranging up to 127, which is barely enough for i128::MIN = -2^127.
// On the other hand, f_max works even if int_ty::MAX is greater than float_ty::MAX. Because
// we're rounding towards zero, we just get float_ty::MAX (which is always an integer).
// This already happens today with u128::MAX = 2^128 - 1 > f32::MAX.
let int_max = |signed: bool, int_width: u64| -> u128 {
let shift_amount = 128 - int_width;
if signed { i128::MAX as u128 >> shift_amount } else { u128::MAX >> shift_amount }
};
let int_min = |signed: bool, int_width: u64| -> i128 {
if signed { i128::MIN >> (128 - int_width) } else { 0 }
};
let compute_clamp_bounds_single = |signed: bool, int_width: u64| -> (u128, u128) {
let rounded_min =
ieee::Single::from_i128_r(int_min(signed, int_width), Round::TowardZero);
assert_eq!(rounded_min.status, Status::OK);
let rounded_max =
ieee::Single::from_u128_r(int_max(signed, int_width), Round::TowardZero);
assert!(rounded_max.value.is_finite());
(rounded_min.value.to_bits(), rounded_max.value.to_bits())
};
let compute_clamp_bounds_double = |signed: bool, int_width: u64| -> (u128, u128) {
let rounded_min =
ieee::Double::from_i128_r(int_min(signed, int_width), Round::TowardZero);
assert_eq!(rounded_min.status, Status::OK);
let rounded_max =
ieee::Double::from_u128_r(int_max(signed, int_width), Round::TowardZero);
assert!(rounded_max.value.is_finite());
(rounded_min.value.to_bits(), rounded_max.value.to_bits())
};
// To implement saturation, we perform the following steps:
//
// 1. Cast val to an integer with fpto[su]i. This may result in undef.
// 2. Compare val to f_min and f_max, and use the comparison results to select:
// a) int_ty::MIN if val < f_min or val is NaN
// b) int_ty::MAX if val > f_max
// c) the result of fpto[su]i otherwise
// 3. If val is NaN, return 0.0, otherwise return the result of step 2.
//
// This avoids resulting undef because values in range [f_min, f_max] by definition fit into the
// destination type. It creates an undef temporary, but *producing* undef is not UB. Our use of
// undef does not introduce any non-determinism either.
// More importantly, the above procedure correctly implements saturating conversion.
// Proof (sketch):
// If val is NaN, 0 is returned by definition.
// Otherwise, val is finite or infinite and thus can be compared with f_min and f_max.
// This yields three cases to consider:
// (1) if val in [f_min, f_max], the result of fpto[su]i is returned, which agrees with
// saturating conversion for inputs in that range.
// (2) if val > f_max, then val is larger than int_ty::MAX. This holds even if f_max is rounded
// (i.e., if f_max < int_ty::MAX) because in those cases, nextUp(f_max) is already larger
// than int_ty::MAX. Because val is larger than int_ty::MAX, the return value of int_ty::MAX
// is correct.
// (3) if val < f_min, then val is smaller than int_ty::MIN. As shown earlier, f_min exactly equals
// int_ty::MIN and therefore the return value of int_ty::MIN is correct.
// QED.
let float_bits_to_llval = |bx: &mut Self, bits| {
let bits_llval = match float_width {
32 => bx.cx().const_u32(bits as u32),
64 => bx.cx().const_u64(bits as u64),
n => bug!("unsupported float width {}", n),
};
bx.bitcast(bits_llval, float_ty)
};
let (f_min, f_max) = match float_width {
32 => compute_clamp_bounds_single(signed, int_width),
64 => compute_clamp_bounds_double(signed, int_width),
n => bug!("unsupported float width {}", n),
};
let f_min = float_bits_to_llval(self, f_min);
let f_max = float_bits_to_llval(self, f_max);
let int_max = self.cx.const_uint_big(int_ty, int_max(signed, int_width));
let int_min = self.cx.const_uint_big(int_ty, int_min(signed, int_width) as u128);
let zero = self.cx.const_uint(int_ty, 0);
// If we're working with vectors, constants must be "splatted": the constant is duplicated
// into each lane of the vector. The algorithm stays the same, we are just using the
// same constant across all lanes.
let maybe_splat = |bx: &mut Self, val| {
if bx.cx().type_kind(dest_ty) == TypeKind::Vector {
bx.vector_splat(bx.vector_length(dest_ty), val)
} else {
val
}
};
let f_min = maybe_splat(self, f_min);
let f_max = maybe_splat(self, f_max);
let int_max = maybe_splat(self, int_max);
let int_min = maybe_splat(self, int_min);
let zero = maybe_splat(self, zero);
// Step 1 ...
let fptosui_result = if signed { self.fptosi(val, dest_ty) } else { self.fptoui(val, dest_ty) };
let less_or_nan = self.fcmp(RealPredicate::RealULT, val, f_min);
let greater = self.fcmp(RealPredicate::RealOGT, val, f_max);
// Step 2: We use two comparisons and two selects, with %s1 being the
// result:
// %less_or_nan = fcmp ult %val, %f_min
// %greater = fcmp olt %val, %f_max
// %s0 = select %less_or_nan, int_ty::MIN, %fptosi_result
// %s1 = select %greater, int_ty::MAX, %s0
// Note that %less_or_nan uses an *unordered* comparison. This
// comparison is true if the operands are not comparable (i.e., if val is
// NaN). The unordered comparison ensures that s1 becomes int_ty::MIN if
// val is NaN.
//
// Performance note: Unordered comparison can be lowered to a "flipped"
// comparison and a negation, and the negation can be merged into the
// select. Therefore, it not necessarily any more expensive than an
// ordered ("normal") comparison. Whether these optimizations will be
// performed is ultimately up to the backend, but at least x86 does
// perform them.
let s0 = self.select(less_or_nan, int_min, fptosui_result);
let s1 = self.select(greater, int_max, s0);
// Step 3: NaN replacement.
// For unsigned types, the above step already yielded int_ty::MIN == 0 if val is NaN.
// Therefore we only need to execute this step for signed integer types.
if signed {
// LLVM has no isNaN predicate, so we use (val == val) instead
let cmp = self.fcmp(RealPredicate::RealOEQ, val, val);
self.select(cmp, s1, zero)
} else {
s1
}
}
#[cfg(feature="master")]
pub fn shuffle_vector(&mut self, v1: RValue<'gcc>, v2: RValue<'gcc>, mask: RValue<'gcc>) -> RValue<'gcc> {
let struct_type = mask.get_type().is_struct().expect("mask of struct type");

View File

@ -19,6 +19,7 @@
#![warn(rust_2018_idioms)]
#![warn(unused_lifetimes)]
extern crate rustc_apfloat;
extern crate rustc_ast;
extern crate rustc_codegen_ssa;
extern crate rustc_data_structures;

View File

@ -725,11 +725,11 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
unsafe { llvm::LLVMBuildSExt(self.llbuilder, val, dest_ty, UNNAMED) }
}
fn fptoui_sat(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> Option<&'ll Value> {
fn fptoui_sat(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
self.fptoint_sat(false, val, dest_ty)
}
fn fptosi_sat(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> Option<&'ll Value> {
fn fptosi_sat(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
self.fptoint_sat(true, val, dest_ty)
}
@ -1429,12 +1429,7 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
}
}
fn fptoint_sat(
&mut self,
signed: bool,
val: &'ll Value,
dest_ty: &'ll Type,
) -> Option<&'ll Value> {
fn fptoint_sat(&mut self, signed: bool, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
let src_ty = self.cx.val_ty(val);
let (float_ty, int_ty, vector_length) = if self.cx.type_kind(src_ty) == TypeKind::Vector {
assert_eq!(self.cx.vector_length(src_ty), self.cx.vector_length(dest_ty));
@ -1459,7 +1454,7 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
format!("llvm.{}.sat.i{}.f{}", instr, int_width, float_width)
};
let f = self.declare_cfn(&name, llvm::UnnamedAddr::No, self.type_func(&[src_ty], dest_ty));
Some(self.call(self.type_func(&[src_ty], dest_ty), f, &[val], None))
self.call(self.type_func(&[src_ty], dest_ty), f, &[val], None)
}
pub(crate) fn landing_pad(

View File

@ -26,7 +26,6 @@ rustc_arena = { path = "../rustc_arena" }
rustc_ast = { path = "../rustc_ast" }
rustc_span = { path = "../rustc_span" }
rustc_middle = { path = "../rustc_middle" }
rustc_apfloat = { path = "../rustc_apfloat" }
rustc_attr = { path = "../rustc_attr" }
rustc_symbol_mangling = { path = "../rustc_symbol_mangling" }
rustc_data_structures = { path = "../rustc_data_structures" }

View File

@ -1,6 +1,5 @@
use super::abi::AbiBuilderMethods;
use super::asm::AsmBuilderMethods;
use super::consts::ConstMethods;
use super::coverageinfo::CoverageInfoBuilderMethods;
use super::debuginfo::DebugInfoBuilderMethods;
use super::intrinsic::IntrinsicCallMethods;
@ -15,7 +14,6 @@ use crate::mir::operand::OperandRef;
use crate::mir::place::PlaceRef;
use crate::MemFlags;
use rustc_apfloat::{ieee, Float, Round, Status};
use rustc_middle::ty::layout::{HasParamEnv, TyAndLayout};
use rustc_middle::ty::Ty;
use rustc_span::Span;
@ -188,8 +186,8 @@ pub trait BuilderMethods<'a, 'tcx>:
fn trunc(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
fn sext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
fn fptoui_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Option<Self::Value>;
fn fptosi_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Option<Self::Value>;
fn fptoui_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
fn fptosi_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
fn fptoui(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
fn fptosi(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
fn uitofp(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
@ -223,156 +221,7 @@ pub trait BuilderMethods<'a, 'tcx>:
return if signed { self.fptosi(x, dest_ty) } else { self.fptoui(x, dest_ty) };
}
let try_sat_result =
if signed { self.fptosi_sat(x, dest_ty) } else { self.fptoui_sat(x, dest_ty) };
if let Some(try_sat_result) = try_sat_result {
return try_sat_result;
}
let int_width = self.cx().int_width(int_ty);
let float_width = self.cx().float_width(float_ty);
// LLVM's fpto[su]i returns undef when the input x is infinite, NaN, or does not fit into the
// destination integer type after rounding towards zero. This `undef` value can cause UB in
// safe code (see issue #10184), so we implement a saturating conversion on top of it:
// Semantically, the mathematical value of the input is rounded towards zero to the next
// mathematical integer, and then the result is clamped into the range of the destination
// integer type. Positive and negative infinity are mapped to the maximum and minimum value of
// the destination integer type. NaN is mapped to 0.
//
// Define f_min and f_max as the largest and smallest (finite) floats that are exactly equal to
// a value representable in int_ty.
// They are exactly equal to int_ty::{MIN,MAX} if float_ty has enough significand bits.
// Otherwise, int_ty::MAX must be rounded towards zero, as it is one less than a power of two.
// int_ty::MIN, however, is either zero or a negative power of two and is thus exactly
// representable. Note that this only works if float_ty's exponent range is sufficiently large.
// f16 or 256 bit integers would break this property. Right now the smallest float type is f32
// with exponents ranging up to 127, which is barely enough for i128::MIN = -2^127.
// On the other hand, f_max works even if int_ty::MAX is greater than float_ty::MAX. Because
// we're rounding towards zero, we just get float_ty::MAX (which is always an integer).
// This already happens today with u128::MAX = 2^128 - 1 > f32::MAX.
let int_max = |signed: bool, int_width: u64| -> u128 {
let shift_amount = 128 - int_width;
if signed { i128::MAX as u128 >> shift_amount } else { u128::MAX >> shift_amount }
};
let int_min = |signed: bool, int_width: u64| -> i128 {
if signed { i128::MIN >> (128 - int_width) } else { 0 }
};
let compute_clamp_bounds_single = |signed: bool, int_width: u64| -> (u128, u128) {
let rounded_min =
ieee::Single::from_i128_r(int_min(signed, int_width), Round::TowardZero);
assert_eq!(rounded_min.status, Status::OK);
let rounded_max =
ieee::Single::from_u128_r(int_max(signed, int_width), Round::TowardZero);
assert!(rounded_max.value.is_finite());
(rounded_min.value.to_bits(), rounded_max.value.to_bits())
};
let compute_clamp_bounds_double = |signed: bool, int_width: u64| -> (u128, u128) {
let rounded_min =
ieee::Double::from_i128_r(int_min(signed, int_width), Round::TowardZero);
assert_eq!(rounded_min.status, Status::OK);
let rounded_max =
ieee::Double::from_u128_r(int_max(signed, int_width), Round::TowardZero);
assert!(rounded_max.value.is_finite());
(rounded_min.value.to_bits(), rounded_max.value.to_bits())
};
// To implement saturation, we perform the following steps:
//
// 1. Cast x to an integer with fpto[su]i. This may result in undef.
// 2. Compare x to f_min and f_max, and use the comparison results to select:
// a) int_ty::MIN if x < f_min or x is NaN
// b) int_ty::MAX if x > f_max
// c) the result of fpto[su]i otherwise
// 3. If x is NaN, return 0.0, otherwise return the result of step 2.
//
// This avoids resulting undef because values in range [f_min, f_max] by definition fit into the
// destination type. It creates an undef temporary, but *producing* undef is not UB. Our use of
// undef does not introduce any non-determinism either.
// More importantly, the above procedure correctly implements saturating conversion.
// Proof (sketch):
// If x is NaN, 0 is returned by definition.
// Otherwise, x is finite or infinite and thus can be compared with f_min and f_max.
// This yields three cases to consider:
// (1) if x in [f_min, f_max], the result of fpto[su]i is returned, which agrees with
// saturating conversion for inputs in that range.
// (2) if x > f_max, then x is larger than int_ty::MAX. This holds even if f_max is rounded
// (i.e., if f_max < int_ty::MAX) because in those cases, nextUp(f_max) is already larger
// than int_ty::MAX. Because x is larger than int_ty::MAX, the return value of int_ty::MAX
// is correct.
// (3) if x < f_min, then x is smaller than int_ty::MIN. As shown earlier, f_min exactly equals
// int_ty::MIN and therefore the return value of int_ty::MIN is correct.
// QED.
let float_bits_to_llval = |bx: &mut Self, bits| {
let bits_llval = match float_width {
32 => bx.cx().const_u32(bits as u32),
64 => bx.cx().const_u64(bits as u64),
n => bug!("unsupported float width {}", n),
};
bx.bitcast(bits_llval, float_ty)
};
let (f_min, f_max) = match float_width {
32 => compute_clamp_bounds_single(signed, int_width),
64 => compute_clamp_bounds_double(signed, int_width),
n => bug!("unsupported float width {}", n),
};
let f_min = float_bits_to_llval(self, f_min);
let f_max = float_bits_to_llval(self, f_max);
let int_max = self.cx().const_uint_big(int_ty, int_max(signed, int_width));
let int_min = self.cx().const_uint_big(int_ty, int_min(signed, int_width) as u128);
let zero = self.cx().const_uint(int_ty, 0);
// If we're working with vectors, constants must be "splatted": the constant is duplicated
// into each lane of the vector. The algorithm stays the same, we are just using the
// same constant across all lanes.
let maybe_splat = |bx: &mut Self, val| {
if bx.cx().type_kind(dest_ty) == TypeKind::Vector {
bx.vector_splat(bx.vector_length(dest_ty), val)
} else {
val
}
};
let f_min = maybe_splat(self, f_min);
let f_max = maybe_splat(self, f_max);
let int_max = maybe_splat(self, int_max);
let int_min = maybe_splat(self, int_min);
let zero = maybe_splat(self, zero);
// Step 1 ...
let fptosui_result = if signed { self.fptosi(x, dest_ty) } else { self.fptoui(x, dest_ty) };
let less_or_nan = self.fcmp(RealPredicate::RealULT, x, f_min);
let greater = self.fcmp(RealPredicate::RealOGT, x, f_max);
// Step 2: We use two comparisons and two selects, with %s1 being the
// result:
// %less_or_nan = fcmp ult %x, %f_min
// %greater = fcmp olt %x, %f_max
// %s0 = select %less_or_nan, int_ty::MIN, %fptosi_result
// %s1 = select %greater, int_ty::MAX, %s0
// Note that %less_or_nan uses an *unordered* comparison. This
// comparison is true if the operands are not comparable (i.e., if x is
// NaN). The unordered comparison ensures that s1 becomes int_ty::MIN if
// x is NaN.
//
// Performance note: Unordered comparison can be lowered to a "flipped"
// comparison and a negation, and the negation can be merged into the
// select. Therefore, it not necessarily any more expensive than an
// ordered ("normal") comparison. Whether these optimizations will be
// performed is ultimately up to the backend, but at least x86 does
// perform them.
let s0 = self.select(less_or_nan, int_min, fptosui_result);
let s1 = self.select(greater, int_max, s0);
// Step 3: NaN replacement.
// For unsigned types, the above step already yielded int_ty::MIN == 0 if x is NaN.
// Therefore we only need to execute this step for signed integer types.
if signed {
// LLVM has no isNaN predicate, so we use (x == x) instead
let cmp = self.fcmp(RealPredicate::RealOEQ, x, x);
self.select(cmp, s1, zero)
} else {
s1
}
if signed { self.fptosi_sat(x, dest_ty) } else { self.fptoui_sat(x, dest_ty) }
}
fn icmp(&mut self, op: IntPredicate, lhs: Self::Value, rhs: Self::Value) -> Self::Value;

View File

@ -78,6 +78,10 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
err_count_on_creation: self.err_count_on_creation,
in_snapshot: self.in_snapshot.clone(),
universe: self.universe.clone(),
normalize_fn_sig_for_diagnostic: self
.normalize_fn_sig_for_diagnostic
.as_ref()
.map(|f| f.clone()),
}
}
}

View File

@ -961,12 +961,23 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
}
fn normalize_fn_sig_for_diagnostic(&self, sig: ty::PolyFnSig<'tcx>) -> ty::PolyFnSig<'tcx> {
if let Some(normalize) = &self.normalize_fn_sig_for_diagnostic {
normalize(self, sig)
} else {
sig
}
}
/// Given two `fn` signatures highlight only sub-parts that are different.
fn cmp_fn_sig(
&self,
sig1: &ty::PolyFnSig<'tcx>,
sig2: &ty::PolyFnSig<'tcx>,
) -> (DiagnosticStyledString, DiagnosticStyledString) {
let sig1 = &self.normalize_fn_sig_for_diagnostic(*sig1);
let sig2 = &self.normalize_fn_sig_for_diagnostic(*sig2);
let get_lifetimes = |sig| {
use rustc_hir::def::Namespace;
let (_, sig, reg) = ty::print::FmtPrinter::new(self.tcx, Namespace::TypeNS)

View File

@ -337,6 +337,9 @@ pub struct InferCtxt<'a, 'tcx> {
/// when we enter into a higher-ranked (`for<..>`) type or trait
/// bound.
universe: Cell<ty::UniverseIndex>,
normalize_fn_sig_for_diagnostic:
Option<Lrc<dyn Fn(&InferCtxt<'_, 'tcx>, ty::PolyFnSig<'tcx>) -> ty::PolyFnSig<'tcx>>>,
}
/// See the `error_reporting` module for more details.
@ -540,6 +543,8 @@ pub struct InferCtxtBuilder<'tcx> {
defining_use_anchor: DefiningAnchor,
considering_regions: bool,
fresh_typeck_results: Option<RefCell<ty::TypeckResults<'tcx>>>,
normalize_fn_sig_for_diagnostic:
Option<Lrc<dyn Fn(&InferCtxt<'_, 'tcx>, ty::PolyFnSig<'tcx>) -> ty::PolyFnSig<'tcx>>>,
}
pub trait TyCtxtInferExt<'tcx> {
@ -553,6 +558,7 @@ impl<'tcx> TyCtxtInferExt<'tcx> for TyCtxt<'tcx> {
defining_use_anchor: DefiningAnchor::Error,
considering_regions: true,
fresh_typeck_results: None,
normalize_fn_sig_for_diagnostic: None,
}
}
}
@ -582,6 +588,14 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
self
}
pub fn with_normalize_fn_sig_for_diagnostic(
mut self,
fun: Lrc<dyn Fn(&InferCtxt<'_, 'tcx>, ty::PolyFnSig<'tcx>) -> ty::PolyFnSig<'tcx>>,
) -> Self {
self.normalize_fn_sig_for_diagnostic = Some(fun);
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
@ -611,6 +625,7 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
defining_use_anchor,
considering_regions,
ref fresh_typeck_results,
ref normalize_fn_sig_for_diagnostic,
} = *self;
let in_progress_typeck_results = fresh_typeck_results.as_ref();
f(InferCtxt {
@ -629,6 +644,9 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
in_snapshot: Cell::new(false),
skip_leak_check: Cell::new(false),
universe: Cell::new(ty::UniverseIndex::ROOT),
normalize_fn_sig_for_diagnostic: normalize_fn_sig_for_diagnostic
.as_ref()
.map(|f| f.clone()),
})
}
}

View File

@ -4,6 +4,7 @@ use super::SubregionOrigin;
use crate::infer::combine::ConstEquateRelation;
use crate::infer::{TypeVariableOrigin, TypeVariableOriginKind};
use crate::traits::Obligation;
use rustc_middle::ty::error::{ExpectedFound, TypeError};
use rustc_middle::ty::relate::{Cause, Relate, RelateResult, TypeRelation};
use rustc_middle::ty::visit::TypeVisitable;
use rustc_middle::ty::TyVar;
@ -141,17 +142,27 @@ impl<'tcx> TypeRelation<'tcx> for Sub<'_, '_, 'tcx> {
Ok(infcx.tcx.mk_ty_var(var))
};
let (a, b) = if self.a_is_expected { (a, b) } else { (b, a) };
let (a, b) = match (a.kind(), b.kind()) {
let (ga, gb) = match (a.kind(), b.kind()) {
(&ty::Opaque(..), _) => (a, generalize(b, true)?),
(_, &ty::Opaque(..)) => (generalize(a, false)?, b),
_ => unreachable!(),
};
self.fields.obligations.extend(
infcx
.handle_opaque_type(a, b, true, &self.fields.trace.cause, self.param_env())?
.handle_opaque_type(ga, gb, true, &self.fields.trace.cause, self.param_env())
// Don't leak any generalized type variables out of this
// subtyping relation in the case of a type error.
.map_err(|err| {
let (ga, gb) = self.fields.infcx.resolve_vars_if_possible((ga, gb));
if let TypeError::Sorts(sorts) = err && sorts.expected == ga && sorts.found == gb {
TypeError::Sorts(ExpectedFound { expected: a, found: b })
} else {
err
}
})?
.obligations,
);
Ok(a)
Ok(ga)
}
_ => {

View File

@ -276,10 +276,23 @@ impl<'tcx> Ty<'tcx> {
}
ty::Slice(ty) if ty.is_simple_ty() => format!("slice `{}`", self).into(),
ty::Slice(_) => "slice".into(),
ty::RawPtr(_) => "*-ptr".into(),
ty::RawPtr(tymut) => {
let tymut_string = match tymut.mutbl {
hir::Mutability::Mut => tymut.to_string(),
hir::Mutability::Not => format!("const {}", tymut.ty),
};
if tymut_string != "_" && (tymut.ty.is_simple_text() || tymut_string.len() < "const raw pointer".len()) {
format!("`*{}`", tymut_string).into()
} else {
// Unknown type name, it's long or has type arguments
"raw pointer".into()
}
},
ty::Ref(_, ty, mutbl) => {
let tymut = ty::TypeAndMut { ty, mutbl };
let tymut_string = tymut.to_string();
if tymut_string != "_"
&& (ty.is_simple_text() || tymut_string.len() < "mutable reference".len())
{

View File

@ -229,6 +229,19 @@ enum VarKind {
Upvar(HirId, Symbol),
}
struct CollectLitsVisitor<'tcx> {
lit_exprs: Vec<&'tcx hir::Expr<'tcx>>,
}
impl<'tcx> Visitor<'tcx> for CollectLitsVisitor<'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if let hir::ExprKind::Lit(_) = expr.kind {
self.lit_exprs.push(expr);
}
intravisit::walk_expr(self, expr);
}
}
struct IrMaps<'tcx> {
tcx: TyCtxt<'tcx>,
live_node_map: HirIdMap<LiveNode>,
@ -1333,7 +1346,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> {
fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) {
self.check_unused_vars_in_pat(&local.pat, None, |spans, hir_id, ln, var| {
self.check_unused_vars_in_pat(&local.pat, None, None, |spans, hir_id, ln, var| {
if local.init.is_some() {
self.warn_about_dead_assign(spans, hir_id, ln, var);
}
@ -1348,7 +1361,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> {
}
fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
self.check_unused_vars_in_pat(&arm.pat, None, |_, _, _, _| {});
self.check_unused_vars_in_pat(&arm.pat, None, None, |_, _, _, _| {});
intravisit::walk_arm(self, arm);
}
}
@ -1387,7 +1400,7 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) {
}
hir::ExprKind::Let(let_expr) => {
this.check_unused_vars_in_pat(let_expr.pat, None, |_, _, _, _| {});
this.check_unused_vars_in_pat(let_expr.pat, None, None, |_, _, _, _| {});
}
// no correctness conditions related to liveness
@ -1508,13 +1521,18 @@ impl<'tcx> Liveness<'_, 'tcx> {
fn warn_about_unused_args(&self, body: &hir::Body<'_>, entry_ln: LiveNode) {
for p in body.params {
self.check_unused_vars_in_pat(&p.pat, Some(entry_ln), |spans, hir_id, ln, var| {
if !self.live_on_entry(ln, var) {
self.report_unused_assign(hir_id, spans, var, |name| {
format!("value passed to `{}` is never read", name)
});
}
});
self.check_unused_vars_in_pat(
&p.pat,
Some(entry_ln),
Some(body),
|spans, hir_id, ln, var| {
if !self.live_on_entry(ln, var) {
self.report_unused_assign(hir_id, spans, var, |name| {
format!("value passed to `{}` is never read", name)
});
}
},
);
}
}
@ -1522,6 +1540,7 @@ impl<'tcx> Liveness<'_, 'tcx> {
&self,
pat: &hir::Pat<'_>,
entry_ln: Option<LiveNode>,
opt_body: Option<&hir::Body<'_>>,
on_used_on_entry: impl Fn(Vec<Span>, HirId, LiveNode, Variable),
) {
// In an or-pattern, only consider the variable; any later patterns must have the same
@ -1549,7 +1568,7 @@ impl<'tcx> Liveness<'_, 'tcx> {
hir_ids_and_spans.into_iter().map(|(_, _, ident_span)| ident_span).collect();
on_used_on_entry(spans, id, ln, var);
} else {
self.report_unused(hir_ids_and_spans, ln, var, can_remove);
self.report_unused(hir_ids_and_spans, ln, var, can_remove, pat, opt_body);
}
}
}
@ -1561,6 +1580,8 @@ impl<'tcx> Liveness<'_, 'tcx> {
ln: LiveNode,
var: Variable,
can_remove: bool,
pat: &hir::Pat<'_>,
opt_body: Option<&hir::Body<'_>>,
) {
let first_hir_id = hir_ids_and_spans[0].0;
@ -1664,6 +1685,9 @@ impl<'tcx> Liveness<'_, 'tcx> {
.collect::<Vec<_>>(),
|lint| {
let mut err = lint.build(&format!("unused variable: `{}`", name));
if self.has_added_lit_match_name_span(&name, opt_body, &mut err) {
err.span_label(pat.span, "unused variable");
}
err.multipart_suggestion(
"if this is intentional, prefix it with an underscore",
non_shorthands,
@ -1677,6 +1701,42 @@ impl<'tcx> Liveness<'_, 'tcx> {
}
}
fn has_added_lit_match_name_span(
&self,
name: &str,
opt_body: Option<&hir::Body<'_>>,
err: &mut rustc_errors::DiagnosticBuilder<'_, ()>,
) -> bool {
let mut has_litstring = false;
let Some(opt_body) = opt_body else {return false;};
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 name_str: &str = syb.as_str();
let mut name_pa = String::from("{");
name_pa.push_str(&name);
name_pa.push('}');
if name_str.contains(&name_pa) {
err.span_label(
lit_expr.span,
"you might have meant to use string interpolation in this string literal",
);
err.multipart_suggestion(
"string interpolation only works in `format!` invocations",
vec![
(lit_expr.span.shrink_to_lo(), "format!(".to_string()),
(lit_expr.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MachineApplicable,
);
has_litstring = true;
}
}
has_litstring
}
fn warn_about_dead_assign(&self, spans: Vec<Span>, hir_id: HirId, ln: LiveNode, var: Variable) {
if !self.live_on_exit(ln, var) {
self.report_unused_assign(hir_id, spans, var, |name| {

View File

@ -17,6 +17,7 @@ use rustc_span::Span;
pub trait TraitEngineExt<'tcx> {
fn new(tcx: TyCtxt<'tcx>) -> Box<Self>;
fn new_in_snapshot(tcx: TyCtxt<'tcx>) -> Box<Self>;
}
impl<'tcx> TraitEngineExt<'tcx> for dyn TraitEngine<'tcx> {
@ -27,6 +28,14 @@ impl<'tcx> TraitEngineExt<'tcx> for dyn TraitEngine<'tcx> {
Box::new(FulfillmentContext::new())
}
}
fn new_in_snapshot(tcx: TyCtxt<'tcx>) -> Box<Self> {
if tcx.sess.opts.unstable_opts.chalk {
Box::new(ChalkFulfillmentContext::new())
} else {
Box::new(FulfillmentContext::new_in_snapshot())
}
}
}
/// Used if you want to have pleasant experience when dealing
@ -41,6 +50,10 @@ impl<'a, 'tcx> ObligationCtxt<'a, 'tcx> {
Self { infcx, engine: RefCell::new(<dyn TraitEngine<'_>>::new(infcx.tcx)) }
}
pub fn new_in_snapshot(infcx: &'a InferCtxt<'a, 'tcx>) -> Self {
Self { infcx, engine: RefCell::new(<dyn TraitEngine<'_>>::new_in_snapshot(infcx.tcx)) }
}
pub fn register_obligation(&self, obligation: PredicateObligation<'tcx>) {
self.engine.borrow_mut().register_predicate_obligation(self.infcx, obligation);
}

View File

@ -20,7 +20,7 @@ use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::Visitor;
use rustc_hir::lang_items::LangItem;
use rustc_hir::{AsyncGeneratorKind, GeneratorKind, Node};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_middle::hir::map;
use rustc_middle::ty::{
self, suggest_arbitrary_trait_bound, suggest_constraining_type_param, AdtKind, DefIdTree,
@ -1589,32 +1589,38 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
expected: ty::PolyTraitRef<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
pub(crate) fn build_fn_sig_ty<'tcx>(
tcx: TyCtxt<'tcx>,
infcx: &InferCtxt<'_, 'tcx>,
trait_ref: ty::PolyTraitRef<'tcx>,
) -> Ty<'tcx> {
let inputs = trait_ref.skip_binder().substs.type_at(1);
let sig = match inputs.kind() {
ty::Tuple(inputs)
if tcx.fn_trait_kind_from_lang_item(trait_ref.def_id()).is_some() =>
if infcx.tcx.fn_trait_kind_from_lang_item(trait_ref.def_id()).is_some() =>
{
tcx.mk_fn_sig(
infcx.tcx.mk_fn_sig(
inputs.iter(),
tcx.mk_ty_infer(ty::TyVar(ty::TyVid::from_u32(0))),
infcx.next_ty_var(TypeVariableOrigin {
span: DUMMY_SP,
kind: TypeVariableOriginKind::MiscVariable,
}),
false,
hir::Unsafety::Normal,
abi::Abi::Rust,
)
}
_ => tcx.mk_fn_sig(
_ => infcx.tcx.mk_fn_sig(
std::iter::once(inputs),
tcx.mk_ty_infer(ty::TyVar(ty::TyVid::from_u32(0))),
infcx.next_ty_var(TypeVariableOrigin {
span: DUMMY_SP,
kind: TypeVariableOriginKind::MiscVariable,
}),
false,
hir::Unsafety::Normal,
abi::Abi::Rust,
),
};
tcx.mk_fn_ptr(trait_ref.rebind(sig))
infcx.tcx.mk_fn_ptr(trait_ref.rebind(sig))
}
let argument_kind = match expected.skip_binder().self_ty().kind() {
@ -1634,11 +1640,10 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
let found_span = found_span.unwrap_or(span);
err.span_label(found_span, "found signature defined here");
let expected = build_fn_sig_ty(self.tcx, expected);
let found = build_fn_sig_ty(self.tcx, found);
let expected = build_fn_sig_ty(self, expected);
let found = build_fn_sig_ty(self, found);
let (expected_str, found_str) =
self.tcx.infer_ctxt().enter(|infcx| infcx.cmp(expected, found));
let (expected_str, found_str) = self.cmp(expected, found);
let signature_kind = format!("{argument_kind} signature");
err.note_expected_found(&signature_kind, expected_str, &signature_kind, found_str);

View File

@ -1,6 +1,7 @@
use super::callee::DeferredCallResolution;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::sync::Lrc;
use rustc_hir as hir;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::HirIdMap;
@ -12,7 +13,9 @@ use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::def_id::LocalDefIdMap;
use rustc_span::{self, Span};
use rustc_trait_selection::infer::InferCtxtExt as _;
use rustc_trait_selection::traits::{self, ObligationCause, TraitEngine, TraitEngineExt};
use rustc_trait_selection::traits::{
self, ObligationCause, ObligationCtxt, TraitEngine, TraitEngineExt as _,
};
use std::cell::RefCell;
use std::ops::Deref;
@ -84,7 +87,29 @@ impl<'tcx> Inherited<'_, 'tcx> {
infcx: tcx
.infer_ctxt()
.ignoring_regions()
.with_fresh_in_progress_typeck_results(hir_owner),
.with_fresh_in_progress_typeck_results(hir_owner)
.with_normalize_fn_sig_for_diagnostic(Lrc::new(move |infcx, fn_sig| {
if fn_sig.has_escaping_bound_vars() {
return fn_sig;
}
infcx.probe(|_| {
let ocx = ObligationCtxt::new_in_snapshot(infcx);
let normalized_fn_sig = ocx.normalize(
ObligationCause::dummy(),
// FIXME(compiler-errors): This is probably not the right param-env...
infcx.tcx.param_env(def_id),
fn_sig,
);
if ocx.select_all_or_error().is_empty() {
let normalized_fn_sig =
infcx.resolve_vars_if_possible(normalized_fn_sig);
if !normalized_fn_sig.needs_infer() {
return normalized_fn_sig;
}
}
fn_sig
})
})),
def_id,
}
}

View File

@ -106,7 +106,7 @@ error[E0308]: mismatched types
--> $DIR/type-check-1.rs:60:26
|
LL | asm!("{}", const 0 as *mut u8);
| ^^^^^^^^^^^^ expected integer, found *-ptr
| ^^^^^^^^^^^^ expected integer, found `*mut u8`
|
= note: expected type `{integer}`
found raw pointer `*mut u8`
@ -133,7 +133,7 @@ error[E0308]: mismatched types
--> $DIR/type-check-1.rs:78:25
|
LL | global_asm!("{}", const 0 as *mut u8);
| ^^^^^^^^^^^^ expected integer, found *-ptr
| ^^^^^^^^^^^^ expected integer, found `*mut u8`
|
= note: expected type `{integer}`
found raw pointer `*mut u8`

View File

@ -2,7 +2,7 @@ error[E0308]: mismatched types
--> $DIR/dst-bad-coercions.rs:14:17
|
LL | let y: &S = x;
| -- ^ expected `&S`, found *-ptr
| -- ^ expected `&S`, found `*const S`
| |
| expected due to this
|
@ -13,7 +13,7 @@ error[E0308]: mismatched types
--> $DIR/dst-bad-coercions.rs:15:21
|
LL | let y: &dyn T = x;
| ------ ^ expected `&dyn T`, found *-ptr
| ------ ^ expected `&dyn T`, found `*const S`
| |
| expected due to this
|
@ -24,7 +24,7 @@ error[E0308]: mismatched types
--> $DIR/dst-bad-coercions.rs:19:17
|
LL | let y: &S = x;
| -- ^ expected `&S`, found *-ptr
| -- ^ expected `&S`, found `*mut S`
| |
| expected due to this
|
@ -35,7 +35,7 @@ error[E0308]: mismatched types
--> $DIR/dst-bad-coercions.rs:20:21
|
LL | let y: &dyn T = x;
| ------ ^ expected `&dyn T`, found *-ptr
| ------ ^ expected `&dyn T`, found `*mut S`
| |
| expected due to this
|

View File

@ -0,0 +1,13 @@
// edition:2021
fn main() {}
struct Error;
struct Okay;
fn foo(t: Result<Okay, Error>) {
t.and_then(|t| -> _ { bar(t) });
//~^ ERROR mismatched types
}
async fn bar(t: Okay) {}

View File

@ -0,0 +1,21 @@
error[E0308]: mismatched types
--> $DIR/issue-99914.rs:9:27
|
LL | t.and_then(|t| -> _ { bar(t) });
| ^^^^^^ expected enum `Result`, found opaque type
|
note: while checking the return type of the `async fn`
--> $DIR/issue-99914.rs:13:23
|
LL | async fn bar(t: Okay) {}
| ^ checked the `Output` of this `async fn`, found opaque type
= note: expected enum `Result<_, Error>`
found opaque type `impl Future<Output = ()>`
help: try wrapping the expression in `Ok`
|
LL | t.and_then(|t| -> _ { Ok(bar(t)) });
| +++ +
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.

View File

@ -4,7 +4,7 @@ error[E0308]: mismatched types
LL | fn function(t: &mut dyn Trait) {
| - help: try adding a return type: `-> *mut dyn Trait`
LL | t as *mut dyn Trait
| ^^^^^^^^^^^^^^^^^^^ expected `()`, found *-ptr
| ^^^^^^^^^^^^^^^^^^^ expected `()`, found `*mut dyn Trait`
|
= note: expected unit type `()`
found raw pointer `*mut dyn Trait`

View File

@ -0,0 +1,16 @@
trait Foo {
type Bar;
}
impl<T> Foo for T {
type Bar = i32;
}
fn foo<T>(_: <T as Foo>::Bar, _: &'static <T as Foo>::Bar) {}
fn needs_i32_ref_fn(_: fn(&'static i32, i32)) {}
fn main() {
needs_i32_ref_fn(foo::<()>);
//~^ ERROR mismatched types
}

View File

@ -0,0 +1,19 @@
error[E0308]: mismatched types
--> $DIR/normalize-fn-sig.rs:14:22
|
LL | needs_i32_ref_fn(foo::<()>);
| ---------------- ^^^^^^^^^ expected `&i32`, found `i32`
| |
| arguments to this function are incorrect
|
= note: expected fn pointer `fn(&'static i32, i32)`
found fn item `fn(i32, &'static i32) {foo::<()>}`
note: function defined here
--> $DIR/normalize-fn-sig.rs:11:4
|
LL | fn needs_i32_ref_fn(_: fn(&'static i32, i32)) {}
| ^^^^^^^^^^^^^^^^ ------------------------
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.

View File

@ -0,0 +1,15 @@
#![deny(unused)]
fn foo(xyza: &str) {
//~^ ERROR unused variable: `xyza`
let _ = "{xyza}";
}
fn foo3(xyza: &str) {
//~^ ERROR unused variable: `xyza`
let _ = "aaa{xyza}bbb";
}
fn main() {
foo("x");
foo3("xx");
}

View File

@ -0,0 +1,44 @@
error: unused variable: `xyza`
--> $DIR/issue-100584.rs:2:8
|
LL | fn foo(xyza: &str) {
| ^^^^ unused variable
LL |
LL | let _ = "{xyza}";
| -------- you might have meant to use string interpolation in this string literal
|
note: the lint level is defined here
--> $DIR/issue-100584.rs:1:9
|
LL | #![deny(unused)]
| ^^^^^^
= note: `#[deny(unused_variables)]` implied by `#[deny(unused)]`
help: string interpolation only works in `format!` invocations
|
LL | let _ = format!("{xyza}");
| ++++++++ +
help: if this is intentional, prefix it with an underscore
|
LL | fn foo(_xyza: &str) {
| ~~~~~
error: unused variable: `xyza`
--> $DIR/issue-100584.rs:7:9
|
LL | fn foo3(xyza: &str) {
| ^^^^ unused variable
LL |
LL | let _ = "aaa{xyza}bbb";
| -------------- you might have meant to use string interpolation in this string literal
|
help: string interpolation only works in `format!` invocations
|
LL | let _ = format!("aaa{xyza}bbb");
| ++++++++ +
help: if this is intentional, prefix it with an underscore
|
LL | fn foo3(_xyza: &str) {
| ~~~~~
error: aborting due to 2 previous errors