Merge from rustc

This commit is contained in:
Ralf Jung 2024-12-12 12:24:31 +01:00
commit f590fa9214
715 changed files with 16769 additions and 7776 deletions

14
.github/renovate.json5 vendored Normal file
View file

@ -0,0 +1,14 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
// Let Renovatebot keep an opened issue that tracks our dependencies
"dependencyDashboard": true,
// Disable "normal" package updates
"enabledManagers": [],
// Update lockfiles once per week
"lockFileMaintenance": {
"enabled": true,
"schedule": [
"before 5am on Tuesday"
]
}
}

8
.gitignore vendored
View file

@ -88,12 +88,12 @@ package.json
tests/rustdoc-gui/src/**.lock
## direnv
.envrc
.direnv/
/.envrc
/.direnv/
## nix
flake.nix
/flake.nix
flake.lock
default.nix
/default.nix
# Before adding new lines, see the comment at the top.

File diff suppressed because it is too large Load diff

View file

@ -21,7 +21,7 @@ standard library, and documentation.
## Why Rust?
- **Performance:** Fast and memory-efficient, suitable for critical services, embedded devices, and easily integrate with other languages.
- **Performance:** Fast and memory-efficient, suitable for critical services, embedded devices, and easily integrated with other languages.
- **Reliability:** Our rich type system and ownership model ensure memory and thread safety, reducing bugs at compile-time.

View file

@ -3119,6 +3119,7 @@ pub struct FieldDef {
pub ident: Option<Ident>,
pub ty: P<Ty>,
pub default: Option<AnonConst>,
pub is_placeholder: bool,
}

View file

@ -1120,13 +1120,14 @@ fn walk_poly_trait_ref<T: MutVisitor>(vis: &mut T, p: &mut PolyTraitRef) {
}
pub fn walk_field_def<T: MutVisitor>(visitor: &mut T, fd: &mut FieldDef) {
let FieldDef { span, ident, vis, id, ty, attrs, is_placeholder: _, safety } = fd;
let FieldDef { span, ident, vis, id, ty, attrs, is_placeholder: _, safety, default } = fd;
visitor.visit_id(id);
visit_attrs(visitor, attrs);
visitor.visit_vis(vis);
visit_safety(visitor, safety);
visit_opt(ident, |ident| visitor.visit_ident(ident));
visitor.visit_ty(ty);
visit_opt(default, |default| visitor.visit_anon_const(default));
visitor.visit_span(span);
}

View file

@ -975,11 +975,13 @@ pub fn walk_struct_def<'a, V: Visitor<'a>>(
}
pub fn walk_field_def<'a, V: Visitor<'a>>(visitor: &mut V, field: &'a FieldDef) -> V::Result {
let FieldDef { attrs, id: _, span: _, vis, ident, ty, is_placeholder: _, safety: _ } = field;
let FieldDef { attrs, id: _, span: _, vis, ident, ty, is_placeholder: _, safety: _, default } =
field;
walk_list!(visitor, visit_attribute, attrs);
try_visit!(visitor.visit_vis(vis));
visit_opt!(visitor, visit_ident, ident);
try_visit!(visitor.visit_ty(ty));
visit_opt!(visitor, visit_anon_const, &*default);
V::Result::output()
}

View file

@ -45,10 +45,6 @@ ast_lowering_bad_return_type_notation_output =
ast_lowering_bad_return_type_notation_position = return type notation not allowed in this position yet
ast_lowering_base_expression_double_dot =
base expression required after `..`
.suggestion = add a base expression here
ast_lowering_clobber_abi_not_supported =
`clobber_abi` is not supported on this target
@ -57,6 +53,9 @@ ast_lowering_closure_cannot_be_static = closures cannot be static
ast_lowering_coroutine_too_many_parameters =
too many parameters for a coroutine (expected 0 or 1 parameters)
ast_lowering_default_field_in_tuple = default fields are not supported in tuple structs
.label = default fields are only supported on structs
ast_lowering_does_not_support_modifiers =
the `{$class_name}` register class does not support template modifiers

View file

@ -38,6 +38,14 @@ pub(crate) struct InvalidAbi {
pub suggestion: Option<InvalidAbiSuggestion>,
}
#[derive(Diagnostic)]
#[diag(ast_lowering_default_field_in_tuple)]
pub(crate) struct TupleStructWithDefault {
#[primary_span]
#[label]
pub span: Span,
}
pub(crate) struct InvalidAbiReason(pub &'static str);
impl Subdiagnostic for InvalidAbiReason {
@ -114,14 +122,6 @@ pub(crate) struct UnderscoreExprLhsAssign {
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(ast_lowering_base_expression_double_dot, code = E0797)]
pub(crate) struct BaseExpressionDoubleDot {
#[primary_span]
#[suggestion(code = "/* expr */", applicability = "has-placeholders", style = "verbose")]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(ast_lowering_await_only_in_async_fn_and_blocks, code = E0728)]
pub(crate) struct AwaitOnlyInAsyncFnAndBlocks {

View file

@ -19,10 +19,10 @@ use thin_vec::{ThinVec, thin_vec};
use visit::{Visitor, walk_expr};
use super::errors::{
AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks, BaseExpressionDoubleDot,
ClosureCannotBeStatic, CoroutineTooManyParameters,
FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd, MatchArmWithNoBody,
NeverPatternWithBody, NeverPatternWithGuard, UnderscoreExprLhsAssign,
AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks, ClosureCannotBeStatic,
CoroutineTooManyParameters, FunctionalRecordUpdateDestructuringAssignment,
InclusiveRangeWithNoEnd, MatchArmWithNoBody, NeverPatternWithBody, NeverPatternWithGuard,
UnderscoreExprLhsAssign,
};
use super::{
GenericArgsMode, ImplTraitContext, LoweringContext, ParamMode, ResolverAstLoweringExt,
@ -357,12 +357,9 @@ impl<'hir> LoweringContext<'_, 'hir> {
),
ExprKind::Struct(se) => {
let rest = match &se.rest {
StructRest::Base(e) => Some(self.lower_expr(e)),
StructRest::Rest(sp) => {
let guar = self.dcx().emit_err(BaseExpressionDoubleDot { span: *sp });
Some(&*self.arena.alloc(self.expr_err(*sp, guar)))
}
StructRest::None => None,
StructRest::Base(e) => hir::StructTailExpr::Base(self.lower_expr(e)),
StructRest::Rest(sp) => hir::StructTailExpr::DefaultFields(*sp),
StructRest::None => hir::StructTailExpr::None,
};
hir::ExprKind::Struct(
self.arena.alloc(self.lower_qpath(
@ -1526,7 +1523,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
hir::ExprKind::Struct(
self.arena.alloc(hir::QPath::LangItem(lang_item, self.lower_span(span))),
fields,
None,
hir::StructTailExpr::None,
)
}

View file

@ -17,7 +17,10 @@ use smallvec::{SmallVec, smallvec};
use thin_vec::ThinVec;
use tracing::instrument;
use super::errors::{InvalidAbi, InvalidAbiReason, InvalidAbiSuggestion, MisplacedRelaxTraitBound};
use super::errors::{
InvalidAbi, InvalidAbiReason, InvalidAbiSuggestion, MisplacedRelaxTraitBound,
TupleStructWithDefault,
};
use super::{
AstOwner, FnDeclKind, ImplTraitContext, ImplTraitPosition, LoweringContext, ParamMode,
ResolverAstLoweringExt,
@ -690,13 +693,27 @@ impl<'hir> LoweringContext<'_, 'hir> {
VariantData::Tuple(fields, id) => {
let ctor_id = self.lower_node_id(*id);
self.alias_attrs(ctor_id, parent_id);
hir::VariantData::Tuple(
self.arena.alloc_from_iter(
fields.iter().enumerate().map(|f| self.lower_field_def(f)),
),
ctor_id,
self.local_def_id(*id),
)
let fields = self
.arena
.alloc_from_iter(fields.iter().enumerate().map(|f| self.lower_field_def(f)));
for field in &fields[..] {
if let Some(default) = field.default {
// Default values in tuple struct and tuple variants are not allowed by the
// RFC due to concerns about the syntax, both in the item definition and the
// expression. We could in the future allow `struct S(i32 = 0);` and force
// users to construct the value with `let _ = S { .. };`.
if self.tcx.features().default_field_values() {
self.dcx().emit_err(TupleStructWithDefault { span: default.span });
} else {
let _ = self.dcx().span_delayed_bug(
default.span,
"expected `default values on `struct` fields aren't supported` \
feature-gate error but none was produced",
);
}
}
}
hir::VariantData::Tuple(fields, ctor_id, self.local_def_id(*id))
}
VariantData::Unit(id) => {
let ctor_id = self.lower_node_id(*id);
@ -723,6 +740,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
None => Ident::new(sym::integer(index), self.lower_span(f.span)),
},
vis_span: self.lower_span(f.vis.span),
default: f.default.as_ref().map(|v| self.lower_anon_const_to_anon_const(v)),
ty,
safety: self.lower_safety(f.safety, hir::Safety::Safe),
}

View file

@ -557,6 +557,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
gate_all!(explicit_tail_calls, "`become` expression is experimental");
gate_all!(generic_const_items, "generic const items are experimental");
gate_all!(guard_patterns, "guard patterns are experimental", "consider using match arm guards");
gate_all!(default_field_values, "default values on fields are experimental");
gate_all!(fn_delegation, "functions delegation is not yet fully implemented");
gate_all!(postfix_match, "postfix match is experimental");
gate_all!(mut_ref, "mutable by-reference bindings are experimental");

View file

@ -1151,7 +1151,11 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
expr: &hir::Expr<'_>,
) {
let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());
let hir::ExprKind::Struct(struct_qpath, fields, Some(base)) = expr.kind else { return };
let hir::ExprKind::Struct(struct_qpath, fields, hir::StructTailExpr::Base(base)) =
expr.kind
else {
return;
};
let hir::QPath::Resolved(_, path) = struct_qpath else { return };
let hir::def::Res::Def(_, def_id) = path.res else { return };
let Some(expr_ty) = typeck_results.node_type_opt(expr.hir_id) else { return };
@ -1239,7 +1243,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
expr: &'tcx hir::Expr<'tcx>,
use_spans: Option<UseSpans<'tcx>>,
) {
if let hir::ExprKind::Struct(_, _, Some(_)) = expr.kind {
if let hir::ExprKind::Struct(_, _, hir::StructTailExpr::Base(_)) = expr.kind {
// We have `S { foo: val, ..base }`. In `check_aggregate_rvalue` we have a single
// `Location` that covers both the `S { ... }` literal, all of its fields and the
// `base`. If the move happens because of `S { foo: val, bar: base.bar }` the `expr`

View file

@ -26,7 +26,7 @@ use rustc_data_structures::graph::dominators::Dominators;
use rustc_errors::Diag;
use rustc_hir as hir;
use rustc_hir::def_id::LocalDefId;
use rustc_index::bit_set::{BitSet, ChunkedBitSet};
use rustc_index::bit_set::{BitSet, MixedBitSet};
use rustc_index::{IndexSlice, IndexVec};
use rustc_infer::infer::{
InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin, TyCtxtInferExt,
@ -1797,7 +1797,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
location: Location,
desired_action: InitializationRequiringAction,
place_span: (PlaceRef<'tcx>, Span),
maybe_uninits: &ChunkedBitSet<MovePathIndex>,
maybe_uninits: &MixedBitSet<MovePathIndex>,
from: u64,
to: u64,
) {

View file

@ -261,7 +261,7 @@ builtin_macros_non_exhaustive_default = default variant must be exhaustive
builtin_macros_non_generic_pointee = the `#[pointee]` attribute may only be used on generic parameters
builtin_macros_non_unit_default = the `#[default]` attribute may only be used on unit enum variants
builtin_macros_non_unit_default = the `#[default]` attribute may only be used on unit enum variants{$post}
.help = consider a manual implementation of `Default`
builtin_macros_only_one_argument = {$name} takes 1 argument

View file

@ -205,7 +205,7 @@ where
let fields = fields
.iter()
.enumerate()
.map(|(i, &(ident, span))| {
.map(|(i, &(ident, span, _))| {
let arg = getarg(cx, span, ident.name, i);
cx.field_imm(span, ident, arg)
})

View file

@ -54,26 +54,38 @@ pub(crate) fn expand_deriving_default(
trait_def.expand(cx, mitem, item, push)
}
fn default_call(cx: &ExtCtxt<'_>, span: Span) -> ast::ptr::P<ast::Expr> {
// Note that `kw::Default` is "default" and `sym::Default` is "Default"!
let default_ident = cx.std_path(&[kw::Default, sym::Default, kw::Default]);
cx.expr_call_global(span, default_ident, ThinVec::new())
}
fn default_struct_substructure(
cx: &ExtCtxt<'_>,
trait_span: Span,
substr: &Substructure<'_>,
summary: &StaticFields,
) -> BlockOrExpr {
// Note that `kw::Default` is "default" and `sym::Default` is "Default"!
let default_ident = cx.std_path(&[kw::Default, sym::Default, kw::Default]);
let default_call = |span| cx.expr_call_global(span, default_ident.clone(), ThinVec::new());
let expr = match summary {
Unnamed(_, IsTuple::No) => cx.expr_ident(trait_span, substr.type_ident),
Unnamed(fields, IsTuple::Yes) => {
let exprs = fields.iter().map(|sp| default_call(*sp)).collect();
let exprs = fields.iter().map(|sp| default_call(cx, *sp)).collect();
cx.expr_call_ident(trait_span, substr.type_ident, exprs)
}
Named(fields) => {
let default_fields = fields
.iter()
.map(|&(ident, span)| cx.field_imm(span, ident, default_call(span)))
.map(|(ident, span, default_val)| {
let value = match default_val {
// We use `Default::default()`.
None => default_call(cx, *span),
// We use the field default const expression.
Some(val) => {
cx.expr(val.value.span, ast::ExprKind::ConstBlock(val.clone()))
}
};
cx.field_imm(*span, *ident, value)
})
.collect();
cx.expr_struct_ident(trait_span, substr.type_ident, default_fields)
}
@ -93,10 +105,38 @@ fn default_enum_substructure(
} {
Ok(default_variant) => {
// We now know there is exactly one unit variant with exactly one `#[default]` attribute.
cx.expr_path(cx.path(default_variant.span, vec![
Ident::new(kw::SelfUpper, default_variant.span),
default_variant.ident,
]))
match &default_variant.data {
VariantData::Unit(_) => cx.expr_path(cx.path(default_variant.span, vec![
Ident::new(kw::SelfUpper, default_variant.span),
default_variant.ident,
])),
VariantData::Struct { fields, .. } => {
// This only happens if `#![feature(default_field_values)]`. We have validated
// all fields have default values in the definition.
let default_fields = fields
.iter()
.map(|field| {
cx.field_imm(field.span, field.ident.unwrap(), match &field.default {
// We use `Default::default()`.
None => default_call(cx, field.span),
// We use the field default const expression.
Some(val) => {
cx.expr(val.value.span, ast::ExprKind::ConstBlock(val.clone()))
}
})
})
.collect();
let path = cx.path(default_variant.span, vec![
Ident::new(kw::SelfUpper, default_variant.span),
default_variant.ident,
]);
cx.expr_struct(default_variant.span, path, default_fields)
}
// Logic error in `extract_default_variant`.
VariantData::Tuple(..) => {
cx.dcx().bug("encountered tuple variant annotated with `#[default]`")
}
}
}
Err(guar) => DummyResult::raw_expr(trait_span, Some(guar)),
};
@ -156,8 +196,20 @@ fn extract_default_variant<'a>(
}
};
if !matches!(variant.data, VariantData::Unit(..)) {
let guar = cx.dcx().emit_err(errors::NonUnitDefault { span: variant.ident.span });
if cx.ecfg.features.default_field_values()
&& let VariantData::Struct { fields, .. } = &variant.data
&& fields.iter().all(|f| f.default.is_some())
// Disallow `#[default] Variant {}`
&& !fields.is_empty()
{
// Allowed
} else if !matches!(variant.data, VariantData::Unit(..)) {
let post = if cx.ecfg.features.default_field_values() {
" or variants where every field has a default value"
} else {
""
};
let guar = cx.dcx().emit_err(errors::NonUnitDefault { span: variant.ident.span, post });
return Err(guar);
}
@ -216,7 +268,12 @@ struct DetectNonVariantDefaultAttr<'a, 'b> {
impl<'a, 'b> rustc_ast::visit::Visitor<'a> for DetectNonVariantDefaultAttr<'a, 'b> {
fn visit_attribute(&mut self, attr: &'a rustc_ast::Attribute) {
if attr.has_name(kw::Default) {
self.cx.dcx().emit_err(errors::NonUnitDefault { span: attr.span });
let post = if self.cx.ecfg.features.default_field_values() {
" or variants where every field has a default value"
} else {
""
};
self.cx.dcx().emit_err(errors::NonUnitDefault { span: attr.span, post });
}
rustc_ast::visit::walk_attribute(self, attr);

View file

@ -182,8 +182,8 @@ pub(crate) use StaticFields::*;
pub(crate) use SubstructureFields::*;
use rustc_ast::ptr::P;
use rustc_ast::{
self as ast, BindingMode, ByRef, EnumDef, Expr, GenericArg, GenericParamKind, Generics,
Mutability, PatKind, VariantData,
self as ast, AnonConst, BindingMode, ByRef, EnumDef, Expr, GenericArg, GenericParamKind,
Generics, Mutability, PatKind, VariantData,
};
use rustc_attr as attr;
use rustc_expand::base::{Annotatable, ExtCtxt};
@ -296,7 +296,7 @@ pub(crate) enum StaticFields {
/// Tuple and unit structs/enum variants like this.
Unnamed(Vec<Span>, IsTuple),
/// Normal structs/struct variants.
Named(Vec<(Ident, Span)>),
Named(Vec<(Ident, Span, Option<AnonConst>)>),
}
/// A summary of the possible sets of fields.
@ -1435,7 +1435,7 @@ impl<'a> TraitDef<'a> {
for field in struct_def.fields() {
let sp = field.span.with_ctxt(self.span.ctxt());
match field.ident {
Some(ident) => named_idents.push((ident, sp)),
Some(ident) => named_idents.push((ident, sp, field.default.clone())),
_ => just_spans.push(sp),
}
}

View file

@ -424,6 +424,7 @@ pub(crate) struct MultipleDefaultsSugg {
pub(crate) struct NonUnitDefault {
#[primary_span]
pub(crate) span: Span,
pub(crate) post: &'static str,
}
#[derive(Diagnostic)]

View file

@ -55,26 +55,26 @@ impl<T: ?Sized> LegacyReceiver for &mut T {}
impl<T: ?Sized> LegacyReceiver for Box<T> {}
#[lang = "copy"]
pub unsafe trait Copy {}
pub trait Copy {}
unsafe impl Copy for bool {}
unsafe impl Copy for u8 {}
unsafe impl Copy for u16 {}
unsafe impl Copy for u32 {}
unsafe impl Copy for u64 {}
unsafe impl Copy for u128 {}
unsafe impl Copy for usize {}
unsafe impl Copy for i8 {}
unsafe impl Copy for i16 {}
unsafe impl Copy for i32 {}
unsafe impl Copy for isize {}
unsafe impl Copy for f32 {}
unsafe impl Copy for f64 {}
unsafe impl Copy for char {}
unsafe impl<'a, T: ?Sized> Copy for &'a T {}
unsafe impl<T: ?Sized> Copy for *const T {}
unsafe impl<T: ?Sized> Copy for *mut T {}
unsafe impl<T: Copy> Copy for Option<T> {}
impl Copy for bool {}
impl Copy for u8 {}
impl Copy for u16 {}
impl Copy for u32 {}
impl Copy for u64 {}
impl Copy for u128 {}
impl Copy for usize {}
impl Copy for i8 {}
impl Copy for i16 {}
impl Copy for i32 {}
impl Copy for isize {}
impl Copy for f32 {}
impl Copy for f64 {}
impl Copy for char {}
impl<'a, T: ?Sized> Copy for &'a T {}
impl<T: ?Sized> Copy for *const T {}
impl<T: ?Sized> Copy for *mut T {}
impl<T: Copy> Copy for Option<T> {}
#[lang = "sync"]
pub unsafe trait Sync {}

View file

@ -52,24 +52,24 @@ impl<T: ?Sized> LegacyReceiver for &mut T {}
impl<T: ?Sized, A: Allocator> LegacyReceiver for Box<T, A> {}
#[lang = "copy"]
pub unsafe trait Copy {}
pub trait Copy {}
unsafe impl Copy for bool {}
unsafe impl Copy for u8 {}
unsafe impl Copy for u16 {}
unsafe impl Copy for u32 {}
unsafe impl Copy for u64 {}
unsafe impl Copy for usize {}
unsafe impl Copy for i8 {}
unsafe impl Copy for i16 {}
unsafe impl Copy for i32 {}
unsafe impl Copy for isize {}
unsafe impl Copy for f32 {}
unsafe impl Copy for f64 {}
unsafe impl Copy for char {}
unsafe impl<'a, T: ?Sized> Copy for &'a T {}
unsafe impl<T: ?Sized> Copy for *const T {}
unsafe impl<T: ?Sized> Copy for *mut T {}
impl Copy for bool {}
impl Copy for u8 {}
impl Copy for u16 {}
impl Copy for u32 {}
impl Copy for u64 {}
impl Copy for usize {}
impl Copy for i8 {}
impl Copy for i16 {}
impl Copy for i32 {}
impl Copy for isize {}
impl Copy for f32 {}
impl Copy for f64 {}
impl Copy for char {}
impl<'a, T: ?Sized> Copy for &'a T {}
impl<T: ?Sized> Copy for *const T {}
impl<T: ?Sized> Copy for *mut T {}
#[lang = "sync"]
pub unsafe trait Sync {}

View file

@ -867,6 +867,13 @@ impl<'gcc, 'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> {
template_str.push_str("\n.popsection");
self.context.add_top_level_asm(None, &template_str);
}
fn mangled_name(&self, instance: Instance<'tcx>) -> String {
// TODO(@Amanieu): Additional mangling is needed on
// some targets to add a leading underscore (Mach-O)
// or byte count suffixes (x86 Windows).
self.tcx.symbol_name(instance).name.to_string()
}
}
fn modifier_to_gcc(

View file

@ -442,6 +442,14 @@ impl<'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'_, 'tcx> {
);
}
}
fn mangled_name(&self, instance: Instance<'tcx>) -> String {
let llval = self.get_fn(instance);
llvm::build_string(|s| unsafe {
llvm::LLVMRustGetMangledName(llval, s);
})
.expect("symbol is not valid UTF-8")
}
}
pub(crate) fn inline_asm_call<'ll>(
@ -504,14 +512,13 @@ pub(crate) fn inline_asm_call<'ll>(
let key = "srcloc";
let kind = llvm::LLVMGetMDKindIDInContext(
bx.llcx,
key.as_ptr() as *const c_char,
key.as_ptr().cast::<c_char>(),
key.len() as c_uint,
);
// srcloc contains one integer for each line of assembly code.
// Unfortunately this isn't enough to encode a full span so instead
// we just encode the start position of each line.
// FIXME: Figure out a way to pass the entire line spans.
// `srcloc` contains one 64-bit integer for each line of assembly code,
// where the lower 32 bits hold the lo byte position and the upper 32 bits
// hold the hi byte position.
let mut srcloc = vec![];
if dia == llvm::AsmDialect::Intel && line_spans.len() > 1 {
// LLVM inserts an extra line to add the ".intel_syntax", so add
@ -521,13 +528,13 @@ pub(crate) fn inline_asm_call<'ll>(
// due to the asm template string coming from a macro. LLVM will
// default to the first srcloc for lines that don't have an
// associated srcloc.
srcloc.push(llvm::LLVMValueAsMetadata(bx.const_i32(0)));
srcloc.push(llvm::LLVMValueAsMetadata(bx.const_u64(0)));
}
srcloc.extend(
line_spans
.iter()
.map(|span| llvm::LLVMValueAsMetadata(bx.const_i32(span.lo().to_u32() as i32))),
);
srcloc.extend(line_spans.iter().map(|span| {
llvm::LLVMValueAsMetadata(bx.const_u64(
u64::from(span.lo().to_u32()) | (u64::from(span.hi().to_u32()) << 32),
))
}));
let md = llvm::LLVMMDNodeInContext2(bx.llcx, srcloc.as_ptr(), srcloc.len());
let md = llvm::LLVMMetadataAsValue(&bx.llcx, md);
llvm::LLVMSetMetadata(call, kind, md);

View file

@ -395,17 +395,9 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
to_add.push(MemoryEffects::None.create_attr(cx.llcx));
}
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NAKED) {
to_add.push(AttributeKind::Naked.create_attr(cx.llcx));
// HACK(jubilee): "indirect branch tracking" works by attaching prologues to functions.
// And it is a module-level attribute, so the alternative is pulling naked functions into
// new LLVM modules. Otherwise LLVM's "naked" functions come with endbr prefixes per
// https://github.com/rust-lang/rust/issues/98768
to_add.push(AttributeKind::NoCfCheck.create_attr(cx.llcx));
if llvm_util::get_version() < (19, 0, 0) {
// Prior to LLVM 19, branch-target-enforcement was disabled by setting the attribute to
// the string "false". Now it is disabled by absence of the attribute.
to_add.push(llvm::CreateAttrStringValue(cx.llcx, "branch-target-enforcement", "false"));
}
// do nothing; a naked function is converted into an extern function
// and a global assembly block. LLVM's support for naked functions is
// not used.
} else {
// Do not set sanitizer attributes for naked functions.
to_add.extend(sanitize_attrs(cx, codegen_fn_attrs.no_sanitize));

View file

@ -25,8 +25,8 @@ use rustc_session::Session;
use rustc_session::config::{
self, Lto, OutputType, Passes, RemapPathScopeComponents, SplitDwarfKind, SwitchWithOptPath,
};
use rustc_span::InnerSpan;
use rustc_span::symbol::sym;
use rustc_span::{BytePos, InnerSpan, Pos, SpanData, SyntaxContext};
use rustc_target::spec::{CodeModel, RelocModel, SanitizerSet, SplitDebuginfo, TlsModel};
use tracing::debug;
@ -415,21 +415,32 @@ fn report_inline_asm(
cgcx: &CodegenContext<LlvmCodegenBackend>,
msg: String,
level: llvm::DiagnosticLevel,
mut cookie: u64,
cookie: u64,
source: Option<(String, Vec<InnerSpan>)>,
) {
// In LTO build we may get srcloc values from other crates which are invalid
// since they use a different source map. To be safe we just suppress these
// in LTO builds.
if matches!(cgcx.lto, Lto::Fat | Lto::Thin) {
cookie = 0;
}
let span = if cookie == 0 || matches!(cgcx.lto, Lto::Fat | Lto::Thin) {
SpanData::default()
} else {
let lo = BytePos::from_u32(cookie as u32);
let hi = BytePos::from_u32((cookie >> 32) as u32);
SpanData {
lo,
// LLVM version < 19 silently truncates the cookie to 32 bits in some situations.
hi: if hi.to_u32() != 0 { hi } else { lo },
ctxt: SyntaxContext::root(),
parent: None,
}
};
let level = match level {
llvm::DiagnosticLevel::Error => Level::Error,
llvm::DiagnosticLevel::Warning => Level::Warning,
llvm::DiagnosticLevel::Note | llvm::DiagnosticLevel::Remark => Level::Note,
};
cgcx.diag_emitter.inline_asm_error(cookie.try_into().unwrap(), msg, level, source);
let msg = msg.strip_prefix("error: ").unwrap_or(&msg).to_string();
cgcx.diag_emitter.inline_asm_error(span, msg, level, source);
}
unsafe extern "C" fn diagnostic_handler(info: &DiagnosticInfo, user: *mut c_void) {

View file

@ -159,6 +159,16 @@ pub(crate) unsafe fn create_module<'ll>(
// See https://github.com/llvm/llvm-project/pull/112084
target_data_layout = target_data_layout.replace("-i128:128", "");
}
if sess.target.arch.starts_with("powerpc64") {
// LLVM 20 updates the powerpc64 layout to correctly align 128 bit integers to 128 bit.
// See https://github.com/llvm/llvm-project/pull/118004
target_data_layout = target_data_layout.replace("-i128:128", "");
}
if sess.target.arch.starts_with("wasm32") || sess.target.arch.starts_with("wasm64") {
// LLVM 20 updates the wasm(32|64) layout to correctly align 128 bit integers to 128 bit.
// See https://github.com/llvm/llvm-project/pull/119204
target_data_layout = target_data_layout.replace("-i128:128", "");
}
}
// Ensure the data-layout values hardcoded remain the defaults.

View file

@ -152,6 +152,34 @@ impl CoverageSpan {
}
}
/// Holds tables of the various region types in one struct.
///
/// Don't pass this struct across FFI; pass the individual region tables as
/// pointer/length pairs instead.
///
/// Each field name has a `_regions` suffix for improved readability after
/// exhaustive destructing, which ensures that all region types are handled.
#[derive(Clone, Debug, Default)]
pub(crate) struct Regions {
pub(crate) code_regions: Vec<CodeRegion>,
pub(crate) branch_regions: Vec<BranchRegion>,
pub(crate) mcdc_branch_regions: Vec<MCDCBranchRegion>,
pub(crate) mcdc_decision_regions: Vec<MCDCDecisionRegion>,
}
impl Regions {
/// Returns true if none of this structure's tables contain any regions.
pub(crate) fn has_no_regions(&self) -> bool {
let Self { code_regions, branch_regions, mcdc_branch_regions, mcdc_decision_regions } =
self;
code_regions.is_empty()
&& branch_regions.is_empty()
&& mcdc_branch_regions.is_empty()
&& mcdc_decision_regions.is_empty()
}
}
/// Must match the layout of `LLVMRustCoverageCodeRegion`.
#[derive(Clone, Debug)]
#[repr(C)]

View file

@ -62,11 +62,10 @@ pub(crate) fn write_filenames_to_buffer<'a>(
pub(crate) fn write_function_mappings_to_buffer(
virtual_file_mapping: &[u32],
expressions: &[ffi::CounterExpression],
code_regions: &[ffi::CodeRegion],
branch_regions: &[ffi::BranchRegion],
mcdc_branch_regions: &[ffi::MCDCBranchRegion],
mcdc_decision_regions: &[ffi::MCDCDecisionRegion],
regions: &ffi::Regions,
) -> Vec<u8> {
let ffi::Regions { code_regions, branch_regions, mcdc_branch_regions, mcdc_decision_regions } =
regions;
llvm::build_byte_buffer(|buffer| unsafe {
llvm::LLVMRustCoverageWriteFunctionMappingsToBuffer(
virtual_file_mapping.as_ptr(),

View file

@ -1,159 +1,38 @@
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::FxIndexSet;
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::CoverageIdsInfo;
use rustc_middle::mir::coverage::{
CounterId, CovTerm, Expression, ExpressionId, FunctionCoverageInfo, Mapping, MappingKind, Op,
CovTerm, CoverageIdsInfo, Expression, FunctionCoverageInfo, Mapping, MappingKind, Op,
SourceRegion,
};
use rustc_middle::ty::Instance;
use tracing::debug;
use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind};
/// Holds all of the coverage mapping data associated with a function instance,
/// collected during traversal of `Coverage` statements in the function's MIR.
#[derive(Debug)]
pub(crate) struct FunctionCoverageCollector<'tcx> {
/// Coverage info that was attached to this function by the instrumentor.
function_coverage_info: &'tcx FunctionCoverageInfo,
ids_info: &'tcx CoverageIdsInfo,
is_used: bool,
}
impl<'tcx> FunctionCoverageCollector<'tcx> {
/// Creates a new set of coverage data for a used (called) function.
pub(crate) fn new(
instance: Instance<'tcx>,
function_coverage_info: &'tcx FunctionCoverageInfo,
ids_info: &'tcx CoverageIdsInfo,
) -> Self {
Self::create(instance, function_coverage_info, ids_info, true)
}
/// Creates a new set of coverage data for an unused (never called) function.
pub(crate) fn unused(
instance: Instance<'tcx>,
function_coverage_info: &'tcx FunctionCoverageInfo,
ids_info: &'tcx CoverageIdsInfo,
) -> Self {
Self::create(instance, function_coverage_info, ids_info, false)
}
fn create(
instance: Instance<'tcx>,
function_coverage_info: &'tcx FunctionCoverageInfo,
ids_info: &'tcx CoverageIdsInfo,
is_used: bool,
) -> Self {
let num_counters = function_coverage_info.num_counters;
let num_expressions = function_coverage_info.expressions.len();
debug!(
"FunctionCoverage::create(instance={instance:?}) has \
num_counters={num_counters}, num_expressions={num_expressions}, is_used={is_used}"
);
Self { function_coverage_info, ids_info, is_used }
}
/// Identify expressions that will always have a value of zero, and note
/// their IDs in [`ZeroExpressions`]. Mappings that refer to a zero expression
/// can instead become mappings to a constant zero value.
///
/// This method mainly exists to preserve the simplifications that were
/// already being performed by the Rust-side expression renumbering, so that
/// the resulting coverage mappings don't get worse.
fn identify_zero_expressions(&self) -> ZeroExpressions {
// The set of expressions that either were optimized out entirely, or
// have zero as both of their operands, and will therefore always have
// a value of zero. Other expressions that refer to these as operands
// can have those operands replaced with `CovTerm::Zero`.
let mut zero_expressions = ZeroExpressions::default();
// Simplify a copy of each expression based on lower-numbered expressions,
// and then update the set of always-zero expressions if necessary.
// (By construction, expressions can only refer to other expressions
// that have lower IDs, so one pass is sufficient.)
for (id, expression) in self.function_coverage_info.expressions.iter_enumerated() {
if !self.is_used || !self.ids_info.expressions_seen.contains(id) {
// If an expression was not seen, it must have been optimized away,
// so any operand that refers to it can be replaced with zero.
zero_expressions.insert(id);
continue;
}
// We don't need to simplify the actual expression data in the
// expressions list; we can just simplify a temporary copy and then
// use that to update the set of always-zero expressions.
let Expression { mut lhs, op, mut rhs } = *expression;
// If an expression has an operand that is also an expression, the
// operand's ID must be strictly lower. This is what lets us find
// all zero expressions in one pass.
let assert_operand_expression_is_lower = |operand_id: ExpressionId| {
assert!(
operand_id < id,
"Operand {operand_id:?} should be less than {id:?} in {expression:?}",
)
};
// If an operand refers to a counter or expression that is always
// zero, then that operand can be replaced with `CovTerm::Zero`.
let maybe_set_operand_to_zero = |operand: &mut CovTerm| {
if let CovTerm::Expression(id) = *operand {
assert_operand_expression_is_lower(id);
}
if is_zero_term(&self.ids_info.counters_seen, &zero_expressions, *operand) {
*operand = CovTerm::Zero;
}
};
maybe_set_operand_to_zero(&mut lhs);
maybe_set_operand_to_zero(&mut rhs);
// Coverage counter values cannot be negative, so if an expression
// involves subtraction from zero, assume that its RHS must also be zero.
// (Do this after simplifications that could set the LHS to zero.)
if lhs == CovTerm::Zero && op == Op::Subtract {
rhs = CovTerm::Zero;
}
// After the above simplifications, if both operands are zero, then
// we know that this expression is always zero too.
if lhs == CovTerm::Zero && rhs == CovTerm::Zero {
zero_expressions.insert(id);
}
}
zero_expressions
}
pub(crate) fn into_finished(self) -> FunctionCoverage<'tcx> {
let zero_expressions = self.identify_zero_expressions();
let FunctionCoverageCollector { function_coverage_info, ids_info, is_used, .. } = self;
FunctionCoverage { function_coverage_info, ids_info, is_used, zero_expressions }
}
}
pub(crate) struct FunctionCoverage<'tcx> {
pub(crate) function_coverage_info: &'tcx FunctionCoverageInfo,
ids_info: &'tcx CoverageIdsInfo,
is_used: bool,
zero_expressions: ZeroExpressions,
/// If `None`, the corresponding function is unused.
ids_info: Option<&'tcx CoverageIdsInfo>,
}
impl<'tcx> FunctionCoverage<'tcx> {
pub(crate) fn new_used(
function_coverage_info: &'tcx FunctionCoverageInfo,
ids_info: &'tcx CoverageIdsInfo,
) -> Self {
Self { function_coverage_info, ids_info: Some(ids_info) }
}
pub(crate) fn new_unused(function_coverage_info: &'tcx FunctionCoverageInfo) -> Self {
Self { function_coverage_info, ids_info: None }
}
/// Returns true for a used (called) function, and false for an unused function.
pub(crate) fn is_used(&self) -> bool {
self.is_used
self.ids_info.is_some()
}
/// 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(crate) fn source_hash(&self) -> u64 {
if self.is_used { self.function_coverage_info.function_source_hash } else { 0 }
if self.is_used() { self.function_coverage_info.function_source_hash } else { 0 }
}
/// Convert this function's coverage expression data into a form that can be
@ -196,37 +75,10 @@ impl<'tcx> FunctionCoverage<'tcx> {
}
fn is_zero_term(&self, term: CovTerm) -> bool {
!self.is_used || is_zero_term(&self.ids_info.counters_seen, &self.zero_expressions, term)
}
}
/// Set of expression IDs that are known to always evaluate to zero.
/// Any mapping or expression operand that refers to these expressions can have
/// that reference replaced with a constant zero value.
#[derive(Default)]
struct ZeroExpressions(FxIndexSet<ExpressionId>);
impl ZeroExpressions {
fn insert(&mut self, id: ExpressionId) {
self.0.insert(id);
}
fn contains(&self, id: ExpressionId) -> bool {
self.0.contains(&id)
}
}
/// Returns `true` if the given term is known to have a value of zero, taking
/// into account knowledge of which counters are unused and which expressions
/// are always zero.
fn is_zero_term(
counters_seen: &BitSet<CounterId>,
zero_expressions: &ZeroExpressions,
term: CovTerm,
) -> bool {
match term {
CovTerm::Zero => true,
CovTerm::Counter(id) => !counters_seen.contains(id),
CovTerm::Expression(id) => zero_expressions.contains(id),
match self.ids_info {
Some(ids_info) => ids_info.is_zero_term(term),
// This function is unused, so all coverage counters/expressions are zero.
None => true,
}
}
}

View file

@ -1,4 +1,3 @@
use std::ffi::CString;
use std::iter;
use itertools::Itertools as _;
@ -9,21 +8,22 @@ use rustc_codegen_ssa::traits::{
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_index::IndexVec;
use rustc_middle::mir::coverage::MappingKind;
use rustc_middle::ty::{self, TyCtxt};
use rustc_middle::{bug, mir};
use rustc_session::RemapFileNameExt;
use rustc_session::config::RemapPathScopeComponents;
use rustc_span::def_id::DefIdSet;
use rustc_span::{Span, Symbol};
use rustc_target::spec::HasTargetSpec;
use tracing::debug;
use crate::common::CodegenCx;
use crate::coverageinfo::map_data::{FunctionCoverage, FunctionCoverageCollector};
use crate::coverageinfo::{ffi, llvm_cov};
use crate::coverageinfo::llvm_cov;
use crate::coverageinfo::map_data::FunctionCoverage;
use crate::coverageinfo::mapgen::covfun::prepare_covfun_record;
use crate::llvm;
mod covfun;
/// Generates and exports the coverage map, which is embedded in special
/// linker sections in the final binary.
///
@ -63,16 +63,11 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
None => return,
};
if function_coverage_map.is_empty() {
// This module has no functions with coverage instrumentation
// This CGU has no functions with coverage instrumentation.
return;
}
let function_coverage_entries = function_coverage_map
.into_iter()
.map(|(instance, function_coverage)| (instance, function_coverage.into_finished()))
.collect::<Vec<_>>();
let all_file_names = function_coverage_entries
let all_file_names = function_coverage_map
.iter()
.map(|(_, fn_cov)| fn_cov.function_coverage_info.body_span)
.map(|span| span_file_name(tcx, span));
@ -85,47 +80,28 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
let filenames_val = cx.const_bytes(&filenames_buffer);
let filenames_ref = llvm_cov::hash_bytes(&filenames_buffer);
// Generate the coverage map header, which contains the filenames used by
// this CGU's coverage mappings, and store it in a well-known global.
generate_covmap_record(cx, covmap_version, filenames_size, filenames_val);
let mut unused_function_names = Vec::new();
// Encode coverage mappings and generate function records
for (instance, function_coverage) in function_coverage_entries {
debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance);
let covfun_records = function_coverage_map
.into_iter()
.filter_map(|(instance, function_coverage)| {
prepare_covfun_record(tcx, &global_file_table, instance, &function_coverage)
})
.collect::<Vec<_>>();
let mangled_function_name = tcx.symbol_name(instance).name;
let source_hash = function_coverage.source_hash();
let is_used = function_coverage.is_used();
// If there are no covfun records for this CGU, don't generate a covmap record.
// Emitting a covmap record without any covfun records causes `llvm-cov` to
// fail when generating coverage reports, and if there are no covfun records
// then the covmap record isn't useful anyway.
// This should prevent a repeat of <https://github.com/rust-lang/rust/issues/133606>.
if covfun_records.is_empty() {
return;
}
let coverage_mapping_buffer =
encode_mappings_for_function(tcx, &global_file_table, &function_coverage);
for covfun in &covfun_records {
unused_function_names.extend(covfun.mangled_function_name_if_unused());
if coverage_mapping_buffer.is_empty() {
if function_coverage.is_used() {
bug!(
"A used function should have had coverage mapping data but did not: {}",
mangled_function_name
);
} else {
debug!("unused function had no coverage mapping data: {}", mangled_function_name);
continue;
}
}
if !is_used {
unused_function_names.push(mangled_function_name);
}
generate_covfun_record(
cx,
mangled_function_name,
source_hash,
filenames_ref,
coverage_mapping_buffer,
is_used,
);
covfun::generate_covfun_record(cx, filenames_ref, covfun)
}
// For unused functions, we need to take their mangled names and store them
@ -146,6 +122,11 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
llvm::set_linkage(array, llvm::Linkage::InternalLinkage);
llvm::set_initializer(array, initializer);
}
// Generate the coverage map header, which contains the filenames used by
// this CGU's coverage mappings, and store it in a well-known global.
// (This is skipped if we returned early due to having no covfun records.)
generate_covmap_record(cx, covmap_version, filenames_size, filenames_val);
}
/// Maps "global" (per-CGU) file ID numbers to their underlying filenames.
@ -213,7 +194,7 @@ rustc_index::newtype_index! {
/// Holds a mapping from "local" (per-function) file IDs to "global" (per-CGU)
/// file IDs.
#[derive(Default)]
#[derive(Debug, Default)]
struct VirtualFileMapping {
local_to_global: IndexVec<LocalFileId, GlobalFileId>,
global_to_local: FxIndexMap<GlobalFileId, LocalFileId>,
@ -227,10 +208,10 @@ impl VirtualFileMapping {
.or_insert_with(|| self.local_to_global.push(global_file_id))
}
fn into_vec(self) -> Vec<u32> {
// This conversion should be optimized away to ~zero overhead.
// In any case, it's probably not hot enough to worry about.
self.local_to_global.into_iter().map(|global| global.as_u32()).collect()
fn to_vec(&self) -> Vec<u32> {
// This clone could be avoided by transmuting `&[GlobalFileId]` to `&[u32]`,
// but it isn't hot or expensive enough to justify the extra unsafety.
self.local_to_global.iter().map(|&global| GlobalFileId::as_u32(global)).collect()
}
}
@ -241,83 +222,6 @@ fn span_file_name(tcx: TyCtxt<'_>, span: Span) -> Symbol {
Symbol::intern(&name)
}
/// Using the expressions and counter regions collected for a single function,
/// generate the variable-sized payload of its corresponding `__llvm_covfun`
/// entry. The payload is returned as a vector of bytes.
///
/// Newly-encountered filenames will be added to the global file table.
fn encode_mappings_for_function(
tcx: TyCtxt<'_>,
global_file_table: &GlobalFileTable,
function_coverage: &FunctionCoverage<'_>,
) -> Vec<u8> {
let counter_regions = function_coverage.counter_regions();
if counter_regions.is_empty() {
return Vec::new();
}
let expressions = function_coverage.counter_expressions().collect::<Vec<_>>();
let mut virtual_file_mapping = VirtualFileMapping::default();
let mut code_regions = vec![];
let mut branch_regions = vec![];
let mut mcdc_branch_regions = vec![];
let mut mcdc_decision_regions = vec![];
// Currently a function's mappings must all be in the same file as its body span.
let file_name = span_file_name(tcx, function_coverage.function_coverage_info.body_span);
// Look up the global file ID for that filename.
let global_file_id = global_file_table.global_file_id_for_file_name(file_name);
// Associate that global file ID with a local file ID for this function.
let local_file_id = virtual_file_mapping.local_id_for_global(global_file_id);
debug!(" file id: {local_file_id:?} => {global_file_id:?} = '{file_name:?}'");
// For each counter/region pair in this function+file, convert it to a
// form suitable for FFI.
for (mapping_kind, region) in counter_regions {
debug!("Adding counter {mapping_kind:?} to map for {region:?}");
let span = ffi::CoverageSpan::from_source_region(local_file_id, region);
match mapping_kind {
MappingKind::Code(term) => {
code_regions.push(ffi::CodeRegion { span, counter: ffi::Counter::from_term(term) });
}
MappingKind::Branch { true_term, false_term } => {
branch_regions.push(ffi::BranchRegion {
span,
true_counter: ffi::Counter::from_term(true_term),
false_counter: ffi::Counter::from_term(false_term),
});
}
MappingKind::MCDCBranch { true_term, false_term, mcdc_params } => {
mcdc_branch_regions.push(ffi::MCDCBranchRegion {
span,
true_counter: ffi::Counter::from_term(true_term),
false_counter: ffi::Counter::from_term(false_term),
mcdc_branch_params: ffi::mcdc::BranchParameters::from(mcdc_params),
});
}
MappingKind::MCDCDecision(mcdc_decision_params) => {
mcdc_decision_regions.push(ffi::MCDCDecisionRegion {
span,
mcdc_decision_params: ffi::mcdc::DecisionParameters::from(mcdc_decision_params),
});
}
}
}
// Encode the function's coverage mappings into a buffer.
llvm_cov::write_function_mappings_to_buffer(
&virtual_file_mapping.into_vec(),
&expressions,
&code_regions,
&branch_regions,
&mcdc_branch_regions,
&mcdc_decision_regions,
)
}
/// Generates the contents of the covmap record for this CGU, which mostly
/// consists of a header and a list of filenames. The record is then stored
/// as a global variable in the `__llvm_covmap` section.
@ -355,61 +259,6 @@ fn generate_covmap_record<'ll>(
cx.add_used_global(llglobal);
}
/// Generates the contents of the covfun record for this function, which
/// contains the function's coverage mapping data. The record is then stored
/// as a global variable in the `__llvm_covfun` section.
fn generate_covfun_record(
cx: &CodegenCx<'_, '_>,
mangled_function_name: &str,
source_hash: u64,
filenames_ref: u64,
coverage_mapping_buffer: Vec<u8>,
is_used: bool,
) {
// Concatenate the encoded coverage mappings
let coverage_mapping_size = coverage_mapping_buffer.len();
let coverage_mapping_val = cx.const_bytes(&coverage_mapping_buffer);
let func_name_hash = llvm_cov::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);
let filenames_ref_val = cx.const_u64(filenames_ref);
let func_record_val = cx.const_struct(
&[
func_name_hash_val,
coverage_mapping_size_val,
source_hash_val,
filenames_ref_val,
coverage_mapping_val,
],
/*packed=*/ true,
);
// Choose a variable name to hold this function's covfun data.
// Functions that are used have a suffix ("u") to distinguish them from
// unused copies of the same function (from different CGUs), so that if a
// linker sees both it won't discard the used copy's data.
let func_record_var_name =
CString::new(format!("__covrec_{:X}{}", func_name_hash, if is_used { "u" } else { "" }))
.unwrap();
debug!("function record var name: {:?}", func_record_var_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, cx.covfun_section_name());
// LLVM's coverage mapping format specifies 8-byte alignment for items in this section.
// <https://llvm.org/docs/CoverageMappingFormat.html>
llvm::set_alignment(llglobal, Align::EIGHT);
if cx.target_spec().supports_comdat() {
llvm::set_comdat(cx.llmod, llglobal, &func_record_var_name);
}
cx.add_used_global(llglobal);
}
/// Each CGU will normally only emit coverage metadata for the functions that it actually generates.
/// But since we don't want unused functions to disappear from coverage reports, we also scan for
/// functions that were instrumented but are not participating in codegen.
@ -536,11 +385,6 @@ fn add_unused_function_coverage<'tcx>(
);
// An unused function's mappings will all be rewritten to map to zero.
let function_coverage = FunctionCoverageCollector::unused(
instance,
function_coverage_info,
tcx.coverage_ids_info(instance.def),
);
let function_coverage = FunctionCoverage::new_unused(function_coverage_info);
cx.coverage_cx().function_coverage_map.borrow_mut().insert(instance, function_coverage);
}

View file

@ -0,0 +1,200 @@
//! For each function that was instrumented for coverage, we need to embed its
//! corresponding coverage mapping metadata inside the `__llvm_covfun`[^win]
//! linker section of the final binary.
//!
//! [^win]: On Windows the section name is `.lcovfun`.
use std::ffi::CString;
use rustc_abi::Align;
use rustc_codegen_ssa::traits::{
BaseTypeCodegenMethods, ConstCodegenMethods, StaticCodegenMethods,
};
use rustc_middle::bug;
use rustc_middle::mir::coverage::MappingKind;
use rustc_middle::ty::{Instance, TyCtxt};
use rustc_target::spec::HasTargetSpec;
use tracing::debug;
use crate::common::CodegenCx;
use crate::coverageinfo::map_data::FunctionCoverage;
use crate::coverageinfo::mapgen::{GlobalFileTable, VirtualFileMapping, span_file_name};
use crate::coverageinfo::{ffi, llvm_cov};
use crate::llvm;
/// Intermediate coverage metadata for a single function, used to help build
/// the final record that will be embedded in the `__llvm_covfun` section.
#[derive(Debug)]
pub(crate) struct CovfunRecord<'tcx> {
mangled_function_name: &'tcx str,
source_hash: u64,
is_used: bool,
virtual_file_mapping: VirtualFileMapping,
expressions: Vec<ffi::CounterExpression>,
regions: ffi::Regions,
}
impl<'tcx> CovfunRecord<'tcx> {
/// FIXME(Zalathar): Make this the responsibility of the code that determines
/// which functions are unused.
pub(crate) fn mangled_function_name_if_unused(&self) -> Option<&'tcx str> {
(!self.is_used).then_some(self.mangled_function_name)
}
}
pub(crate) fn prepare_covfun_record<'tcx>(
tcx: TyCtxt<'tcx>,
global_file_table: &GlobalFileTable,
instance: Instance<'tcx>,
function_coverage: &FunctionCoverage<'tcx>,
) -> Option<CovfunRecord<'tcx>> {
let mut covfun = CovfunRecord {
mangled_function_name: tcx.symbol_name(instance).name,
source_hash: function_coverage.source_hash(),
is_used: function_coverage.is_used(),
virtual_file_mapping: VirtualFileMapping::default(),
expressions: function_coverage.counter_expressions().collect::<Vec<_>>(),
regions: ffi::Regions::default(),
};
fill_region_tables(tcx, global_file_table, function_coverage, &mut covfun);
if covfun.regions.has_no_regions() {
if covfun.is_used {
bug!("a used function should have had coverage mapping data but did not: {covfun:?}");
} else {
debug!(?covfun, "unused function had no coverage mapping data");
return None;
}
}
Some(covfun)
}
/// Populates the mapping region tables in the current function's covfun record.
fn fill_region_tables<'tcx>(
tcx: TyCtxt<'tcx>,
global_file_table: &GlobalFileTable,
function_coverage: &FunctionCoverage<'tcx>,
covfun: &mut CovfunRecord<'tcx>,
) {
let counter_regions = function_coverage.counter_regions();
if counter_regions.is_empty() {
return;
}
// Currently a function's mappings must all be in the same file as its body span.
let file_name = span_file_name(tcx, function_coverage.function_coverage_info.body_span);
// Look up the global file ID for that filename.
let global_file_id = global_file_table.global_file_id_for_file_name(file_name);
// Associate that global file ID with a local file ID for this function.
let local_file_id = covfun.virtual_file_mapping.local_id_for_global(global_file_id);
debug!(" file id: {local_file_id:?} => {global_file_id:?} = '{file_name:?}'");
let ffi::Regions { code_regions, branch_regions, mcdc_branch_regions, mcdc_decision_regions } =
&mut covfun.regions;
// For each counter/region pair in this function+file, convert it to a
// form suitable for FFI.
for (mapping_kind, region) in counter_regions {
debug!("Adding counter {mapping_kind:?} to map for {region:?}");
let span = ffi::CoverageSpan::from_source_region(local_file_id, region);
match mapping_kind {
MappingKind::Code(term) => {
code_regions.push(ffi::CodeRegion { span, counter: ffi::Counter::from_term(term) });
}
MappingKind::Branch { true_term, false_term } => {
branch_regions.push(ffi::BranchRegion {
span,
true_counter: ffi::Counter::from_term(true_term),
false_counter: ffi::Counter::from_term(false_term),
});
}
MappingKind::MCDCBranch { true_term, false_term, mcdc_params } => {
mcdc_branch_regions.push(ffi::MCDCBranchRegion {
span,
true_counter: ffi::Counter::from_term(true_term),
false_counter: ffi::Counter::from_term(false_term),
mcdc_branch_params: ffi::mcdc::BranchParameters::from(mcdc_params),
});
}
MappingKind::MCDCDecision(mcdc_decision_params) => {
mcdc_decision_regions.push(ffi::MCDCDecisionRegion {
span,
mcdc_decision_params: ffi::mcdc::DecisionParameters::from(mcdc_decision_params),
});
}
}
}
}
/// Generates the contents of the covfun record for this function, which
/// contains the function's coverage mapping data. The record is then stored
/// as a global variable in the `__llvm_covfun` section.
pub(crate) fn generate_covfun_record<'tcx>(
cx: &CodegenCx<'_, 'tcx>,
filenames_ref: u64,
covfun: &CovfunRecord<'tcx>,
) {
let &CovfunRecord {
mangled_function_name,
source_hash,
is_used,
ref virtual_file_mapping,
ref expressions,
ref regions,
} = covfun;
// Encode the function's coverage mappings into a buffer.
let coverage_mapping_buffer = llvm_cov::write_function_mappings_to_buffer(
&virtual_file_mapping.to_vec(),
expressions,
regions,
);
// Concatenate the encoded coverage mappings
let coverage_mapping_size = coverage_mapping_buffer.len();
let coverage_mapping_val = cx.const_bytes(&coverage_mapping_buffer);
let func_name_hash = llvm_cov::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);
let filenames_ref_val = cx.const_u64(filenames_ref);
let func_record_val = cx.const_struct(
&[
func_name_hash_val,
coverage_mapping_size_val,
source_hash_val,
filenames_ref_val,
coverage_mapping_val,
],
/*packed=*/ true,
);
// Choose a variable name to hold this function's covfun data.
// Functions that are used have a suffix ("u") to distinguish them from
// unused copies of the same function (from different CGUs), so that if a
// linker sees both it won't discard the used copy's data.
let func_record_var_name =
CString::new(format!("__covrec_{:X}{}", func_name_hash, if is_used { "u" } else { "" }))
.unwrap();
debug!("function record var name: {:?}", func_record_var_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, cx.covfun_section_name());
// LLVM's coverage mapping format specifies 8-byte alignment for items in this section.
// <https://llvm.org/docs/CoverageMappingFormat.html>
llvm::set_alignment(llglobal, Align::EIGHT);
if cx.target_spec().supports_comdat() {
llvm::set_comdat(cx.llmod, llglobal, &func_record_var_name);
}
cx.add_used_global(llglobal);
}

View file

@ -13,7 +13,7 @@ use tracing::{debug, instrument};
use crate::builder::Builder;
use crate::common::CodegenCx;
use crate::coverageinfo::map_data::FunctionCoverageCollector;
use crate::coverageinfo::map_data::FunctionCoverage;
use crate::llvm;
pub(crate) mod ffi;
@ -24,8 +24,7 @@ mod mapgen;
/// Extra per-CGU context/state needed for coverage instrumentation.
pub(crate) struct CguCoverageContext<'ll, 'tcx> {
/// Coverage data for each instrumented function identified by DefId.
pub(crate) function_coverage_map:
RefCell<FxIndexMap<Instance<'tcx>, FunctionCoverageCollector<'tcx>>>,
pub(crate) function_coverage_map: RefCell<FxIndexMap<Instance<'tcx>, FunctionCoverage<'tcx>>>,
pub(crate) pgo_func_name_var_map: RefCell<FxHashMap<Instance<'tcx>, &'ll llvm::Value>>,
pub(crate) mcdc_condition_bitmap_map: RefCell<FxHashMap<Instance<'tcx>, Vec<&'ll llvm::Value>>>,
@ -42,9 +41,7 @@ impl<'ll, 'tcx> CguCoverageContext<'ll, 'tcx> {
}
}
fn take_function_coverage_map(
&self,
) -> FxIndexMap<Instance<'tcx>, FunctionCoverageCollector<'tcx>> {
fn take_function_coverage_map(&self) -> FxIndexMap<Instance<'tcx>, FunctionCoverage<'tcx>> {
self.function_coverage_map.replace(FxIndexMap::default())
}
@ -161,8 +158,7 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
// This includes functions that were not partitioned into this CGU,
// but were MIR-inlined into one of this CGU's functions.
coverage_cx.function_coverage_map.borrow_mut().entry(instance).or_insert_with(|| {
FunctionCoverageCollector::new(
instance,
FunctionCoverage::new_used(
function_coverage_info,
bx.tcx.coverage_ids_info(instance.def),
)

View file

@ -151,7 +151,7 @@ impl InlineAsmDiagnostic {
unsafe { SrcMgrDiagnostic::unpack(super::LLVMRustGetSMDiagnostic(di, &mut cookie)) };
InlineAsmDiagnostic {
level: smdiag.level,
cookie: cookie.into(),
cookie,
message: smdiag.message,
source: smdiag.source,
}

View file

@ -2317,7 +2317,7 @@ unsafe extern "C" {
pub fn LLVMRustGetSMDiagnostic<'a>(
DI: &'a DiagnosticInfo,
cookie_out: &mut c_uint,
cookie_out: &mut u64,
) -> &'a SMDiagnostic;
pub fn LLVMRustUnpackSMDiagnostic(

View file

@ -230,6 +230,8 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option<LLVMFea
"aarch64"
} else if sess.target.arch == "sparc64" {
"sparc"
} else if sess.target.arch == "powerpc64" {
"powerpc"
} else {
&*sess.target.arch
};
@ -289,6 +291,7 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option<LLVMFea
// https://github.com/llvm/llvm-project/blob/llvmorg-18.1.0/llvm/lib/Target/Sparc/MCTargetDesc/SparcELFObjectWriter.cpp#L26
("sparc", "v8plus") if get_version().0 == 19 => Some(LLVMFeature::new("v9")),
("sparc", "v8plus") if get_version().0 < 19 => None,
("powerpc", "power8-crypto") => Some(LLVMFeature::new("crypto")),
(_, s) => Some(LLVMFeature::new(s)),
}
}

View file

@ -17,6 +17,7 @@ regex = "1.4"
rustc_abi = { path = "../rustc_abi" }
rustc_arena = { path = "../rustc_arena" }
rustc_ast = { path = "../rustc_ast" }
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
rustc_attr = { path = "../rustc_attr" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }
@ -42,7 +43,7 @@ tempfile = "3.2"
thin-vec = "0.2.12"
thorin-dwp = "0.8"
tracing = "0.1"
wasm-encoder = "0.216.0"
wasm-encoder = "0.219"
# tidy-alphabetical-end
[target.'cfg(unix)'.dependencies]

View file

@ -201,6 +201,11 @@ codegen_ssa_missing_memory_ordering = Atomic intrinsic missing memory ordering
codegen_ssa_missing_query_depgraph =
found CGU-reuse attribute but `-Zquery-dep-graph` was not specified
codegen_ssa_mixed_export_name_and_no_mangle = `{$no_mangle_attr}` attribute may not be used in combination with `#[export_name]`
.label = `{$no_mangle_attr}` is ignored
.note = `#[export_name]` takes precedence
.suggestion = remove the `{$no_mangle_attr}` attribute
codegen_ssa_msvc_missing_linker = the msvc targets depend on the msvc linker but `link.exe` was not found
codegen_ssa_multiple_external_func_decl = multiple declarations of external function `{$function}` from library `{$library_name}` have different calling conventions

View file

@ -992,12 +992,12 @@ fn link_natively(
let mut output = prog.stderr.clone();
output.extend_from_slice(&prog.stdout);
let escaped_output = escape_linker_output(&output, flavor);
// FIXME: Add UI tests for this error.
let err = errors::LinkingFailed {
linker_path: &linker_path,
exit_status: prog.status,
command: &cmd,
command: cmd,
escaped_output,
verbose: sess.opts.verbose,
};
sess.dcx().emit_err(err);
// If MSVC's `link.exe` was expected but the return code
@ -2745,6 +2745,15 @@ fn add_upstream_rust_crates(
.find(|(ty, _)| *ty == crate_type)
.expect("failed to find crate type in dependency format list");
if sess.target.is_like_aix {
// Unlike ELF linkers, AIX doesn't feature `DT_SONAME` to override
// the dependency name when outputing a shared library. Thus, `ld` will
// use the full path to shared libraries as the dependency if passed it
// by default unless `noipath` is passed.
// https://www.ibm.com/docs/en/aix/7.3?topic=l-ld-command.
cmd.link_or_cc_arg("-bnoipath");
}
for &cnum in &codegen_results.crate_info.used_crates {
// We may not pass all crates through to the linker. Some crates may appear statically in
// an existing dylib, meaning we'll pick up all the symbols from the dylib.

View file

@ -1694,6 +1694,8 @@ impl<'a> Linker for AixLinker<'a> {
fn pgo_gen(&mut self) {
self.link_arg("-bdbg:namedsects:ss");
self.link_arg("-u");
self.link_arg("__llvm_profile_runtime");
}
fn control_flow_guard(&mut self) {}

View file

@ -34,7 +34,7 @@ use rustc_session::config::{
};
use rustc_span::source_map::SourceMap;
use rustc_span::symbol::sym;
use rustc_span::{BytePos, FileName, InnerSpan, Pos, Span};
use rustc_span::{FileName, InnerSpan, Span, SpanData};
use rustc_target::spec::{MergeFunctions, SanitizerSet};
use tracing::debug;
@ -1837,7 +1837,7 @@ fn spawn_work<'a, B: ExtraBackendMethods>(
enum SharedEmitterMessage {
Diagnostic(Diagnostic),
InlineAsmError(u32, String, Level, Option<(String, Vec<InnerSpan>)>),
InlineAsmError(SpanData, String, Level, Option<(String, Vec<InnerSpan>)>),
Fatal(String),
}
@ -1859,12 +1859,12 @@ impl SharedEmitter {
pub fn inline_asm_error(
&self,
cookie: u32,
span: SpanData,
msg: String,
level: Level,
source: Option<(String, Vec<InnerSpan>)>,
) {
drop(self.sender.send(SharedEmitterMessage::InlineAsmError(cookie, msg, level, source)));
drop(self.sender.send(SharedEmitterMessage::InlineAsmError(span, msg, level, source)));
}
fn fatal(&self, msg: &str) {
@ -1953,17 +1953,12 @@ impl SharedEmitterMain {
dcx.emit_diagnostic(d);
sess.dcx().abort_if_errors();
}
Ok(SharedEmitterMessage::InlineAsmError(cookie, msg, level, source)) => {
Ok(SharedEmitterMessage::InlineAsmError(span, msg, level, source)) => {
assert_matches!(level, Level::Error | Level::Warning | Level::Note);
let msg = msg.strip_prefix("error: ").unwrap_or(&msg).to_string();
let mut err = Diag::<()>::new(sess.dcx(), level, msg);
// If the cookie is 0 then we don't have span information.
if cookie != 0 {
let pos = BytePos::from_u32(cookie);
let span = Span::with_root_ctxt(pos, pos);
err.span(span);
};
if !span.is_dummy() {
err.span(span.span());
}
// Point to the generated assembly if it is available.
if let Some((buffer, spans)) = source {

View file

@ -3,11 +3,10 @@ use rustc_attr::{InlineAttr, InstructionSetAttr, OptimizeAttr, list_contains_nam
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::codes::*;
use rustc_errors::{DiagMessage, SubdiagMessage, struct_span_code_err};
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
use rustc_hir::weak_lang_items::WEAK_LANG_ITEMS;
use rustc_hir::{LangItem, lang_items};
use rustc_hir::{self as hir, HirId, LangItem, lang_items};
use rustc_middle::middle::codegen_fn_attrs::{
CodegenFnAttrFlags, CodegenFnAttrs, PatchableFunctionEntry,
};
@ -78,6 +77,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
let mut inline_span = None;
let mut link_ordinal_span = None;
let mut no_sanitize_span = None;
let mut mixed_export_name_no_mangle_lint_state = MixedExportNameAndNoMangleState::default();
for attr in attrs.iter() {
// In some cases, attribute are only valid on functions, but it's the `check_attr`
@ -116,7 +116,12 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
sym::naked => codegen_fn_attrs.flags |= CodegenFnAttrFlags::NAKED,
sym::no_mangle => {
if tcx.opt_item_name(did.to_def_id()).is_some() {
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE;
mixed_export_name_no_mangle_lint_state.track_no_mangle(
attr.span,
tcx.local_def_id_to_hir_id(did),
attr,
);
} else {
tcx.dcx()
.struct_span_err(
@ -240,6 +245,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
.emit();
}
codegen_fn_attrs.export_name = Some(s);
mixed_export_name_no_mangle_lint_state.track_export_name(attr.span);
}
}
sym::target_feature => {
@ -513,6 +519,8 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
}
}
mixed_export_name_no_mangle_lint_state.lint_if_mixed(tcx);
codegen_fn_attrs.inline = attrs.iter().fold(InlineAttr::None, |ia, attr| {
if !attr.has_name(sym::inline) {
return ia;
@ -542,6 +550,13 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
}
});
// naked function MUST NOT be inlined! This attribute is required for the rust compiler itself,
// but not for the code generation backend because at that point the naked function will just be
// a declaration, with a definition provided in global assembly.
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NAKED) {
codegen_fn_attrs.inline = InlineAttr::Never;
}
codegen_fn_attrs.optimize = attrs.iter().fold(OptimizeAttr::None, |ia, attr| {
if !attr.has_name(sym::optimize) {
return ia;
@ -626,10 +641,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
}
}
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NAKED) {
codegen_fn_attrs.inline = InlineAttr::Never;
}
// Weak lang items have the same semantics as "std internal" symbols in the
// sense that they're preserved through all our LTO passes and only
// strippable by the linker.
@ -779,6 +790,49 @@ fn check_link_name_xor_ordinal(
}
}
#[derive(Default)]
struct MixedExportNameAndNoMangleState<'a> {
export_name: Option<Span>,
hir_id: Option<HirId>,
no_mangle: Option<Span>,
no_mangle_attr: Option<&'a ast::Attribute>,
}
impl<'a> MixedExportNameAndNoMangleState<'a> {
fn track_export_name(&mut self, span: Span) {
self.export_name = Some(span);
}
fn track_no_mangle(&mut self, span: Span, hir_id: HirId, attr_name: &'a ast::Attribute) {
self.no_mangle = Some(span);
self.hir_id = Some(hir_id);
self.no_mangle_attr = Some(attr_name);
}
/// Emit diagnostics if the lint condition is met.
fn lint_if_mixed(self, tcx: TyCtxt<'_>) {
if let Self {
export_name: Some(export_name),
no_mangle: Some(no_mangle),
hir_id: Some(hir_id),
no_mangle_attr: Some(no_mangle_attr),
} = self
{
tcx.emit_node_span_lint(
lint::builtin::UNUSED_ATTRIBUTES,
hir_id,
no_mangle,
errors::MixedExportNameAndNoMangle {
no_mangle,
no_mangle_attr: rustc_ast_pretty::pprust::attribute_to_string(no_mangle_attr),
export_name,
removal_span: no_mangle,
},
);
}
}
}
pub(crate) fn provide(providers: &mut Providers) {
*providers = Providers { codegen_fn_attrs, should_inherit_track_caller, ..*providers };
}

View file

@ -1,6 +1,7 @@
//! Errors emitted by codegen_ssa
use std::borrow::Cow;
use std::ffi::OsString;
use std::io::Error;
use std::num::ParseIntError;
use std::path::{Path, PathBuf};
@ -10,7 +11,7 @@ use rustc_errors::codes::*;
use rustc_errors::{
Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, IntoDiagArg, Level,
};
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
use rustc_middle::ty::Ty;
use rustc_middle::ty::layout::LayoutError;
use rustc_span::{Span, Symbol};
@ -345,21 +346,82 @@ impl<G: EmissionGuarantee> Diagnostic<'_, G> for ThorinErrorWrapper {
}
pub(crate) struct LinkingFailed<'a> {
pub linker_path: &'a PathBuf,
pub linker_path: &'a Path,
pub exit_status: ExitStatus,
pub command: &'a Command,
pub command: Command,
pub escaped_output: String,
pub verbose: bool,
}
impl<G: EmissionGuarantee> Diagnostic<'_, G> for LinkingFailed<'_> {
fn into_diag(self, dcx: DiagCtxtHandle<'_>, level: Level) -> Diag<'_, G> {
fn into_diag(mut self, dcx: DiagCtxtHandle<'_>, level: Level) -> Diag<'_, G> {
let mut diag = Diag::new(dcx, level, fluent::codegen_ssa_linking_failed);
diag.arg("linker_path", format!("{}", self.linker_path.display()));
diag.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);
if self.verbose {
diag.note(format!("{:?}", self.command));
} else {
enum ArgGroup {
Regular(OsString),
Objects(usize),
Rlibs(PathBuf, Vec<OsString>),
}
// Omit rust object files and fold rlibs in the error by default to make linker errors a
// bit less verbose.
let orig_args = self.command.take_args();
let mut args: Vec<ArgGroup> = vec![];
for arg in orig_args {
if arg.as_encoded_bytes().ends_with(b".rcgu.o") {
if let Some(ArgGroup::Objects(n)) = args.last_mut() {
*n += 1;
} else {
args.push(ArgGroup::Objects(1));
}
} else if arg.as_encoded_bytes().ends_with(b".rlib") {
let rlib_path = Path::new(&arg);
let dir = rlib_path.parent().unwrap();
let filename = rlib_path.file_name().unwrap().to_owned();
if let Some(ArgGroup::Rlibs(parent, rlibs)) = args.last_mut() {
if parent == dir {
rlibs.push(filename);
} else {
args.push(ArgGroup::Rlibs(dir.to_owned(), vec![filename]));
}
} else {
args.push(ArgGroup::Rlibs(dir.to_owned(), vec![filename]));
}
} else {
args.push(ArgGroup::Regular(arg));
}
}
self.command.args(args.into_iter().map(|arg_group| match arg_group {
ArgGroup::Regular(arg) => arg,
ArgGroup::Objects(n) => OsString::from(format!("<{n} object files omitted>")),
ArgGroup::Rlibs(dir, rlibs) => {
let mut arg = dir.into_os_string();
arg.push("/{");
let mut first = true;
for rlib in rlibs {
if !first {
arg.push(",");
}
first = false;
arg.push(rlib);
}
arg.push("}");
arg
}
}));
diag.note(format!("{:?}", self.command));
diag.note("some arguments are omitted. use `--verbose` to show all linker arguments");
}
diag.note(self.escaped_output);
// Trying to match an error from OS linkers
// which by now we have no way to translate.
@ -1114,3 +1176,15 @@ impl<G: EmissionGuarantee> Diagnostic<'_, G> for TargetFeatureDisableOrEnable<'_
#[derive(Diagnostic)]
#[diag(codegen_ssa_aix_strip_not_used)]
pub(crate) struct AixStripNotUsed;
#[derive(LintDiagnostic)]
#[diag(codegen_ssa_mixed_export_name_and_no_mangle)]
pub(crate) struct MixedExportNameAndNoMangle {
#[label]
pub no_mangle: Span,
pub no_mangle_attr: String,
#[note]
pub export_name: Span,
#[suggestion(style = "verbose", code = "", applicability = "machine-applicable")]
pub removal_span: Span,
}

View file

@ -20,6 +20,7 @@ mod coverageinfo;
pub mod debuginfo;
mod intrinsic;
mod locals;
mod naked_asm;
pub mod operand;
pub mod place;
mod rvalue;
@ -176,6 +177,11 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
let fn_abi = cx.fn_abi_of_instance(instance, ty::List::empty());
debug!("fn_abi: {:?}", fn_abi);
if cx.tcx().codegen_fn_attrs(instance.def_id()).flags.contains(CodegenFnAttrFlags::NAKED) {
crate::mir::naked_asm::codegen_naked_asm::<Bx>(cx, &mir, instance);
return;
}
let debug_context = cx.create_function_debug_context(instance, fn_abi, llfn, mir);
let start_llbb = Bx::append_block(cx, llfn, "start");

View file

@ -0,0 +1,266 @@
use rustc_attr::InstructionSetAttr;
use rustc_middle::mir::mono::{Linkage, MonoItem, MonoItemData, Visibility};
use rustc_middle::mir::{Body, InlineAsmOperand};
use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutOf};
use rustc_middle::ty::{Instance, TyCtxt};
use rustc_middle::{bug, ty};
use rustc_span::sym;
use crate::common;
use crate::traits::{AsmCodegenMethods, BuilderMethods, GlobalAsmOperandRef, MiscCodegenMethods};
pub(crate) fn codegen_naked_asm<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
cx: &'a Bx::CodegenCx,
mir: &Body<'tcx>,
instance: Instance<'tcx>,
) {
let rustc_middle::mir::TerminatorKind::InlineAsm {
asm_macro: _,
template,
ref operands,
options,
line_spans,
targets: _,
unwind: _,
} = mir.basic_blocks.iter().next().unwrap().terminator().kind
else {
bug!("#[naked] functions should always terminate with an asm! block")
};
let operands: Vec<_> =
operands.iter().map(|op| inline_to_global_operand::<Bx>(cx, instance, op)).collect();
let item_data = cx.codegen_unit().items().get(&MonoItem::Fn(instance)).unwrap();
let name = cx.mangled_name(instance);
let (begin, end) = prefix_and_suffix(cx.tcx(), instance, &name, item_data);
let mut template_vec = Vec::new();
template_vec.push(rustc_ast::ast::InlineAsmTemplatePiece::String(begin.into()));
template_vec.extend(template.iter().cloned());
template_vec.push(rustc_ast::ast::InlineAsmTemplatePiece::String(end.into()));
cx.codegen_global_asm(&template_vec, &operands, options, line_spans);
}
fn inline_to_global_operand<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
cx: &'a Bx::CodegenCx,
instance: Instance<'tcx>,
op: &InlineAsmOperand<'tcx>,
) -> GlobalAsmOperandRef<'tcx> {
match op {
InlineAsmOperand::Const { value } => {
let const_value = instance
.instantiate_mir_and_normalize_erasing_regions(
cx.tcx(),
cx.typing_env(),
ty::EarlyBinder::bind(value.const_),
)
.eval(cx.tcx(), cx.typing_env(), value.span)
.expect("erroneous constant missed by mono item collection");
let mono_type = instance.instantiate_mir_and_normalize_erasing_regions(
cx.tcx(),
cx.typing_env(),
ty::EarlyBinder::bind(value.ty()),
);
let string = common::asm_const_to_str(
cx.tcx(),
value.span,
const_value,
cx.layout_of(mono_type),
);
GlobalAsmOperandRef::Const { string }
}
InlineAsmOperand::SymFn { value } => {
let mono_type = instance.instantiate_mir_and_normalize_erasing_regions(
cx.tcx(),
cx.typing_env(),
ty::EarlyBinder::bind(value.ty()),
);
let instance = match mono_type.kind() {
&ty::FnDef(def_id, args) => Instance::new(def_id, args),
_ => bug!("asm sym is not a function"),
};
GlobalAsmOperandRef::SymFn { instance }
}
InlineAsmOperand::SymStatic { def_id } => {
GlobalAsmOperandRef::SymStatic { def_id: *def_id }
}
InlineAsmOperand::In { .. }
| InlineAsmOperand::Out { .. }
| InlineAsmOperand::InOut { .. }
| InlineAsmOperand::Label { .. } => {
bug!("invalid operand type for naked_asm!")
}
}
}
enum AsmBinaryFormat {
Elf,
Macho,
Coff,
}
impl AsmBinaryFormat {
fn from_target(target: &rustc_target::spec::Target) -> Self {
if target.is_like_windows {
Self::Coff
} else if target.is_like_osx {
Self::Macho
} else {
Self::Elf
}
}
}
fn prefix_and_suffix<'tcx>(
tcx: TyCtxt<'tcx>,
instance: Instance<'tcx>,
asm_name: &str,
item_data: &MonoItemData,
) -> (String, String) {
use std::fmt::Write;
let asm_binary_format = AsmBinaryFormat::from_target(&tcx.sess.target);
let is_arm = tcx.sess.target.arch == "arm";
let is_thumb = tcx.sess.unstable_target_features.contains(&sym::thumb_mode);
let attrs = tcx.codegen_fn_attrs(instance.def_id());
let link_section = attrs.link_section.map(|symbol| symbol.as_str().to_string());
let align = attrs.alignment.map(|a| a.bytes()).unwrap_or(4);
// See https://sourceware.org/binutils/docs/as/ARM-Directives.html for info on these directives.
// In particular, `.arm` can also be written `.code 32` and `.thumb` as `.code 16`.
let (arch_prefix, arch_suffix) = if is_arm {
(
match attrs.instruction_set {
None => match is_thumb {
true => ".thumb\n.thumb_func",
false => ".arm",
},
Some(InstructionSetAttr::ArmT32) => ".thumb\n.thumb_func",
Some(InstructionSetAttr::ArmA32) => ".arm",
},
match is_thumb {
true => ".thumb",
false => ".arm",
},
)
} else {
("", "")
};
let emit_fatal = |msg| tcx.dcx().span_fatal(tcx.def_span(instance.def_id()), msg);
// see https://godbolt.org/z/cPK4sxKor.
let write_linkage = |w: &mut String| -> std::fmt::Result {
match item_data.linkage {
Linkage::External => {
writeln!(w, ".globl {asm_name}")?;
}
Linkage::LinkOnceAny | Linkage::LinkOnceODR | Linkage::WeakAny | Linkage::WeakODR => {
match asm_binary_format {
AsmBinaryFormat::Elf | AsmBinaryFormat::Coff => {
writeln!(w, ".weak {asm_name}")?;
}
AsmBinaryFormat::Macho => {
writeln!(w, ".globl {asm_name}")?;
writeln!(w, ".weak_definition {asm_name}")?;
}
}
}
Linkage::Internal | Linkage::Private => {
// write nothing
}
Linkage::Appending => emit_fatal("Only global variables can have appending linkage!"),
Linkage::Common => emit_fatal("Functions may not have common linkage"),
Linkage::AvailableExternally => {
// this would make the function equal an extern definition
emit_fatal("Functions may not have available_externally linkage")
}
Linkage::ExternalWeak => {
// FIXME: actually this causes a SIGILL in LLVM
emit_fatal("Functions may not have external weak linkage")
}
}
Ok(())
};
let mut begin = String::new();
let mut end = String::new();
match asm_binary_format {
AsmBinaryFormat::Elf => {
let section = link_section.unwrap_or(format!(".text.{asm_name}"));
let progbits = match is_arm {
true => "%progbits",
false => "@progbits",
};
let function = match is_arm {
true => "%function",
false => "@function",
};
writeln!(begin, ".pushsection {section},\"ax\", {progbits}").unwrap();
writeln!(begin, ".balign {align}").unwrap();
write_linkage(&mut begin).unwrap();
if let Visibility::Hidden = item_data.visibility {
writeln!(begin, ".hidden {asm_name}").unwrap();
}
writeln!(begin, ".type {asm_name}, {function}").unwrap();
if !arch_prefix.is_empty() {
writeln!(begin, "{}", arch_prefix).unwrap();
}
writeln!(begin, "{asm_name}:").unwrap();
writeln!(end).unwrap();
writeln!(end, ".size {asm_name}, . - {asm_name}").unwrap();
writeln!(end, ".popsection").unwrap();
if !arch_suffix.is_empty() {
writeln!(end, "{}", arch_suffix).unwrap();
}
}
AsmBinaryFormat::Macho => {
let section = link_section.unwrap_or("__TEXT,__text".to_string());
writeln!(begin, ".pushsection {},regular,pure_instructions", section).unwrap();
writeln!(begin, ".balign {align}").unwrap();
write_linkage(&mut begin).unwrap();
if let Visibility::Hidden = item_data.visibility {
writeln!(begin, ".private_extern {asm_name}").unwrap();
}
writeln!(begin, "{asm_name}:").unwrap();
writeln!(end).unwrap();
writeln!(end, ".popsection").unwrap();
if !arch_suffix.is_empty() {
writeln!(end, "{}", arch_suffix).unwrap();
}
}
AsmBinaryFormat::Coff => {
let section = link_section.unwrap_or(format!(".text.{asm_name}"));
writeln!(begin, ".pushsection {},\"xr\"", section).unwrap();
writeln!(begin, ".balign {align}").unwrap();
write_linkage(&mut begin).unwrap();
writeln!(begin, ".def {asm_name}").unwrap();
writeln!(begin, ".scl 2").unwrap();
writeln!(begin, ".type 32").unwrap();
writeln!(begin, ".endef {asm_name}").unwrap();
writeln!(begin, "{asm_name}:").unwrap();
writeln!(end).unwrap();
writeln!(end, ".popsection").unwrap();
if !arch_suffix.is_empty() {
writeln!(end, "{}", arch_suffix).unwrap();
}
}
}
(begin, end)
}

View file

@ -1,4 +1,5 @@
use rustc_hir as hir;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::mir::interpret::ErrorHandled;
use rustc_middle::mir::mono::{Linkage, MonoItem, Visibility};
use rustc_middle::ty::Instance;
@ -135,7 +136,13 @@ impl<'a, 'tcx: 'a> MonoItemExt<'a, 'tcx> for MonoItem<'tcx> {
cx.predefine_static(def_id, linkage, visibility, symbol_name);
}
MonoItem::Fn(instance) => {
cx.predefine_fn(instance, linkage, visibility, symbol_name);
let attrs = cx.tcx().codegen_fn_attrs(instance.def_id());
if attrs.flags.contains(CodegenFnAttrFlags::NAKED) {
// do not define this function; it will become a global assembly block
} else {
cx.predefine_fn(instance, linkage, visibility, symbol_name);
};
}
MonoItem::GlobalAsm(..) => {}
}

View file

@ -68,4 +68,11 @@ pub trait AsmCodegenMethods<'tcx> {
options: InlineAsmOptions,
line_spans: &[Span],
);
/// The mangled name of this instance
///
/// Additional mangling is used on
/// some targets to add a leading underscore (Mach-O)
/// or byte count suffixes (x86 Windows).
fn mangled_name(&self, instance: Instance<'tcx>) -> String;
}

View file

@ -170,7 +170,7 @@ where
let reported = if allowed_in_infallible {
ReportedErrorInfo::allowed_in_infallible(g)
} else {
ReportedErrorInfo::from(g)
ReportedErrorInfo::const_eval_error(g)
};
ErrorHandled::Reported(reported, span)
}

View file

@ -3,13 +3,13 @@ use std::sync::atomic::Ordering::Relaxed;
use either::{Left, Right};
use rustc_abi::{self as abi, BackendRepr};
use rustc_hir::def::DefKind;
use rustc_middle::bug;
use rustc_middle::mir::interpret::{AllocId, ErrorHandled, InterpErrorInfo};
use rustc_middle::mir::interpret::{AllocId, ErrorHandled, InterpErrorInfo, ReportedErrorInfo};
use rustc_middle::mir::{self, ConstAlloc, ConstValue};
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::layout::{HasTypingEnv, LayoutOf};
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::{bug, throw_inval};
use rustc_span::def_id::LocalDefId;
use rustc_span::{DUMMY_SP, Span};
use tracing::{debug, instrument, trace};
@ -93,18 +93,18 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
match intern_result {
Ok(()) => {}
Err(InternResult::FoundDanglingPointer) => {
return Err(ecx
.tcx
.dcx()
.emit_err(errors::DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind }))
.into();
throw_inval!(AlreadyReported(ReportedErrorInfo::non_const_eval_error(
ecx.tcx
.dcx()
.emit_err(errors::DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind }),
)));
}
Err(InternResult::FoundBadMutablePointer) => {
return Err(ecx
.tcx
.dcx()
.emit_err(errors::MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind }))
.into();
throw_inval!(AlreadyReported(ReportedErrorInfo::non_const_eval_error(
ecx.tcx
.dcx()
.emit_err(errors::MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind }),
)));
}
}

View file

@ -8,6 +8,7 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexMap, IndexEntry};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{self as hir, CRATE_HIR_ID, LangItem};
use rustc_middle::mir::AssertMessage;
use rustc_middle::mir::interpret::ReportedErrorInfo;
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::layout::{HasTypingEnv, TyAndLayout};
use rustc_middle::ty::{self, Ty, TyCtxt};
@ -563,7 +564,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
.tcx
.dcx()
.span_delayed_bug(span, "The deny lint should have already errored");
throw_inval!(AlreadyReported(guard.into()));
throw_inval!(AlreadyReported(ReportedErrorInfo::allowed_in_infallible(guard)));
}
} else if new_steps > start && new_steps.is_power_of_two() {
// Only report after a certain number of terminators have been evaluated and the

View file

@ -1,6 +1,6 @@
use rustc_abi::{BackendRepr, VariantIdx};
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId};
use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId, ReportedErrorInfo};
use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout};
use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt};
use rustc_middle::{bug, mir};
@ -261,7 +261,7 @@ pub(crate) fn eval_to_valtree<'tcx>(
ValTreeCreationError::NodesOverflow => {
let handled =
tcx.dcx().emit_err(MaxNumNodesInConstErr { span, global_const_id });
Err(handled.into())
Err(ReportedErrorInfo::allowed_in_infallible(handled).into())
}
ValTreeCreationError::NonSupportedType(ty) => Ok(Err(ty)),
}

View file

@ -268,7 +268,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
};
// do not continue if typeck errors occurred (can only occur in local crate)
if let Some(err) = body.tainted_by_errors {
throw_inval!(AlreadyReported(ReportedErrorInfo::from(err)));
throw_inval!(AlreadyReported(ReportedErrorInfo::non_const_eval_error(err)));
}
interp_ok(body)
}
@ -317,7 +317,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
Ok(None) => throw_inval!(TooGeneric),
// FIXME(eddyb) this could be a bit more specific than `AlreadyReported`.
Err(error_reported) => throw_inval!(AlreadyReported(error_reported.into())),
Err(error_guaranteed) => throw_inval!(AlreadyReported(
ReportedErrorInfo::non_const_eval_error(error_guaranteed)
)),
}
}

View file

@ -5,8 +5,7 @@
use std::assert_matches::assert_matches;
use either::{Either, Left, Right};
use rustc_abi::{Align, BackendRepr, HasDataLayout, Size};
use rustc_ast::Mutability;
use rustc_abi::{BackendRepr, HasDataLayout, Size};
use rustc_middle::ty::Ty;
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
use rustc_middle::{bug, mir, span_bug};
@ -1018,40 +1017,31 @@ where
self.allocate_dyn(layout, kind, MemPlaceMeta::None)
}
/// Allocates a sequence of bytes in the interpreter's memory.
/// For immutable allocations, uses deduplication to reuse existing memory.
/// For mutable allocations, creates a new unique allocation.
pub fn allocate_bytes(
/// Allocates a sequence of bytes in the interpreter's memory with alignment 1.
/// This is allocated in immutable global memory and deduplicated.
pub fn allocate_bytes_dedup(
&mut self,
bytes: &[u8],
align: Align,
kind: MemoryKind<M::MemoryKind>,
mutbl: Mutability,
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
// Use cache for immutable strings.
if mutbl.is_not() {
// Use dedup'd allocation function.
let salt = M::get_global_alloc_salt(self, None);
let id = self.tcx.allocate_bytes_dedup(bytes, salt);
let salt = M::get_global_alloc_salt(self, None);
let id = self.tcx.allocate_bytes_dedup(bytes, salt);
// Turn untagged "global" pointers (obtained via `tcx`) into the machine pointer to the allocation.
M::adjust_alloc_root_pointer(&self, Pointer::from(id), Some(kind))
} else {
// Allocate new memory for mutable data.
self.allocate_bytes_ptr(bytes, align, kind, mutbl)
}
// Turn untagged "global" pointers (obtained via `tcx`) into the machine pointer to the allocation.
M::adjust_alloc_root_pointer(
&self,
Pointer::from(id),
M::GLOBAL_KIND.map(MemoryKind::Machine),
)
}
/// Allocates a string in the interpreter's memory with metadata for length.
/// Uses `allocate_bytes` internally but adds string-specific metadata handling.
pub fn allocate_str(
/// Allocates a string in the interpreter's memory, returning it as a (wide) place.
/// This is allocated in immutable global memory and deduplicated.
pub fn allocate_str_dedup(
&mut self,
str: &str,
kind: MemoryKind<M::MemoryKind>,
mutbl: Mutability,
) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> {
let bytes = str.as_bytes();
let ptr = self.allocate_bytes(bytes, Align::ONE, kind, mutbl)?;
let ptr = self.allocate_bytes_dedup(bytes)?;
// Create length metadata for the string.
let meta = Scalar::from_target_usize(u64::try_from(bytes.len()).unwrap(), self);

View file

@ -1,7 +1,7 @@
use rustc_hir::LangItem;
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::{self, Mutability};
use rustc_middle::ty::{self};
use rustc_middle::{bug, mir};
use rustc_span::symbol::Symbol;
use tracing::trace;
@ -20,12 +20,9 @@ fn alloc_caller_location<'tcx>(
// This can fail if rustc runs out of memory right here. Trying to emit an error would be
// pointless, since that would require allocating more memory than these short strings.
let file = if loc_details.file {
ecx.allocate_str(filename.as_str(), MemoryKind::CallerLocation, Mutability::Not).unwrap()
ecx.allocate_str_dedup(filename.as_str()).unwrap()
} else {
// FIXME: This creates a new allocation each time. It might be preferable to
// perform this allocation only once, and re-use the `MPlaceTy`.
// See https://github.com/rust-lang/rust/pull/89920#discussion_r730012398
ecx.allocate_str("<redacted>", MemoryKind::CallerLocation, Mutability::Not).unwrap()
ecx.allocate_str_dedup("<redacted>").unwrap()
};
let file = file.map_provenance(CtfeProvenance::as_immutable);
let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) };

View file

@ -1291,7 +1291,7 @@ impl<'a> DiagCtxtHandle<'a> {
Diag::<ErrorGuaranteed>::new(self, DelayedBug, msg.into()).emit()
}
/// Ensures that an error is printed. See `Level::DelayedBug`.
/// Ensures that an error is printed. See [`Level::DelayedBug`].
///
/// Note: this function used to be called `delay_span_bug`. It was renamed
/// to match similar functions like `span_err`, `span_warn`, etc.

View file

@ -174,6 +174,7 @@ pub(crate) fn placeholder(
vis,
is_placeholder: true,
safety: Safety::Default,
default: None,
}]),
AstFragmentKind::Variants => AstFragment::Variants(smallvec![ast::Variant {
attrs: Default::default(),

View file

@ -455,6 +455,9 @@ declare_features! (
(unstable, custom_test_frameworks, "1.30.0", Some(50297)),
/// Allows declarative macros 2.0 (`macro`).
(unstable, decl_macro, "1.17.0", Some(39412)),
/// Allows the use of default values on struct definitions and the construction of struct
/// literals with the functional update syntax without a base.
(unstable, default_field_values, "CURRENT_RUSTC_VERSION", Some(132162)),
/// Allows using `#[deprecated_safe]` to deprecate the safeness of a function or trait
(unstable, deprecated_safe, "1.61.0", Some(94978)),
/// Allows having using `suggestion` in the `#[deprecated]` attribute.

View file

@ -9,7 +9,6 @@ use rustc_macros::{Decodable, Encodable, HashStable_Generic};
use rustc_span::Symbol;
use rustc_span::def_id::{DefId, LocalDefId};
use rustc_span::hygiene::MacroKind;
use rustc_span::symbol::kw;
use crate::definitions::DefPathData;
use crate::hir;
@ -256,7 +255,6 @@ impl DefKind {
pub fn def_path_data(self, name: Symbol) -> DefPathData {
match self {
DefKind::Struct | DefKind::Union if name == kw::Empty => DefPathData::AnonAdt,
DefKind::Mod
| DefKind::Struct
| DefKind::Union

View file

@ -289,8 +289,6 @@ pub enum DefPathData {
/// An existential `impl Trait` type node.
/// Argument position `impl Trait` have a `TypeNs` with their pretty-printed name.
OpaqueTy,
/// An anonymous struct or union type i.e. `struct { foo: Type }` or `union { bar: Type }`
AnonAdt,
}
impl Definitions {
@ -415,7 +413,7 @@ impl DefPathData {
TypeNs(name) | ValueNs(name) | MacroNs(name) | LifetimeNs(name) => Some(name),
Impl | ForeignMod | CrateRoot | Use | GlobalAsm | Closure | Ctor | AnonConst
| OpaqueTy | AnonAdt => None,
| OpaqueTy => None,
}
}
@ -438,7 +436,6 @@ impl DefPathData {
Ctor => DefPathDataName::Anon { namespace: sym::constructor },
AnonConst => DefPathDataName::Anon { namespace: sym::constant },
OpaqueTy => DefPathDataName::Anon { namespace: sym::opaque },
AnonAdt => DefPathDataName::Anon { namespace: sym::anon_adt },
}
}
}

View file

@ -1857,7 +1857,12 @@ impl Expr<'_> {
base.can_have_side_effects()
}
ExprKind::Struct(_, fields, init) => {
fields.iter().map(|field| field.expr).chain(init).any(|e| e.can_have_side_effects())
let init_side_effects = match init {
StructTailExpr::Base(init) => init.can_have_side_effects(),
StructTailExpr::DefaultFields(_) | StructTailExpr::None => false,
};
fields.iter().map(|field| field.expr).any(|e| e.can_have_side_effects())
|| init_side_effects
}
ExprKind::Array(args)
@ -1926,20 +1931,52 @@ impl Expr<'_> {
ExprKind::Path(QPath::Resolved(None, path2)),
) => path1.res == path2.res,
(
ExprKind::Struct(QPath::LangItem(LangItem::RangeTo, _), [val1], None),
ExprKind::Struct(QPath::LangItem(LangItem::RangeTo, _), [val2], None),
ExprKind::Struct(
QPath::LangItem(LangItem::RangeTo, _),
[val1],
StructTailExpr::None,
),
ExprKind::Struct(
QPath::LangItem(LangItem::RangeTo, _),
[val2],
StructTailExpr::None,
),
)
| (
ExprKind::Struct(QPath::LangItem(LangItem::RangeToInclusive, _), [val1], None),
ExprKind::Struct(QPath::LangItem(LangItem::RangeToInclusive, _), [val2], None),
ExprKind::Struct(
QPath::LangItem(LangItem::RangeToInclusive, _),
[val1],
StructTailExpr::None,
),
ExprKind::Struct(
QPath::LangItem(LangItem::RangeToInclusive, _),
[val2],
StructTailExpr::None,
),
)
| (
ExprKind::Struct(QPath::LangItem(LangItem::RangeFrom, _), [val1], None),
ExprKind::Struct(QPath::LangItem(LangItem::RangeFrom, _), [val2], None),
ExprKind::Struct(
QPath::LangItem(LangItem::RangeFrom, _),
[val1],
StructTailExpr::None,
),
ExprKind::Struct(
QPath::LangItem(LangItem::RangeFrom, _),
[val2],
StructTailExpr::None,
),
) => val1.expr.equivalent_for_indexing(val2.expr),
(
ExprKind::Struct(QPath::LangItem(LangItem::Range, _), [val1, val3], None),
ExprKind::Struct(QPath::LangItem(LangItem::Range, _), [val2, val4], None),
ExprKind::Struct(
QPath::LangItem(LangItem::Range, _),
[val1, val3],
StructTailExpr::None,
),
ExprKind::Struct(
QPath::LangItem(LangItem::Range, _),
[val2, val4],
StructTailExpr::None,
),
) => {
val1.expr.equivalent_for_indexing(val2.expr)
&& val3.expr.equivalent_for_indexing(val4.expr)
@ -2096,7 +2133,7 @@ pub enum ExprKind<'hir> {
///
/// E.g., `Foo {x: 1, y: 2}`, or `Foo {x: 1, .. base}`,
/// where `base` is the `Option<Expr>`.
Struct(&'hir QPath<'hir>, &'hir [ExprField<'hir>], Option<&'hir Expr<'hir>>),
Struct(&'hir QPath<'hir>, &'hir [ExprField<'hir>], StructTailExpr<'hir>),
/// An array literal constructed from one repeated element.
///
@ -2111,6 +2148,19 @@ pub enum ExprKind<'hir> {
Err(rustc_span::ErrorGuaranteed),
}
#[derive(Debug, Clone, Copy, HashStable_Generic)]
pub enum StructTailExpr<'hir> {
/// A struct expression where all the fields are explicitly enumerated: `Foo { a, b }`.
None,
/// A struct expression with a "base", an expression of the same type as the outer struct that
/// will be used to populate any fields not explicitly mentioned: `Foo { ..base }`
Base(&'hir Expr<'hir>),
/// A struct expression with a `..` tail but no "base" expression. The values from the struct
/// fields' default values will be used to populate any fields not explicitly mentioned:
/// `Foo { .. }`.
DefaultFields(Span),
}
/// Represents an optionally `Self`-qualified value/type path or associated extension.
///
/// To resolve the path to a `DefId`, call [`qpath_res`].
@ -2832,8 +2882,6 @@ pub enum TyKind<'hir> {
Never,
/// A tuple (`(A, B, C, D, ...)`).
Tup(&'hir [Ty<'hir>]),
/// An anonymous struct or union type i.e. `struct { foo: Type }` or `union { foo: Type }`
AnonAdt(ItemId),
/// A path to a type definition (`module::module::...::Type`), or an
/// associated type (e.g., `<Vec<T> as Trait>::Type` or `<T>::Target`).
///
@ -3172,6 +3220,7 @@ pub struct FieldDef<'hir> {
pub def_id: LocalDefId,
pub ty: &'hir Ty<'hir>,
pub safety: Safety,
pub default: Option<&'hir AnonConst>,
}
impl FieldDef<'_> {

View file

@ -748,7 +748,10 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>)
ExprKind::Struct(ref qpath, fields, ref optional_base) => {
try_visit!(visitor.visit_qpath(qpath, expression.hir_id, expression.span));
walk_list!(visitor, visit_expr_field, fields);
visit_opt!(visitor, visit_expr, optional_base);
match optional_base {
StructTailExpr::Base(base) => try_visit!(visitor.visit_expr(base)),
StructTailExpr::None | StructTailExpr::DefaultFields(_) => {}
}
}
ExprKind::Tup(subexpressions) => {
walk_list!(visitor, visit_expr, subexpressions);
@ -901,9 +904,6 @@ pub fn walk_ty<'v, V: Visitor<'v>>(visitor: &mut V, typ: &'v Ty<'v>) -> V::Resul
}
TyKind::Typeof(ref expression) => try_visit!(visitor.visit_anon_const(expression)),
TyKind::Infer | TyKind::InferDelegation(..) | TyKind::Err(_) => {}
TyKind::AnonAdt(item_id) => {
try_visit!(visitor.visit_nested_item(item_id));
}
TyKind::Pat(ty, pat) => {
try_visit!(visitor.visit_ty(ty));
try_visit!(visitor.visit_pattern_type_pattern(pat));
@ -1190,10 +1190,14 @@ pub fn walk_struct_def<'v, V: Visitor<'v>>(
V::Result::output()
}
pub fn walk_field_def<'v, V: Visitor<'v>>(visitor: &mut V, field: &'v FieldDef<'v>) -> V::Result {
try_visit!(visitor.visit_id(field.hir_id));
try_visit!(visitor.visit_ident(field.ident));
visitor.visit_ty(field.ty)
pub fn walk_field_def<'v, V: Visitor<'v>>(
visitor: &mut V,
FieldDef { hir_id, ident, ty, default, span: _, vis_span: _, def_id: _, safety: _ }: &'v FieldDef<'v>,
) -> V::Result {
try_visit!(visitor.visit_id(*hir_id));
try_visit!(visitor.visit_ident(*ident));
visit_opt!(visitor, visit_anon_const, default);
visitor.visit_ty(*ty)
}
pub fn walk_enum_def<'v, V: Visitor<'v>>(

View file

@ -96,12 +96,14 @@ hir_analysis_coercion_between_struct_same_note = expected coercion between the s
hir_analysis_coercion_between_struct_single_note = expected a single field to be coerced, none found
hir_analysis_const_bound_for_non_const_trait =
`{$modifier}` can only be applied to `#[const_trait]` traits
hir_analysis_const_bound_for_non_const_trait = `{$modifier}` can only be applied to `#[const_trait]` traits
.label = can't be applied to `{$trait_name}`
.note = `{$trait_name}` can't be used with `{$modifier}` because it isn't annotated with `#[const_trait]`
.suggestion = {$suggestion_pre}mark `{$trait_name}` as `#[const_trait]` to allow it to have `const` implementations
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
hir_analysis_const_impl_for_non_const_trait = const `impl` for trait `{$trait_name}` which is not marked with `#[const_trait]`
.label = this trait is not `const`
.suggestion = {$suggestion_pre}mark `{$trait_name}` as `#[const_trait]` to allow it to have `const` implementations
.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

View file

@ -1104,6 +1104,25 @@ fn check_type_defn<'tcx>(
for variant in variants.iter() {
// All field types must be well-formed.
for field in &variant.fields {
if let Some(def_id) = field.value
&& let Some(_ty) = tcx.type_of(def_id).no_bound_vars()
{
// FIXME(generic_const_exprs, default_field_values): this is a hack and needs to
// be refactored to check the instantiate-ability of the code better.
if let Some(def_id) = def_id.as_local()
&& let hir::Node::AnonConst(anon) = tcx.hir_node_by_def_id(def_id)
&& let expr = &tcx.hir().body(anon.body).value
&& let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
&& let Res::Def(DefKind::ConstParam, _def_id) = path.res
{
// Do not evaluate bare `const` params, as those would ICE and are only
// usable if `#![feature(generic_const_exprs)]` is enabled.
} else {
// Evaluate the constant proactively, to emit an error if the constant has
// an unconditional error. We only do so if the const has no type params.
let _ = tcx.const_eval_poly(def_id.into());
}
}
let field_id = field.did.expect_local();
let hir::FieldDef { ty: hir_ty, .. } =
tcx.hir_node_by_def_id(field_id).expect_field();

View file

@ -103,7 +103,7 @@ fn visit_implementation_of_copy(checker: &Checker<'_>) -> Result<(), ErrorGuaran
}
let cause = traits::ObligationCause::misc(DUMMY_SP, impl_did);
match type_allowed_to_implement_copy(tcx, param_env, self_type, cause) {
match type_allowed_to_implement_copy(tcx, param_env, self_type, cause, impl_header.safety) {
Ok(()) => Ok(()),
Err(CopyImplementationError::InfringingFields(fields)) => {
let span = tcx.hir().expect_item(impl_did).expect_impl().self_ty.span;
@ -123,6 +123,12 @@ fn visit_implementation_of_copy(checker: &Checker<'_>) -> Result<(), ErrorGuaran
let span = tcx.hir().expect_item(impl_did).expect_impl().self_ty.span;
Err(tcx.dcx().emit_err(errors::CopyImplOnTypeWithDtor { span }))
}
Err(CopyImplementationError::HasUnsafeFields) => {
let span = tcx.hir().expect_item(impl_did).expect_impl().self_ty.span;
Err(tcx
.dcx()
.span_delayed_bug(span, format!("cannot implement `Copy` for `{}`", self_type)))
}
}
}

View file

@ -3,7 +3,7 @@
use rustc_errors::codes::*;
use rustc_errors::struct_span_code_err;
use rustc_hir::Safety;
use rustc_hir::{LangItem, Safety};
use rustc_middle::ty::ImplPolarity::*;
use rustc_middle::ty::print::PrintTraitRefExt as _;
use rustc_middle::ty::{ImplTraitHeader, TraitDef, TyCtxt};
@ -20,7 +20,19 @@ pub(super) fn check_item(
tcx.generics_of(def_id).own_params.iter().find(|p| p.pure_wrt_drop).map(|_| "may_dangle");
let trait_ref = trait_header.trait_ref.instantiate_identity();
match (trait_def.safety, unsafe_attr, trait_header.safety, trait_header.polarity) {
let is_copy = tcx.is_lang_item(trait_def.def_id, LangItem::Copy);
let trait_def_safety = if is_copy {
// If `Self` has unsafe fields, `Copy` is unsafe to implement.
if trait_header.trait_ref.skip_binder().self_ty().has_unsafe_fields() {
rustc_hir::Safety::Unsafe
} else {
rustc_hir::Safety::Safe
}
} else {
trait_def.safety
};
match (trait_def_safety, unsafe_attr, trait_header.safety, trait_header.polarity) {
(Safety::Safe, None, Safety::Unsafe, Positive | Reservation) => {
let span = tcx.def_span(def_id);
return Err(struct_span_code_err!(
@ -48,12 +60,22 @@ pub(super) fn check_item(
"the trait `{}` requires an `unsafe impl` declaration",
trait_ref.print_trait_sugared()
)
.with_note(format!(
"the trait `{}` enforces invariants that the compiler can't check. \
Review the trait documentation and make sure this implementation \
upholds those invariants before adding the `unsafe` keyword",
trait_ref.print_trait_sugared()
))
.with_note(if is_copy {
format!(
"the trait `{}` cannot be safely implemented for `{}` \
because it has unsafe fields. Review the invariants \
of those fields before adding an `unsafe impl`",
trait_ref.print_trait_sugared(),
trait_ref.self_ty(),
)
} else {
format!(
"the trait `{}` enforces invariants that the compiler can't check. \
Review the trait documentation and make sure this implementation \
upholds those invariants before adding the `unsafe` keyword",
trait_ref.print_trait_sugared()
)
})
.with_span_suggestion_verbose(
span.shrink_to_lo(),
"add `unsafe` to this trait implementation",

View file

@ -1021,12 +1021,12 @@ impl<'tcx> FieldUniquenessCheckContext<'tcx> {
}
}
fn lower_variant(
tcx: TyCtxt<'_>,
fn lower_variant<'tcx>(
tcx: TyCtxt<'tcx>,
variant_did: Option<LocalDefId>,
ident: Ident,
discr: ty::VariantDiscr,
def: &hir::VariantData<'_>,
def: &hir::VariantData<'tcx>,
adt_kind: ty::AdtKind,
parent_did: LocalDefId,
) -> ty::VariantDef {
@ -1042,6 +1042,7 @@ fn lower_variant(
name: f.ident.name,
vis: tcx.visibility(f.def_id),
safety: f.safety,
value: f.default.map(|v| v.def_id.to_def_id()),
})
.collect();
let recovered = match def {
@ -1637,11 +1638,23 @@ fn check_impl_constness(
}
let trait_name = tcx.item_name(trait_def_id).to_string();
let (local_trait_span, suggestion_pre) =
match (trait_def_id.is_local(), tcx.sess.is_nightly_build()) {
(true, true) => (
Some(tcx.def_span(trait_def_id).shrink_to_lo()),
if tcx.features().const_trait_impl() {
""
} else {
"enable `#![feature(const_trait_impl)]` in your crate and "
},
),
(false, _) | (_, false) => (None, ""),
};
Some(tcx.dcx().emit_err(errors::ConstImplForNonConstTrait {
trait_ref_span: hir_trait_ref.path.span,
trait_name,
local_trait_span:
trait_def_id.as_local().map(|_| tcx.def_span(trait_def_id).shrink_to_lo()),
local_trait_span,
suggestion_pre,
marking: (),
adding: (),
}))

View file

@ -711,12 +711,19 @@ pub(super) fn assert_only_contains_predicates_from<'tcx>(
`{filter:?}` implied bounds: {clause:?}"
);
}
ty::ClauseKind::HostEffect(host_effect_predicate) => {
assert_eq!(
host_effect_predicate.self_ty(),
ty,
"expected `Self` predicate when computing \
`{filter:?}` implied bounds: {clause:?}"
);
}
ty::ClauseKind::RegionOutlives(_)
| ty::ClauseKind::ConstArgHasType(_, _)
| ty::ClauseKind::WellFormed(_)
| ty::ClauseKind::ConstEvaluatable(_)
| ty::ClauseKind::HostEffect(..) => {
| ty::ClauseKind::ConstEvaluatable(_) => {
bug!(
"unexpected non-`Self` predicate when computing \
`{filter:?}` implied bounds: {clause:?}"

View file

@ -125,6 +125,12 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> {
return ty;
}
Node::Field(&hir::FieldDef { default: Some(c), def_id: field_def_id, .. })
if c.hir_id == hir_id =>
{
tcx.type_of(field_def_id).instantiate_identity()
}
_ => Ty::new_error_with_message(
tcx,
span,

View file

@ -530,10 +530,16 @@ pub(crate) struct GenericArgsOnOverriddenImpl {
#[diag(hir_analysis_const_impl_for_non_const_trait)]
pub(crate) struct ConstImplForNonConstTrait {
#[primary_span]
#[label]
pub trait_ref_span: Span,
pub trait_name: String,
#[suggestion(applicability = "machine-applicable", code = "#[const_trait]")]
#[suggestion(
applicability = "machine-applicable",
code = "#[const_trait] ",
style = "verbose"
)]
pub local_trait_span: Option<Span>,
pub suggestion_pre: &'static str,
#[note]
pub marking: (),
#[note(hir_analysis_adding)]
@ -544,8 +550,19 @@ pub(crate) struct ConstImplForNonConstTrait {
#[diag(hir_analysis_const_bound_for_non_const_trait)]
pub(crate) struct ConstBoundForNonConstTrait {
#[primary_span]
#[label]
pub span: Span,
pub modifier: &'static str,
#[note]
pub def_span: Option<Span>,
pub suggestion_pre: &'static str,
#[suggestion(
applicability = "machine-applicable",
code = "#[const_trait] ",
style = "verbose"
)]
pub suggestion: Option<Span>,
pub trait_name: String,
}
#[derive(Diagnostic)]

View file

@ -203,7 +203,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
// 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();
let def_id = projection_bound.item_def_id();
def_ids.swap_remove(&def_id);
if tcx.generics_require_sized_self(def_id) {
tcx.emit_node_span_lint(
@ -413,7 +413,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
late_bound_in_projection_term,
late_bound_in_term,
|br_name| {
let item_name = tcx.item_name(pred.projection_def_id());
let item_name = tcx.item_name(pred.item_def_id());
struct_span_code_err!(
self.dcx(),
span,

View file

@ -50,7 +50,7 @@ use rustc_span::{DUMMY_SP, Span};
use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::wf::object_region_bounds;
use rustc_trait_selection::traits::{self, ObligationCtxt};
use tracing::{debug, debug_span, instrument};
use tracing::{debug, instrument};
use crate::bounds::Bounds;
use crate::check::check_abi_fn_ptr;
@ -737,9 +737,26 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
if let hir::BoundConstness::Always(span) | hir::BoundConstness::Maybe(span) = constness
&& !self.tcx().is_const_trait(trait_def_id)
{
let (def_span, suggestion, suggestion_pre) =
match (trait_def_id.is_local(), self.tcx().sess.is_nightly_build()) {
(true, true) => (
None,
Some(tcx.def_span(trait_def_id).shrink_to_lo()),
if self.tcx().features().const_trait_impl() {
""
} else {
"enable `#![feature(const_trait_impl)]` in your crate and "
},
),
(false, _) | (_, false) => (Some(tcx.def_span(trait_def_id)), None, ""),
};
self.dcx().emit_err(crate::errors::ConstBoundForNonConstTrait {
span,
modifier: constness.as_str(),
def_span,
trait_name: self.tcx().def_path_str(trait_def_id),
suggestion_pre,
suggestion,
});
}
@ -2287,19 +2304,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
hir::TyKind::Tup(fields) => {
Ty::new_tup_from_iter(tcx, fields.iter().map(|t| self.lower_ty(t)))
}
hir::TyKind::AnonAdt(item_id) => {
let _guard = debug_span!("AnonAdt");
let did = item_id.owner_id.def_id;
let adt_def = tcx.adt_def(did);
let args = ty::GenericArgs::for_item(tcx, did.to_def_id(), |param, _| {
tcx.mk_param_from_def(param)
});
debug!(?args);
Ty::new_adt(tcx, adt_def, tcx.mk_args(args))
}
hir::TyKind::BareFn(bf) => {
require_c_abi_if_c_variadic(tcx, bf.decl, bf.abi, hir_ty.span);

View file

@ -330,7 +330,6 @@ impl<'a> State<'a> {
hir::TyKind::Infer | hir::TyKind::InferDelegation(..) => {
self.word("_");
}
hir::TyKind::AnonAdt(..) => self.word("/* anonymous adt */"),
hir::TyKind::Pat(ty, pat) => {
self.print_type(ty);
self.word(" is ");
@ -1080,22 +1079,36 @@ impl<'a> State<'a> {
&mut self,
qpath: &hir::QPath<'_>,
fields: &[hir::ExprField<'_>],
wth: Option<&hir::Expr<'_>>,
wth: hir::StructTailExpr<'_>,
) {
self.print_qpath(qpath, true);
self.word("{");
self.commasep_cmnt(Consistent, fields, |s, field| s.print_expr_field(field), |f| f.span);
if let Some(expr) = wth {
self.ibox(INDENT_UNIT);
if !fields.is_empty() {
self.word(",");
self.space();
match wth {
hir::StructTailExpr::Base(expr) => {
self.ibox(INDENT_UNIT);
if !fields.is_empty() {
self.word(",");
self.space();
}
self.word("..");
self.print_expr(expr);
self.end();
}
hir::StructTailExpr::DefaultFields(_) => {
self.ibox(INDENT_UNIT);
if !fields.is_empty() {
self.word(",");
self.space();
}
self.word("..");
self.end();
}
hir::StructTailExpr::None => {
if !fields.is_empty() {
self.word(",");
}
}
self.word("..");
self.print_expr(expr);
self.end();
} else if !fields.is_empty() {
self.word(",");
}
self.word("}");

View file

@ -10,6 +10,12 @@ hir_typeck_address_of_temporary_taken = cannot take address of a temporary
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_base_expression_double_dot = base expression required after `..`
hir_typeck_base_expression_double_dot_add_expr = add a base expression here
hir_typeck_base_expression_double_dot_enable_default_field_values =
add `#![feature(default_field_values)]` to the crate attributes to enable default values on `struct` fields
hir_typeck_base_expression_double_dot_remove = remove the `..` as all the fields are already present
hir_typeck_candidate_trait_note = `{$trait_name}` defines an item `{$item_name}`{$action_or_ty ->
[NONE] {""}
[implement] , perhaps you need to implement it

View file

@ -454,20 +454,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
closure_kind: hir::ClosureKind,
projection: ty::PolyProjectionPredicate<'tcx>,
) -> Option<ExpectedSig<'tcx>> {
let tcx = self.tcx;
let trait_def_id = projection.trait_def_id(tcx);
let def_id = projection.item_def_id();
// For now, we only do signature deduction based off of the `Fn` and `AsyncFn` traits,
// for closures and async closures, respectively.
match closure_kind {
hir::ClosureKind::Closure
if self.tcx.fn_trait_kind_from_def_id(trait_def_id).is_some() =>
{
hir::ClosureKind::Closure if self.tcx.is_lang_item(def_id, LangItem::FnOnceOutput) => {
self.extract_sig_from_projection(cause_span, projection)
}
hir::ClosureKind::CoroutineClosure(hir::CoroutineDesugaring::Async)
if self.tcx.async_fn_trait_kind_from_def_id(trait_def_id).is_some() =>
if self.tcx.is_lang_item(def_id, LangItem::AsyncFnOnceOutput) =>
{
self.extract_sig_from_projection(cause_span, projection)
}
@ -475,7 +471,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// `F: FnOnce() -> Fut, Fut: Future<Output = T>` style bound. Let's still
// guide inference here, since it's beneficial for the user.
hir::ClosureKind::CoroutineClosure(hir::CoroutineDesugaring::Async)
if self.tcx.fn_trait_kind_from_def_id(trait_def_id).is_some() =>
if self.tcx.is_lang_item(def_id, LangItem::FnOnceOutput) =>
{
self.extract_sig_from_projection_and_future_bound(cause_span, projection)
}

View file

@ -15,6 +15,47 @@ use rustc_span::{Span, Symbol};
use crate::fluent_generated as fluent;
#[derive(Diagnostic)]
#[diag(hir_typeck_base_expression_double_dot, code = E0797)]
pub(crate) struct BaseExpressionDoubleDot {
#[primary_span]
pub span: Span,
#[subdiagnostic]
pub default_field_values: Option<BaseExpressionDoubleDotEnableDefaultFieldValues>,
#[subdiagnostic]
pub add_expr: Option<BaseExpressionDoubleDotAddExpr>,
#[subdiagnostic]
pub remove_dots: Option<BaseExpressionDoubleDotRemove>,
}
#[derive(Subdiagnostic)]
#[suggestion(
hir_typeck_base_expression_double_dot_remove,
code = "",
applicability = "machine-applicable",
style = "verbose"
)]
pub(crate) struct BaseExpressionDoubleDotRemove {
#[primary_span]
pub span: Span,
}
#[derive(Subdiagnostic)]
#[suggestion(
hir_typeck_base_expression_double_dot_add_expr,
code = "/* expr */",
applicability = "has-placeholders",
style = "verbose"
)]
pub(crate) struct BaseExpressionDoubleDotAddExpr {
#[primary_span]
pub span: Span,
}
#[derive(Subdiagnostic)]
#[help(hir_typeck_base_expression_double_dot_enable_default_field_values)]
pub(crate) struct BaseExpressionDoubleDotEnableDefaultFieldValues;
#[derive(Diagnostic)]
#[diag(hir_typeck_field_multiply_specified_in_initializer, code = E0062)]
pub(crate) struct FieldMultiplySpecifiedInInitializer {

View file

@ -44,10 +44,11 @@ use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectatio
use crate::TupleArgumentsFlag::DontTupleArguments;
use crate::coercion::{CoerceMany, DynamicCoerceMany};
use crate::errors::{
AddressOfTemporaryTaken, FieldMultiplySpecifiedInInitializer,
FunctionalRecordUpdateOnNonStruct, HelpUseLatestEdition, ReturnLikeStatementKind,
ReturnStmtOutsideOfFnBody, StructExprNonExhaustive, TypeMismatchFruTypo,
YieldExprOutsideOfCoroutine,
AddressOfTemporaryTaken, BaseExpressionDoubleDot, BaseExpressionDoubleDotAddExpr,
BaseExpressionDoubleDotEnableDefaultFieldValues, BaseExpressionDoubleDotRemove,
FieldMultiplySpecifiedInInitializer, FunctionalRecordUpdateOnNonStruct, HelpUseLatestEdition,
ReturnLikeStatementKind, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive,
TypeMismatchFruTypo, YieldExprOutsideOfCoroutine,
};
use crate::{
BreakableCtxt, CoroutineTypes, Diverges, FnCtxt, Needs, cast, fatally_break_rust,
@ -402,6 +403,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
})
| hir::Node::ImplItem(hir::ImplItem { kind: hir::ImplItemKind::Const(..), .. }) => true,
hir::Node::Pat(_) => {
self.dcx().span_delayed_bug(expr.span, "place expr not allowed in pattern");
true
}
// These nodes do not have direct sub-exprs.
hir::Node::Param(_)
| hir::Node::Item(_)
@ -414,7 +420,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
| hir::Node::Ty(_)
| hir::Node::AssocItemConstraint(_)
| hir::Node::TraitRef(_)
| hir::Node::Pat(_)
| hir::Node::PatField(_)
| hir::Node::LetStmt(_)
| hir::Node::Synthetic
@ -723,7 +728,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.suggest_assoc_method_call(segs);
let e =
self.dcx().span_delayed_bug(qpath.span(), "`Res::Err` but no error emitted");
self.set_tainted_by_errors(e);
Ty::new_error(tcx, e)
}
Res::Def(DefKind::Variant, _) => {
@ -1855,11 +1859,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn check_expr_struct(
&self,
expr: &hir::Expr<'_>,
expr: &hir::Expr<'tcx>,
expected: Expectation<'tcx>,
qpath: &QPath<'tcx>,
qpath: &'tcx QPath<'tcx>,
fields: &'tcx [hir::ExprField<'tcx>],
base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>,
base_expr: &'tcx hir::StructTailExpr<'tcx>,
) -> Ty<'tcx> {
// Find the relevant variant
let (variant, adt_ty) = match self.check_struct_path(qpath, expr.hir_id) {
@ -1899,7 +1903,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
span: Span,
variant: &'tcx ty::VariantDef,
hir_fields: &'tcx [hir::ExprField<'tcx>],
base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>,
base_expr: &'tcx hir::StructTailExpr<'tcx>,
) {
let tcx = self.tcx;
@ -2023,13 +2027,90 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// the fields with the base_expr. This could cause us to hit errors later
// when certain fields are assumed to exist that in fact do not.
if error_happened {
if let Some(base_expr) = base_expr {
if let hir::StructTailExpr::Base(base_expr) = base_expr {
self.check_expr(base_expr);
}
return;
}
if let Some(base_expr) = base_expr {
if let hir::StructTailExpr::DefaultFields(span) = *base_expr {
let mut missing_mandatory_fields = Vec::new();
let mut missing_optional_fields = Vec::new();
for f in &variant.fields {
let ident = self.tcx.adjust_ident(f.ident(self.tcx), variant.def_id);
if let Some(_) = remaining_fields.remove(&ident) {
if f.value.is_none() {
missing_mandatory_fields.push(ident);
} else {
missing_optional_fields.push(ident);
}
}
}
if !self.tcx.features().default_field_values() {
self.dcx().emit_err(BaseExpressionDoubleDot {
span: span.shrink_to_hi(),
// We only mention enabling the feature if this is a nightly rustc *and* the
// expression would make sense with the feature enabled.
default_field_values: if self.tcx.sess.is_nightly_build()
&& missing_mandatory_fields.is_empty()
&& !missing_optional_fields.is_empty()
{
Some(BaseExpressionDoubleDotEnableDefaultFieldValues)
} else {
None
},
add_expr: if !missing_mandatory_fields.is_empty()
|| !missing_optional_fields.is_empty()
{
Some(BaseExpressionDoubleDotAddExpr { span: span.shrink_to_hi() })
} else {
None
},
remove_dots: if missing_mandatory_fields.is_empty()
&& missing_optional_fields.is_empty()
{
Some(BaseExpressionDoubleDotRemove { span })
} else {
None
},
});
return;
}
if !missing_mandatory_fields.is_empty() {
let s = pluralize!(missing_mandatory_fields.len());
let fields: Vec<_> =
missing_mandatory_fields.iter().map(|f| format!("`{f}`")).collect();
let fields = match &fields[..] {
[] => unreachable!(),
[only] => only.to_string(),
[start @ .., last] => format!("{} and {last}", start.join(", ")),
};
self.dcx()
.struct_span_err(
span.shrink_to_hi(),
format!("missing mandatory field{s} {fields}"),
)
.emit();
return;
}
let fru_tys = match adt_ty.kind() {
ty::Adt(adt, args) if adt.is_struct() => variant
.fields
.iter()
.map(|f| self.normalize(span, f.ty(self.tcx, args)))
.collect(),
ty::Adt(adt, args) if adt.is_enum() => variant
.fields
.iter()
.map(|f| self.normalize(span, f.ty(self.tcx, args)))
.collect(),
_ => {
self.dcx().emit_err(FunctionalRecordUpdateOnNonStruct { span });
return;
}
};
self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr.hir_id, fru_tys);
} else if let hir::StructTailExpr::Base(base_expr) = base_expr {
// FIXME: We are currently creating two branches here in order to maintain
// consistency. But they should be merged as much as possible.
let fru_tys = if self.tcx.features().type_changing_struct_update() {
@ -2161,12 +2242,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn check_struct_fields_on_error(
&self,
fields: &'tcx [hir::ExprField<'tcx>],
base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>,
base_expr: &'tcx hir::StructTailExpr<'tcx>,
) {
for field in fields {
self.check_expr(field.expr);
}
if let Some(base) = *base_expr {
if let hir::StructTailExpr::Base(base) = *base_expr {
self.check_expr(base);
}
}
@ -2611,33 +2692,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
None
}
// Check field access expressions
/// Check field access expressions, this works for both structs and tuples.
/// Returns the Ty of the field.
///
/// ```not_rust
/// base.field
/// ^^^^^^^^^^ expr
/// ^^^^ base
/// ^^^^^ field
/// ```
fn check_expr_field(
&self,
expr: &'tcx hir::Expr<'tcx>,
base: &'tcx hir::Expr<'tcx>,
field: Ident,
// The expected type hint of the field.
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
debug!("check_field(expr: {:?}, base: {:?}, field: {:?})", expr, base, field);
let base_ty = self.check_expr(base);
let base_ty = self.structurally_resolve_type(base.span, base_ty);
// Whether we are trying to access a private field. Used for error reporting.
let mut private_candidate = None;
// Field expressions automatically deref
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, args) if !base_def.is_enum() => {
debug!("struct named {:?}", deref_base_ty);
let body_hir_id = self.tcx.local_def_id_to_hir_id(self.body_id);
let (ident, def_scope) =
self.tcx.adjust_ident_and_get_scope(field, base_def.did(), body_hir_id);
// we don't care to report errors for a struct if the struct itself is tainted
if let Err(guar) = base_def.non_enum_variant().has_errors() {
return Ty::new_error(self.tcx(), guar);
}
let fn_body_hir_id = self.tcx.local_def_id_to_hir_id(self.body_id);
let (ident, def_scope) =
self.tcx.adjust_ident_and_get_scope(field, base_def.did(), fn_body_hir_id);
if let Some((idx, field)) = self.find_adt_field(*base_def, ident) {
self.write_field_index(expr.hir_id, idx);
@ -2671,6 +2765,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
_ => {}
}
}
// We failed to check the expression, report an error.
// Emits an error if we deref an infer variable, like calling `.field` on a base type of &_.
self.structurally_resolve_type(autoderef.span(), autoderef.final_ty(false));
if let Some((adjustments, did)) = private_candidate {
@ -2695,6 +2792,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr.hir_id,
expected.only_has_type(self),
) {
// If taking a method instead of calling it
self.ban_take_value_of_method(expr, base_ty, field)
} else if !base_ty.is_primitive_ty() {
self.ban_nonexisting_field(field, base, expr, base_ty)

View file

@ -686,7 +686,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
fn walk_struct_expr<'hir>(
&self,
fields: &[hir::ExprField<'_>],
opt_with: &Option<&'hir hir::Expr<'_>>,
opt_with: &hir::StructTailExpr<'hir>,
) -> Result<(), Cx::Error> {
// Consume the expressions supplying values for each field.
for field in fields {
@ -702,8 +702,8 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
}
let with_expr = match *opt_with {
Some(w) => &*w,
None => {
hir::StructTailExpr::Base(w) => &*w,
hir::StructTailExpr::DefaultFields(_) | hir::StructTailExpr::None => {
return Ok(());
}
};

View file

@ -903,6 +903,34 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
let detect_dotdot = |err: &mut Diag<'_>, ty: Ty<'_>, expr: &hir::Expr<'_>| {
if let ty::Adt(adt, _) = ty.kind()
&& self.tcx().lang_items().get(hir::LangItem::RangeFull) == Some(adt.did())
&& let hir::ExprKind::Struct(
hir::QPath::LangItem(hir::LangItem::RangeFull, _),
[],
_,
) = expr.kind
{
// We have `Foo(a, .., c)`, where the user might be trying to use the "rest" syntax
// from default field values, which is not supported on tuples.
let explanation = if self.tcx.features().default_field_values() {
"this is only supported on non-tuple struct literals"
} else if self.tcx.sess.is_nightly_build() {
"this is only supported on non-tuple struct literals when \
`#![feature(default_field_values)]` is enabled"
} else {
"this is not supported"
};
let msg = format!(
"you might have meant to use `..` to skip providing a value for \
expected fields, but {explanation}; it is instead interpreted as a \
`std::ops::RangeFull` literal",
);
err.span_help(expr.span, msg);
}
};
let mut reported = None;
errors.retain(|error| {
let Error::Invalid(provided_idx, expected_idx, Compatibility::Incompatible(Some(e))) =
@ -1009,6 +1037,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
tuple_arguments,
);
suggest_confusable(&mut err);
detect_dotdot(&mut err, provided_ty, provided_args[provided_idx]);
return err.emit();
}
@ -1133,6 +1162,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
None,
None,
);
detect_dotdot(&mut err, provided_ty, provided_args[provided_idx]);
}
Error::Extra(arg_idx) => {
let (provided_ty, provided_span) = provided_arg_tys[arg_idx];
@ -1216,6 +1246,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
};
prev_extra_idx = Some(arg_idx.index())
}
detect_dotdot(&mut err, provided_ty, provided_args[arg_idx]);
}
Error::Missing(expected_idx) => {
// If there are multiple missing arguments adjacent to each other,

View file

@ -296,6 +296,111 @@ impl<T: Idx> From<GrowableBitSet<T>> for BitSet<T> {
}
}
impl<T> Clone for BitSet<T> {
fn clone(&self) -> Self {
BitSet { domain_size: self.domain_size, words: self.words.clone(), marker: PhantomData }
}
fn clone_from(&mut self, from: &Self) {
self.domain_size = from.domain_size;
self.words.clone_from(&from.words);
}
}
impl<T: Idx> fmt::Debug for BitSet<T> {
fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
w.debug_list().entries(self.iter()).finish()
}
}
impl<T: Idx> ToString for BitSet<T> {
fn to_string(&self) -> String {
let mut result = String::new();
let mut sep = '[';
// Note: this is a little endian printout of bytes.
// i tracks how many bits we have printed so far.
let mut i = 0;
for word in &self.words {
let mut word = *word;
for _ in 0..WORD_BYTES {
// for each byte in `word`:
let remain = self.domain_size - i;
// If less than a byte remains, then mask just that many bits.
let mask = if remain <= 8 { (1 << remain) - 1 } else { 0xFF };
assert!(mask <= 0xFF);
let byte = word & mask;
result.push_str(&format!("{sep}{byte:02x}"));
if remain <= 8 {
break;
}
word >>= 8;
i += 8;
sep = '-';
}
sep = '|';
}
result.push(']');
result
}
}
pub struct BitIter<'a, T: Idx> {
/// A copy of the current word, but with any already-visited bits cleared.
/// (This lets us use `trailing_zeros()` to find the next set bit.) When it
/// is reduced to 0, we move onto the next word.
word: Word,
/// The offset (measured in bits) of the current word.
offset: usize,
/// Underlying iterator over the words.
iter: slice::Iter<'a, Word>,
marker: PhantomData<T>,
}
impl<'a, T: Idx> BitIter<'a, T> {
#[inline]
fn new(words: &'a [Word]) -> BitIter<'a, T> {
// We initialize `word` and `offset` to degenerate values. On the first
// call to `next()` we will fall through to getting the first word from
// `iter`, which sets `word` to the first word (if there is one) and
// `offset` to 0. Doing it this way saves us from having to maintain
// additional state about whether we have started.
BitIter {
word: 0,
offset: usize::MAX - (WORD_BITS - 1),
iter: words.iter(),
marker: PhantomData,
}
}
}
impl<'a, T: Idx> Iterator for BitIter<'a, T> {
type Item = T;
fn next(&mut self) -> Option<T> {
loop {
if self.word != 0 {
// Get the position of the next set bit in the current word,
// then clear the bit.
let bit_pos = self.word.trailing_zeros() as usize;
self.word ^= 1 << bit_pos;
return Some(T::new(bit_pos + self.offset));
}
// Move onto the next word. `wrapping_add()` is needed to handle
// the degenerate initial value given to `offset` in `new()`.
self.word = *self.iter.next()?;
self.offset = self.offset.wrapping_add(WORD_BITS);
}
}
}
/// A fixed-size bitset type with a partially dense, partially sparse
/// representation. The bitset is broken into chunks, and chunks that are all
/// zeros or all ones are represented and handled very efficiently.
@ -305,6 +410,9 @@ impl<T: Idx> From<GrowableBitSet<T>> for BitSet<T> {
/// some stretches with lots of 0s and 1s mixed in a way that causes trouble
/// for `IntervalSet`.
///
/// Best used via `MixedBitSet`, rather than directly, because `MixedBitSet`
/// has better performance for small bitsets.
///
/// `T` is an index type, typically a newtyped `usize` wrapper, but it can also
/// just be `usize`.
///
@ -958,117 +1066,12 @@ fn sequential_update<T: Idx>(
it.fold(false, |changed, elem| self_update(elem) | changed)
}
impl<T> Clone for BitSet<T> {
fn clone(&self) -> Self {
BitSet { domain_size: self.domain_size, words: self.words.clone(), marker: PhantomData }
}
fn clone_from(&mut self, from: &Self) {
self.domain_size = from.domain_size;
self.words.clone_from(&from.words);
}
}
impl<T: Idx> fmt::Debug for BitSet<T> {
fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
w.debug_list().entries(self.iter()).finish()
}
}
impl<T: Idx> fmt::Debug for ChunkedBitSet<T> {
fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
w.debug_list().entries(self.iter()).finish()
}
}
impl<T: Idx> ToString for BitSet<T> {
fn to_string(&self) -> String {
let mut result = String::new();
let mut sep = '[';
// Note: this is a little endian printout of bytes.
// i tracks how many bits we have printed so far.
let mut i = 0;
for word in &self.words {
let mut word = *word;
for _ in 0..WORD_BYTES {
// for each byte in `word`:
let remain = self.domain_size - i;
// If less than a byte remains, then mask just that many bits.
let mask = if remain <= 8 { (1 << remain) - 1 } else { 0xFF };
assert!(mask <= 0xFF);
let byte = word & mask;
result.push_str(&format!("{sep}{byte:02x}"));
if remain <= 8 {
break;
}
word >>= 8;
i += 8;
sep = '-';
}
sep = '|';
}
result.push(']');
result
}
}
pub struct BitIter<'a, T: Idx> {
/// A copy of the current word, but with any already-visited bits cleared.
/// (This lets us use `trailing_zeros()` to find the next set bit.) When it
/// is reduced to 0, we move onto the next word.
word: Word,
/// The offset (measured in bits) of the current word.
offset: usize,
/// Underlying iterator over the words.
iter: slice::Iter<'a, Word>,
marker: PhantomData<T>,
}
impl<'a, T: Idx> BitIter<'a, T> {
#[inline]
fn new(words: &'a [Word]) -> BitIter<'a, T> {
// We initialize `word` and `offset` to degenerate values. On the first
// call to `next()` we will fall through to getting the first word from
// `iter`, which sets `word` to the first word (if there is one) and
// `offset` to 0. Doing it this way saves us from having to maintain
// additional state about whether we have started.
BitIter {
word: 0,
offset: usize::MAX - (WORD_BITS - 1),
iter: words.iter(),
marker: PhantomData,
}
}
}
impl<'a, T: Idx> Iterator for BitIter<'a, T> {
type Item = T;
fn next(&mut self) -> Option<T> {
loop {
if self.word != 0 {
// Get the position of the next set bit in the current word,
// then clear the bit.
let bit_pos = self.word.trailing_zeros() as usize;
self.word ^= 1 << bit_pos;
return Some(T::new(bit_pos + self.offset));
}
// Move onto the next word. `wrapping_add()` is needed to handle
// the degenerate initial value given to `offset` in `new()`.
self.word = *self.iter.next()?;
self.offset = self.offset.wrapping_add(WORD_BITS);
}
}
}
#[inline]
fn bitwise<Op>(out_vec: &mut [Word], in_vec: &[Word], op: Op) -> bool
where
@ -1106,6 +1109,158 @@ where
false
}
/// A bitset with a mixed representation, using `BitSet` for small and medium
/// bitsets, and `ChunkedBitSet` for large bitsets, i.e. those with enough bits
/// for at least two chunks. This is a good choice for many bitsets that can
/// have large domain sizes (e.g. 5000+).
///
/// `T` is an index type, typically a newtyped `usize` wrapper, but it can also
/// just be `usize`.
///
/// All operations that involve an element will panic if the element is equal
/// to or greater than the domain size. All operations that involve two bitsets
/// will panic if the bitsets have differing domain sizes.
#[derive(PartialEq, Eq)]
pub enum MixedBitSet<T> {
Small(BitSet<T>),
Large(ChunkedBitSet<T>),
}
impl<T> MixedBitSet<T> {
pub fn domain_size(&self) -> usize {
match self {
MixedBitSet::Small(set) => set.domain_size(),
MixedBitSet::Large(set) => set.domain_size(),
}
}
}
impl<T: Idx> MixedBitSet<T> {
#[inline]
pub fn new_empty(domain_size: usize) -> MixedBitSet<T> {
if domain_size <= CHUNK_BITS {
MixedBitSet::Small(BitSet::new_empty(domain_size))
} else {
MixedBitSet::Large(ChunkedBitSet::new_empty(domain_size))
}
}
#[inline]
pub fn is_empty(&self) -> bool {
match self {
MixedBitSet::Small(set) => set.is_empty(),
MixedBitSet::Large(set) => set.is_empty(),
}
}
#[inline]
pub fn contains(&self, elem: T) -> bool {
match self {
MixedBitSet::Small(set) => set.contains(elem),
MixedBitSet::Large(set) => set.contains(elem),
}
}
#[inline]
pub fn insert(&mut self, elem: T) -> bool {
match self {
MixedBitSet::Small(set) => set.insert(elem),
MixedBitSet::Large(set) => set.insert(elem),
}
}
pub fn insert_all(&mut self) {
match self {
MixedBitSet::Small(set) => set.insert_all(),
MixedBitSet::Large(set) => set.insert_all(),
}
}
#[inline]
pub fn remove(&mut self, elem: T) -> bool {
match self {
MixedBitSet::Small(set) => set.remove(elem),
MixedBitSet::Large(set) => set.remove(elem),
}
}
pub fn iter(&self) -> MixedBitIter<'_, T> {
match self {
MixedBitSet::Small(set) => MixedBitIter::Small(set.iter()),
MixedBitSet::Large(set) => MixedBitIter::Large(set.iter()),
}
}
bit_relations_inherent_impls! {}
}
impl<T> Clone for MixedBitSet<T> {
fn clone(&self) -> Self {
match self {
MixedBitSet::Small(set) => MixedBitSet::Small(set.clone()),
MixedBitSet::Large(set) => MixedBitSet::Large(set.clone()),
}
}
/// WARNING: this implementation of clone_from may panic if the two
/// bitsets have different domain sizes. This constraint is not inherent to
/// `clone_from`, but it works with the existing call sites and allows a
/// faster implementation, which is important because this function is hot.
fn clone_from(&mut self, from: &Self) {
match (self, from) {
(MixedBitSet::Small(set), MixedBitSet::Small(from)) => set.clone_from(from),
(MixedBitSet::Large(set), MixedBitSet::Large(from)) => set.clone_from(from),
_ => panic!("MixedBitSet size mismatch"),
}
}
}
impl<T: Idx> BitRelations<MixedBitSet<T>> for MixedBitSet<T> {
fn union(&mut self, other: &MixedBitSet<T>) -> bool {
match (self, other) {
(MixedBitSet::Small(set), MixedBitSet::Small(other)) => set.union(other),
(MixedBitSet::Large(set), MixedBitSet::Large(other)) => set.union(other),
_ => panic!("MixedBitSet size mismatch"),
}
}
fn subtract(&mut self, other: &MixedBitSet<T>) -> bool {
match (self, other) {
(MixedBitSet::Small(set), MixedBitSet::Small(other)) => set.subtract(other),
(MixedBitSet::Large(set), MixedBitSet::Large(other)) => set.subtract(other),
_ => panic!("MixedBitSet size mismatch"),
}
}
fn intersect(&mut self, _other: &MixedBitSet<T>) -> bool {
unimplemented!("implement if/when necessary");
}
}
impl<T: Idx> fmt::Debug for MixedBitSet<T> {
fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MixedBitSet::Small(set) => set.fmt(w),
MixedBitSet::Large(set) => set.fmt(w),
}
}
}
pub enum MixedBitIter<'a, T: Idx> {
Small(BitIter<'a, T>),
Large(ChunkedBitIter<'a, T>),
}
impl<'a, T: Idx> Iterator for MixedBitIter<'a, T> {
type Item = T;
fn next(&mut self) -> Option<T> {
match self {
MixedBitIter::Small(iter) => iter.next(),
MixedBitIter::Large(iter) => iter.next(),
}
}
}
/// A resizable bitset type with a dense representation.
///
/// `T` is an index type, typically a newtyped `usize` wrapper, but it can also
@ -1374,7 +1529,7 @@ impl<R: Idx, C: Idx> fmt::Debug for BitMatrix<R, C> {
/// sparse representation.
///
/// Initially, every row has no explicit representation. If any bit within a
/// row is set, the entire row is instantiated as `Some(<ChunkedBitSet>)`.
/// row is set, the entire row is instantiated as `Some(<BitSet>)`.
/// Furthermore, any previously uninstantiated rows prior to it will be
/// instantiated as `None`. Those prior rows may themselves become fully
/// instantiated later on if any of their bits are set.
@ -1388,7 +1543,7 @@ where
C: Idx,
{
num_columns: usize,
rows: IndexVec<R, Option<ChunkedBitSet<C>>>,
rows: IndexVec<R, Option<BitSet<C>>>,
}
impl<R: Idx, C: Idx> SparseBitMatrix<R, C> {
@ -1397,10 +1552,10 @@ impl<R: Idx, C: Idx> SparseBitMatrix<R, C> {
Self { num_columns, rows: IndexVec::new() }
}
fn ensure_row(&mut self, row: R) -> &mut ChunkedBitSet<C> {
// Instantiate any missing rows up to and including row `row` with an empty ChunkedBitSet.
// Then replace row `row` with a full ChunkedBitSet if necessary.
self.rows.get_or_insert_with(row, || ChunkedBitSet::new_empty(self.num_columns))
fn ensure_row(&mut self, row: R) -> &mut BitSet<C> {
// Instantiate any missing rows up to and including row `row` with an empty `BitSet`.
// Then replace row `row` with a full `BitSet` if necessary.
self.rows.get_or_insert_with(row, || BitSet::new_empty(self.num_columns))
}
/// Sets the cell at `(row, column)` to true. Put another way, insert
@ -1474,7 +1629,7 @@ impl<R: Idx, C: Idx> SparseBitMatrix<R, C> {
self.row(row).into_iter().flat_map(|r| r.iter())
}
pub fn row(&self, row: R) -> Option<&ChunkedBitSet<C>> {
pub fn row(&self, row: R) -> Option<&BitSet<C>> {
self.rows.get(row)?.as_ref()
}
@ -1484,7 +1639,7 @@ impl<R: Idx, C: Idx> SparseBitMatrix<R, C> {
/// Returns true if the row was changed.
pub fn intersect_row<Set>(&mut self, row: R, set: &Set) -> bool
where
ChunkedBitSet<C>: BitRelations<Set>,
BitSet<C>: BitRelations<Set>,
{
match self.rows.get_mut(row) {
Some(Some(row)) => row.intersect(set),
@ -1498,7 +1653,7 @@ impl<R: Idx, C: Idx> SparseBitMatrix<R, C> {
/// Returns true if the row was changed.
pub fn subtract_row<Set>(&mut self, row: R, set: &Set) -> bool
where
ChunkedBitSet<C>: BitRelations<Set>,
BitSet<C>: BitRelations<Set>,
{
match self.rows.get_mut(row) {
Some(Some(row)) => row.subtract(set),
@ -1512,7 +1667,7 @@ impl<R: Idx, C: Idx> SparseBitMatrix<R, C> {
/// Returns true if the row was changed.
pub fn union_row<Set>(&mut self, row: R, set: &Set) -> bool
where
ChunkedBitSet<C>: BitRelations<Set>,
BitSet<C>: BitRelations<Set>,
{
self.ensure_row(row).union(set)
}

View file

@ -503,15 +503,15 @@ fn sparse_matrix_operations() {
matrix.insert(2, 99);
matrix.insert(4, 0);
let mut disjoint: ChunkedBitSet<usize> = ChunkedBitSet::new_empty(100);
let mut disjoint: BitSet<usize> = BitSet::new_empty(100);
disjoint.insert(33);
let mut superset = ChunkedBitSet::new_empty(100);
let mut superset = BitSet::new_empty(100);
superset.insert(22);
superset.insert(75);
superset.insert(33);
let mut subset = ChunkedBitSet::new_empty(100);
let mut subset = BitSet::new_empty(100);
subset.insert(22);
// SparseBitMatrix::remove

View file

@ -33,6 +33,7 @@ pub mod unescape;
mod tests;
use unicode_properties::UnicodeEmoji;
pub use unicode_xid::UNICODE_VERSION as UNICODE_XID_VERSION;
use self::LiteralKind::*;
use self::TokenKind::*;

View file

@ -359,6 +359,7 @@ lint_improper_ctypes_128bit = 128-bit integers don't currently have a known stab
lint_improper_ctypes_array_help = consider passing a pointer to the array
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_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead
@ -376,9 +377,7 @@ lint_improper_ctypes_enum_repr_help =
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_improper_ctypes_fnptr_indirect_reason = the function pointer to `{$ty}` is FFI-unsafe due to `{$inner_ty}`
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
@ -389,11 +388,7 @@ lint_improper_ctypes_opaque = opaque types have no C equivalent
lint_improper_ctypes_pat_help = consider using the base type instead
lint_improper_ctypes_pat_reason = pattern types have no C equivalent
lint_improper_ctypes_sized_ptr_to_unsafe_type =
this reference (`{$ty}`) is ABI-compatible with a C pointer, but `{$inner_ty}` itself does not have a C layout
lint_improper_ctypes_slice_help = consider using a raw pointer to the slice's first element (and a length) instead
lint_improper_ctypes_slice_help = consider using a raw pointer instead
lint_improper_ctypes_slice_reason = slices have no C equivalent
lint_improper_ctypes_str_help = consider using `*const u8` and a length instead
@ -419,10 +414,6 @@ lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[re
lint_improper_ctypes_union_layout_reason = this union has unspecified layout
lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive
lint_improper_ctypes_unsized_box = this box for an unsized type contains metadata, which makes it incompatible with a C pointer
lint_improper_ctypes_unsized_ptr = this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer
lint_improper_ctypes_unsized_ref = this reference to an unsized type contains metadata, which makes it incompatible with a C pointer
lint_incomplete_include =
include macro expected single expression in source

View file

@ -625,6 +625,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations {
cx.param_env,
ty,
traits::ObligationCause::misc(item.span, item.owner_id.def_id),
hir::Safety::Safe,
)
.is_ok()
{

View file

@ -1851,44 +1851,13 @@ pub(crate) struct UnpredictableFunctionPointerComparisonsSuggestion<'a> {
pub right: Span,
}
pub(crate) struct ImproperCTypesLayer<'a> {
pub ty: Ty<'a>,
pub inner_ty: Option<Ty<'a>>,
pub note: DiagMessage,
pub span_note: Option<Span>,
pub help: Option<DiagMessage>,
}
impl<'a> Subdiagnostic for ImproperCTypesLayer<'a> {
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
self,
diag: &mut Diag<'_, G>,
f: &F,
) {
diag.arg("ty", self.ty);
if let Some(ty) = self.inner_ty {
diag.arg("inner_ty", ty);
}
if let Some(help) = self.help {
let msg = f(diag, help.into());
diag.help(msg);
}
let msg = f(diag, self.note.into());
diag.note(msg);
if let Some(note) = self.span_note {
let msg = f(diag, fluent::lint_note.into());
diag.span_note(note, msg);
};
}
}
pub(crate) struct ImproperCTypes<'a> {
pub ty: Ty<'a>,
pub desc: &'a str,
pub label: Span,
pub reasons: Vec<ImproperCTypesLayer<'a>>,
pub help: Option<DiagMessage>,
pub note: DiagMessage,
pub span_note: Option<Span>,
}
// Used because of the complexity of Option<DiagMessage>, DiagMessage, and Option<Span>
@ -1898,8 +1867,12 @@ impl<'a> LintDiagnostic<'a, ()> for ImproperCTypes<'_> {
diag.arg("ty", self.ty);
diag.arg("desc", self.desc);
diag.span_label(self.label, fluent::lint_label);
for reason in self.reasons.into_iter() {
diag.subdiagnostic(reason);
if let Some(help) = self.help {
diag.help(help);
}
diag.note(self.note);
if let Some(note) = self.span_note {
diag.span_note(note, fluent::lint_note);
}
}
}

View file

@ -22,10 +22,10 @@ mod improper_ctypes;
use crate::lints::{
AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion,
AmbiguousWidePointerComparisonsAddrSuggestion, AtomicOrderingFence, AtomicOrderingLoad,
AtomicOrderingStore, ImproperCTypes, ImproperCTypesLayer, InvalidAtomicOrderingDiag,
InvalidNanComparisons, InvalidNanComparisonsSuggestion,
UnpredictableFunctionPointerComparisons, UnpredictableFunctionPointerComparisonsSuggestion,
UnusedComparisons, VariantSizeDifferencesDiag,
AtomicOrderingStore, ImproperCTypes, InvalidAtomicOrderingDiag, InvalidNanComparisons,
InvalidNanComparisonsSuggestion, UnpredictableFunctionPointerComparisons,
UnpredictableFunctionPointerComparisonsSuggestion, UnusedComparisons,
VariantSizeDifferencesDiag,
};
use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent};
@ -727,109 +727,7 @@ struct CTypesVisitorState<'tcx> {
enum FfiResult<'tcx> {
FfiSafe,
FfiPhantom(Ty<'tcx>),
FfiUnsafe {
ty: Ty<'tcx>,
reason: DiagMessage,
help: Option<DiagMessage>,
},
FfiUnsafeWrapper {
ty: Ty<'tcx>,
reason: DiagMessage,
help: Option<DiagMessage>,
wrapped: Box<FfiResult<'tcx>>,
},
}
/// Determine if a type is sized or not, and wether it affects references/pointers/boxes to it
#[derive(Clone, Copy)]
enum TypeSizedness {
/// type of definite size (pointers are C-compatible)
Definite,
/// unsized type because it includes an opaque/foreign type (pointers are C-compatible)
UnsizedWithExternType,
/// unsized type for other reasons (slice, string, dyn Trait, closure, ...) (pointers are not C-compatible)
UnsizedWithMetadata,
}
/// Is this type unsized because it contains (or is) a foreign type?
/// (Returns Err if the type happens to be sized after all)
fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> TypeSizedness {
let tcx = cx.tcx;
if ty.is_sized(tcx, cx.typing_env()) {
TypeSizedness::Definite
} else {
match ty.kind() {
ty::Slice(_) => TypeSizedness::UnsizedWithMetadata,
ty::Str => TypeSizedness::UnsizedWithMetadata,
ty::Dynamic(..) => TypeSizedness::UnsizedWithMetadata,
ty::Foreign(..) => TypeSizedness::UnsizedWithExternType,
// While opaque types are checked for earlier, if a projection in a struct field
// normalizes to an opaque type, then it will reach this branch.
ty::Alias(ty::Opaque, ..) => todo!("We... don't know enough about this type yet?"),
ty::Adt(def, args) => {
// for now assume: boxes and phantoms don't mess with this
match def.adt_kind() {
AdtKind::Union | AdtKind::Enum => {
bug!("unions and enums are necessarily sized")
}
AdtKind::Struct => {
if let Some(sym::cstring_type | sym::cstr_type) =
tcx.get_diagnostic_name(def.did())
{
return TypeSizedness::UnsizedWithMetadata;
}
// FIXME: how do we deal with non-exhaustive unsized structs/unions?
if def.non_enum_variant().fields.is_empty() {
bug!("an empty struct is necessarily sized");
}
let variant = def.non_enum_variant();
// only the last field may be unsized
let n_fields = variant.fields.len();
let last_field = &variant.fields[(n_fields - 1).into()];
let field_ty = last_field.ty(cx.tcx, args);
let field_ty = cx
.tcx
.try_normalize_erasing_regions(cx.typing_env(), field_ty)
.unwrap_or(field_ty);
match get_type_sizedness(cx, field_ty) {
s @ (TypeSizedness::UnsizedWithMetadata
| TypeSizedness::UnsizedWithExternType) => s,
TypeSizedness::Definite => {
bug!("failed to find the reason why struct `{:?}` is unsized", ty)
}
}
}
}
}
ty::Tuple(tuple) => {
// only the last field may be unsized
let n_fields = tuple.len();
let field_ty: Ty<'tcx> = tuple[n_fields - 1];
//let field_ty = last_field.ty(cx.tcx, args);
let field_ty = cx
.tcx
.try_normalize_erasing_regions(cx.typing_env(), field_ty)
.unwrap_or(field_ty);
match get_type_sizedness(cx, field_ty) {
s @ (TypeSizedness::UnsizedWithMetadata
| TypeSizedness::UnsizedWithExternType) => s,
TypeSizedness::Definite => {
bug!("failed to find the reason why tuple `{:?}` is unsized", ty)
}
}
}
ty => {
bug!(
"we shouldn't be trying to determine if this is unsized for a reason or another: `{:?}`",
ty
)
}
}
}
FfiUnsafe { ty: Ty<'tcx>, reason: DiagMessage, help: Option<DiagMessage> },
}
pub(crate) fn nonnull_optimization_guaranteed<'tcx>(
@ -866,7 +764,7 @@ fn ty_is_known_nonnull<'tcx>(
match ty.kind() {
ty::FnPtr(..) => true,
ty::Ref(..) => true,
ty::Adt(def, _) if def.is_box() => true,
ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true,
ty::Adt(def, args) if def.repr().transparent() && !def.is_union() => {
let marked_non_null = nonnull_optimization_guaranteed(tcx, *def);
@ -1035,13 +933,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
/// Check if the type is array and emit an unsafe type lint.
fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool {
if let ty::Array(..) = ty.kind() {
self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer {
self.emit_ffi_unsafe_type_lint(
ty,
note: fluent::lint_improper_ctypes_array_reason,
help: Some(fluent::lint_improper_ctypes_array_help),
inner_ty: None,
span_note: None,
}]);
sp,
fluent::lint_improper_ctypes_array_reason,
Some(fluent::lint_improper_ctypes_array_help),
);
true
} else {
false
@ -1098,9 +995,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
all_phantom &= match self.check_field_type_for_ffi(acc, field, args) {
FfiSafe => false,
// `()` fields are FFI-safe!
FfiUnsafe { ty, .. } | FfiUnsafeWrapper { ty, .. } if ty.is_unit() => false,
FfiUnsafe { ty, .. } if ty.is_unit() => false,
FfiPhantom(..) => true,
r @ (FfiUnsafe { .. } | FfiUnsafeWrapper { .. }) => return r,
r @ FfiUnsafe { .. } => return r,
}
}
@ -1134,47 +1031,16 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
match *ty.kind() {
ty::Adt(def, args) => {
if let Some(inner_ty) = ty.boxed_ty() {
if let TypeSizedness::UnsizedWithExternType | TypeSizedness::Definite =
get_type_sizedness(self.cx, inner_ty)
{
// discussion on declaration vs definition:
// see the `ty::RawPtr(inner_ty, _) | ty::Ref(_, inner_ty, _)` arm
// of this `match *ty.kind()` block
if matches!(self.mode, CItemKind::Definition) {
return FfiSafe;
} else {
let inner_res = self.check_type_for_ffi(acc, inner_ty);
return match inner_res {
FfiUnsafe { .. } | FfiUnsafeWrapper { .. } => FfiUnsafeWrapper {
ty,
reason: fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type,
wrapped: Box::new(inner_res),
help: None,
},
_ => inner_res,
};
}
if let Some(boxed) = ty.boxed_ty()
&& matches!(self.mode, CItemKind::Definition)
{
if boxed.is_sized(tcx, self.cx.typing_env()) {
return FfiSafe;
} else {
let help = match inner_ty.kind() {
ty::Str => Some(fluent::lint_improper_ctypes_str_help),
ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help),
ty::Adt(def, _)
if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union)
&& matches!(
tcx.get_diagnostic_name(def.did()),
Some(sym::cstring_type | sym::cstr_type)
)
&& !acc.base_ty.is_mutable_ptr() =>
{
Some(fluent::lint_improper_ctypes_cstr_help)
}
_ => None,
};
return FfiUnsafe {
ty,
reason: fluent::lint_improper_ctypes_unsized_box,
help,
reason: fluent::lint_improper_ctypes_box,
help: None,
};
}
}
@ -1330,6 +1196,15 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
help: Some(fluent::lint_improper_ctypes_tuple_help),
},
ty::RawPtr(ty, _) | ty::Ref(_, ty, _)
if {
matches!(self.mode, CItemKind::Definition)
&& ty.is_sized(self.cx.tcx, self.cx.typing_env())
} =>
{
FfiSafe
}
ty::RawPtr(ty, _)
if match ty.kind() {
ty::Tuple(tuple) => tuple.is_empty(),
@ -1339,70 +1214,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
FfiSafe
}
ty::RawPtr(inner_ty, _) | ty::Ref(_, inner_ty, _) => {
if let TypeSizedness::UnsizedWithExternType | TypeSizedness::Definite =
get_type_sizedness(self.cx, inner_ty)
{
// there's a nuance on what this lint should do for
// function definitions (`extern "C" fn fn_name(...) {...}`)
// versus declarations (`unsafe extern "C" {fn fn_name(...);}`).
// This is touched upon in https://github.com/rust-lang/rust/issues/66220
// and https://github.com/rust-lang/rust/pull/72700
//
// The big question is: what does "ABI safety" mean? if you have something translated to a C pointer
// (which has a stable layout) but points to FFI-unsafe type, is it safe?
// On one hand, the function's ABI will match that of a similar C-declared function API,
// on the other, dereferencing the pointer on the other side of the FFI boundary will be painful.
// In this code, the opinion on is split between function declarations and function definitions,
// with the idea that at least one side of the FFI boundary needs to treat the pointee as an opaque type.
// For declarations, we see this as unsafe, but for definitions, we see this as safe.
//
// For extern function declarations, the actual definition of the function is written somewhere else,
// meaning the declaration is free to express this opaqueness with an extern type (opaque caller-side) or a std::ffi::c_void (opaque callee-side)
// For extern function definitions, however, in the case where the type is opaque caller-side, it is not opaque callee-side,
// and having the full type information is necessary to compile the function.
if matches!(self.mode, CItemKind::Definition) {
return FfiSafe;
} else if matches!(ty.kind(), ty::RawPtr(..))
&& matches!(inner_ty.kind(), ty::Tuple(tuple) if tuple.is_empty())
{
FfiSafe
} else {
let inner_res = self.check_type_for_ffi(acc, inner_ty);
return match inner_res {
FfiSafe => inner_res,
_ => FfiUnsafeWrapper {
ty,
reason: fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type,
wrapped: Box::new(inner_res),
help: None,
},
};
}
} else {
let help = match inner_ty.kind() {
ty::Str => Some(fluent::lint_improper_ctypes_str_help),
ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help),
ty::Adt(def, _)
if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union)
&& matches!(
tcx.get_diagnostic_name(def.did()),
Some(sym::cstring_type | sym::cstr_type)
)
&& !acc.base_ty.is_mutable_ptr() =>
{
Some(fluent::lint_improper_ctypes_cstr_help)
}
_ => None,
};
let reason = match ty.kind() {
ty::RawPtr(..) => fluent::lint_improper_ctypes_unsized_ptr,
ty::Ref(..) => fluent::lint_improper_ctypes_unsized_ref,
_ => unreachable!(),
};
FfiUnsafe { ty, reason, help }
}
}
ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.check_type_for_ffi(acc, ty),
ty::Array(inner_ty, _) => self.check_type_for_ffi(acc, inner_ty),
@ -1420,14 +1232,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
for arg in sig.inputs() {
match self.check_type_for_ffi(acc, *arg) {
FfiSafe => {}
r => {
return FfiUnsafeWrapper {
ty,
reason: fluent::lint_improper_ctypes_fnptr_indirect_reason,
help: None,
wrapped: Box::new(r),
};
}
r => return r,
}
}
@ -1436,15 +1241,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
return FfiSafe;
}
match self.check_type_for_ffi(acc, ret_ty) {
r @ (FfiSafe | FfiPhantom(_)) => r,
r => FfiUnsafeWrapper {
ty: ty.clone(),
reason: fluent::lint_improper_ctypes_fnptr_indirect_reason,
help: None,
wrapped: Box::new(r),
},
}
self.check_type_for_ffi(acc, ret_ty)
}
ty::Foreign(..) => FfiSafe,
@ -1481,7 +1278,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
&mut self,
ty: Ty<'tcx>,
sp: Span,
mut reasons: Vec<ImproperCTypesLayer<'tcx>>,
note: DiagMessage,
help: Option<DiagMessage>,
) {
let lint = match self.mode {
CItemKind::Declaration => IMPROPER_CTYPES,
@ -1491,17 +1289,21 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
CItemKind::Declaration => "block",
CItemKind::Definition => "fn",
};
for reason in reasons.iter_mut() {
reason.span_note = if let ty::Adt(def, _) = reason.ty.kind()
&& let Some(sp) = self.cx.tcx.hir().span_if_local(def.did())
{
Some(sp)
} else {
None
};
}
self.cx.emit_span_lint(lint, sp, ImproperCTypes { ty, desc, label: sp, reasons });
let span_note = if let ty::Adt(def, _) = ty.kind()
&& let Some(sp) = self.cx.tcx.hir().span_if_local(def.did())
{
Some(sp)
} else {
None
};
self.cx.emit_span_lint(lint, sp, ImproperCTypes {
ty,
desc,
label: sp,
help,
note,
span_note,
});
}
fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool {
@ -1530,13 +1332,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
.visit_with(&mut ProhibitOpaqueTypes)
.break_value()
{
self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer {
ty,
note: fluent::lint_improper_ctypes_opaque,
span_note: Some(sp),
help: None,
inner_ty: None,
}]);
self.emit_ffi_unsafe_type_lint(ty, sp, fluent::lint_improper_ctypes_opaque, None);
true
} else {
false
@ -1575,71 +1371,15 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
match self.check_type_for_ffi(&mut acc, ty) {
FfiResult::FfiSafe => {}
FfiResult::FfiPhantom(ty) => {
self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer {
self.emit_ffi_unsafe_type_lint(
ty,
note: fluent::lint_improper_ctypes_only_phantomdata,
span_note: None, // filled later
help: None,
inner_ty: None,
}]);
sp,
fluent::lint_improper_ctypes_only_phantomdata,
None,
);
}
FfiResult::FfiUnsafe { ty, reason, help } => {
self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer {
ty,
help,
note: reason,
span_note: None, // filled later
inner_ty: None,
}]);
}
ffir @ FfiResult::FfiUnsafeWrapper { .. } => {
let mut ffiresult_recursor = ControlFlow::Continue(&ffir);
let mut cimproper_layers: Vec<ImproperCTypesLayer<'tcx>> = vec![];
// this whole while block converts the arbitrarily-deep
// FfiResult stack to an ImproperCTypesLayer Vec
while let ControlFlow::Continue(ref ffir_rec) = ffiresult_recursor {
match ffir_rec {
FfiResult::FfiPhantom(ty) => {
if let Some(layer) = cimproper_layers.last_mut() {
layer.inner_ty = Some(ty.clone());
}
cimproper_layers.push(ImproperCTypesLayer {
ty: ty.clone(),
inner_ty: None,
help: None,
note: fluent::lint_improper_ctypes_only_phantomdata,
span_note: None, // filled later
});
ffiresult_recursor = ControlFlow::Break(());
}
FfiResult::FfiUnsafe { ty, reason, help }
| FfiResult::FfiUnsafeWrapper { ty, reason, help, .. } => {
if let Some(layer) = cimproper_layers.last_mut() {
layer.inner_ty = Some(ty.clone());
}
cimproper_layers.push(ImproperCTypesLayer {
ty: ty.clone(),
inner_ty: None,
help: help.clone(),
note: reason.clone(),
span_note: None, // filled later
});
if let FfiResult::FfiUnsafeWrapper { wrapped, .. } = ffir_rec {
ffiresult_recursor = ControlFlow::Continue(wrapped.as_ref());
} else {
ffiresult_recursor = ControlFlow::Break(());
}
}
FfiResult::FfiSafe => {
bug!("malformed FfiResult stack: it should be unsafe all the way down")
}
};
}
// should always have at least one type
let last_ty = cimproper_layers.last().unwrap().ty.clone();
self.emit_ffi_unsafe_type_lint(last_ty, sp, cimproper_layers);
self.emit_ffi_unsafe_type_lint(ty, sp, reason, help);
}
}
}

View file

@ -1535,7 +1535,7 @@ extern "C" LLVMTypeKind LLVMRustGetTypeKind(LLVMTypeRef Ty) {
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(SMDiagnostic, LLVMSMDiagnosticRef)
extern "C" LLVMSMDiagnosticRef LLVMRustGetSMDiagnostic(LLVMDiagnosticInfoRef DI,
unsigned *Cookie) {
uint64_t *Cookie) {
llvm::DiagnosticInfoSrcMgr *SM =
static_cast<llvm::DiagnosticInfoSrcMgr *>(unwrap(DI));
*Cookie = SM->getLocCookie();

View file

@ -1104,6 +1104,7 @@ impl<'a> CrateMetadataRef<'a> {
name: self.item_name(did.index),
vis: self.get_visibility(did.index),
safety: self.get_safety(did.index),
value: self.get_default_field(did.index),
})
.collect(),
adt_kind,
@ -1169,6 +1170,10 @@ impl<'a> CrateMetadataRef<'a> {
self.root.tables.safety.get(self, id).unwrap_or_else(|| self.missing("safety", id))
}
fn get_default_field(self, id: DefIndex) -> Option<DefId> {
self.root.tables.default_fields.get(self, id).map(|d| d.decode(self))
}
fn get_trait_item_def_id(self, id: DefIndex) -> Option<DefId> {
self.root.tables.trait_item_def_id.get(self, id).map(|d| d.decode_from_cdata(self))
}

View file

@ -1401,6 +1401,13 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
continue;
}
if def_kind == DefKind::Field
&& let hir::Node::Field(field) = tcx.hir_node_by_def_id(local_id)
&& let Some(anon) = field.default
{
record!(self.tables.default_fields[def_id] <- anon.def_id.to_def_id());
}
if should_encode_span(def_kind) {
let def_span = tcx.def_span(local_id);
record!(self.tables.def_span[def_id] <- def_span);

View file

@ -450,6 +450,7 @@ define_tables! {
trait_def: Table<DefIndex, LazyValue<ty::TraitDef>>,
trait_item_def_id: Table<DefIndex, RawDefId>,
expn_that_defined: Table<DefIndex, LazyValue<ExpnId>>,
default_fields: Table<DefIndex, LazyValue<DefId>>,
params_in_repr: Table<DefIndex, LazyValue<BitSet<u32>>>,
repr_options: Table<DefIndex, LazyValue<ReprOptions>>,
// `def_keys` and `def_path_hashes` represent a lazy version of a

View file

@ -8,6 +8,7 @@ use rustc_session::config::RemapPathScopeComponents;
use rustc_span::{DUMMY_SP, Span};
use rustc_type_ir::visit::TypeVisitableExt;
use super::interpret::ReportedErrorInfo;
use crate::mir::interpret::{AllocId, ConstAllocation, ErrorHandled, Scalar, alloc_range};
use crate::mir::{Promoted, pretty_print_const_value};
use crate::ty::print::{pretty_print_const, with_no_trimmed_paths};
@ -331,7 +332,10 @@ impl<'tcx> Const<'tcx> {
ConstKind::Expr(_) => {
bug!("Normalization of `ty::ConstKind::Expr` is unimplemented")
}
_ => Err(tcx.dcx().delayed_bug("Unevaluated `ty::Const` in MIR body").into()),
_ => Err(ReportedErrorInfo::non_const_eval_error(
tcx.dcx().delayed_bug("Unevaluated `ty::Const` in MIR body"),
)
.into()),
}
}
Const::Unevaluated(uneval, _) => {

View file

@ -3,6 +3,7 @@
use std::fmt::{self, Debug, Formatter};
use rustc_index::IndexVec;
use rustc_index::bit_set::BitSet;
use rustc_macros::{HashStable, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable};
use rustc_span::Span;
@ -310,3 +311,41 @@ pub struct MCDCDecisionSpan {
pub decision_depth: u16,
pub num_conditions: usize,
}
/// Summarizes coverage IDs inserted by the `InstrumentCoverage` MIR pass
/// (for compiler option `-Cinstrument-coverage`), after MIR optimizations
/// have had a chance to potentially remove some of them.
///
/// Used by the `coverage_ids_info` query.
#[derive(Clone, TyEncodable, TyDecodable, Debug, HashStable)]
pub struct CoverageIdsInfo {
pub counters_seen: BitSet<CounterId>,
pub zero_expressions: BitSet<ExpressionId>,
}
impl CoverageIdsInfo {
/// Coverage codegen needs to know how many coverage counters are ever
/// incremented within a function, so that it can set the `num-counters`
/// argument of the `llvm.instrprof.increment` intrinsic.
///
/// This may be less than the highest counter ID emitted by the
/// InstrumentCoverage MIR pass, if the highest-numbered counter increments
/// were removed by MIR optimizations.
pub fn num_counters_after_mir_opts(&self) -> u32 {
// FIXME(Zalathar): Currently this treats an unused counter as "used"
// if its ID is less than that of the highest counter that really is
// used. Fixing this would require adding a renumbering step somewhere.
self.counters_seen.last_set_in(..).map_or(0, |max| max.as_u32() + 1)
}
/// Returns `true` if the given term is known to have a value of zero, taking
/// into account knowledge of which counters are unused and which expressions
/// are always zero.
pub fn is_zero_term(&self, term: CovTerm) -> bool {
match term {
CovTerm::Zero => true,
CovTerm::Counter(id) => !self.counters_seen.contains(id),
CovTerm::Expression(id) => self.zero_expressions.contains(id),
}
}
}

View file

@ -28,10 +28,10 @@ pub enum ErrorHandled {
TooGeneric(Span),
}
impl From<ErrorGuaranteed> for ErrorHandled {
impl From<ReportedErrorInfo> for ErrorHandled {
#[inline]
fn from(error: ErrorGuaranteed) -> ErrorHandled {
ErrorHandled::Reported(error.into(), DUMMY_SP)
fn from(error: ReportedErrorInfo) -> ErrorHandled {
ErrorHandled::Reported(error, DUMMY_SP)
}
}
@ -64,6 +64,20 @@ pub struct ReportedErrorInfo {
}
impl ReportedErrorInfo {
#[inline]
pub fn const_eval_error(error: ErrorGuaranteed) -> ReportedErrorInfo {
ReportedErrorInfo { allowed_in_infallible: false, error }
}
/// Use this when the error that led to this is *not* a const-eval error
/// (e.g., a layout or type checking error).
#[inline]
pub fn non_const_eval_error(error: ErrorGuaranteed) -> ReportedErrorInfo {
ReportedErrorInfo { allowed_in_infallible: true, error }
}
/// Use this when the error that led to this *is* a const-eval error, but
/// we do allow it to occur in infallible constants (e.g., resource exhaustion).
#[inline]
pub fn allowed_in_infallible(error: ErrorGuaranteed) -> ReportedErrorInfo {
ReportedErrorInfo { allowed_in_infallible: true, error }
@ -74,13 +88,6 @@ impl ReportedErrorInfo {
}
}
impl From<ErrorGuaranteed> for ReportedErrorInfo {
#[inline]
fn from(error: ErrorGuaranteed) -> ReportedErrorInfo {
ReportedErrorInfo { allowed_in_infallible: false, error }
}
}
impl Into<ErrorGuaranteed> for ReportedErrorInfo {
#[inline]
fn into(self) -> ErrorGuaranteed {
@ -180,12 +187,6 @@ fn print_backtrace(backtrace: &Backtrace) {
eprintln!("\n\nAn error occurred in the MIR interpreter:\n{backtrace}");
}
impl From<ErrorGuaranteed> for InterpErrorInfo<'_> {
fn from(err: ErrorGuaranteed) -> Self {
InterpErrorKind::InvalidProgram(InvalidProgramInfo::AlreadyReported(err.into())).into()
}
}
impl From<ErrorHandled> for InterpErrorInfo<'_> {
fn from(err: ErrorHandled) -> Self {
InterpErrorKind::InvalidProgram(match err {

View file

@ -6,6 +6,7 @@ use tracing::{debug, instrument};
use super::{
ErrorHandled, EvalToAllocationRawResult, EvalToConstValueResult, EvalToValTreeResult, GlobalId,
ReportedErrorInfo,
};
use crate::mir;
use crate::query::TyCtxtEnsure;
@ -81,7 +82,9 @@ impl<'tcx> TyCtxt<'tcx> {
// For errors during resolution, we deliberately do not point at the usage site of the constant,
// since for these errors the place the constant is used shouldn't matter.
Ok(None) => Err(ErrorHandled::TooGeneric(DUMMY_SP)),
Err(err) => Err(ErrorHandled::Reported(err.into(), DUMMY_SP)),
Err(err) => {
Err(ErrorHandled::Reported(ReportedErrorInfo::non_const_eval_error(err), DUMMY_SP))
}
}
}
@ -138,7 +141,9 @@ impl<'tcx> TyCtxt<'tcx> {
// For errors during resolution, we deliberately do not point at the usage site of the constant,
// since for these errors the place the constant is used shouldn't matter.
Ok(None) => Err(ErrorHandled::TooGeneric(DUMMY_SP)),
Err(err) => Err(ErrorHandled::Reported(err.into(), DUMMY_SP)),
Err(err) => {
Err(ErrorHandled::Reported(ReportedErrorInfo::non_const_eval_error(err), DUMMY_SP))
}
}
}

View file

@ -19,6 +19,7 @@ use rustc_target::spec::SymbolVisibility;
use tracing::debug;
use crate::dep_graph::{DepNode, WorkProduct, WorkProductId};
use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use crate::ty::{GenericArgs, Instance, InstanceKind, SymbolName, TyCtxt};
/// Describes how a monomorphization will be instantiated in object files.
@ -104,7 +105,9 @@ impl<'tcx> MonoItem<'tcx> {
let entry_def_id = tcx.entry_fn(()).map(|(id, _)| id);
// If this function isn't inlined or otherwise has an extern
// indicator, then we'll be creating a globally shared version.
if tcx.codegen_fn_attrs(instance.def_id()).contains_extern_indicator()
let codegen_fn_attrs = tcx.codegen_fn_attrs(instance.def_id());
if codegen_fn_attrs.contains_extern_indicator()
|| codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NAKED)
|| !instance.def.generates_cgu_internal_copy(tcx)
|| Some(instance.def_id()) == entry_def_id
{

View file

@ -8,7 +8,7 @@ use rustc_abi::{FieldIdx, VariantIdx};
use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::ErrorGuaranteed;
use rustc_hir::def_id::LocalDefId;
use rustc_index::bit_set::{BitMatrix, BitSet};
use rustc_index::bit_set::BitMatrix;
use rustc_index::{Idx, IndexVec};
use rustc_macros::{HashStable, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable};
use rustc_span::Span;
@ -16,7 +16,6 @@ use rustc_span::symbol::Symbol;
use smallvec::SmallVec;
use super::{ConstValue, SourceInfo};
use crate::mir;
use crate::ty::fold::fold_regions;
use crate::ty::{self, CoroutineArgsExt, OpaqueHiddenType, Ty, TyCtxt};
@ -351,30 +350,3 @@ pub struct DestructuredConstant<'tcx> {
pub variant: Option<VariantIdx>,
pub fields: &'tcx [(ConstValue<'tcx>, Ty<'tcx>)],
}
/// Summarizes coverage IDs inserted by the `InstrumentCoverage` MIR pass
/// (for compiler option `-Cinstrument-coverage`), after MIR optimizations
/// have had a chance to potentially remove some of them.
///
/// Used by the `coverage_ids_info` query.
#[derive(Clone, TyEncodable, TyDecodable, Debug, HashStable)]
pub struct CoverageIdsInfo {
pub counters_seen: BitSet<mir::coverage::CounterId>,
pub expressions_seen: BitSet<mir::coverage::ExpressionId>,
}
impl CoverageIdsInfo {
/// Coverage codegen needs to know how many coverage counters are ever
/// incremented within a function, so that it can set the `num-counters`
/// argument of the `llvm.instrprof.increment` intrinsic.
///
/// This may be less than the highest counter ID emitted by the
/// InstrumentCoverage MIR pass, if the highest-numbered counter increments
/// were removed by MIR optimizations.
pub fn num_counters_after_mir_opts(&self) -> u32 {
// FIXME(Zalathar): Currently this treats an unused counter as "used"
// if its ID is less than that of the highest counter that really is
// used. Fixing this would require adding a renumbering step somewhere.
self.counters_seen.last_set_in(..).map_or(0, |max| max.as_u32() + 1)
}
}

View file

@ -581,7 +581,7 @@ rustc_queries! {
/// Summarizes coverage IDs inserted by the `InstrumentCoverage` MIR pass
/// (for compiler option `-Cinstrument-coverage`), after MIR optimizations
/// have had a chance to potentially remove some of them.
query coverage_ids_info(key: ty::InstanceKind<'tcx>) -> &'tcx mir::CoverageIdsInfo {
query coverage_ids_info(key: ty::InstanceKind<'tcx>) -> &'tcx mir::coverage::CoverageIdsInfo {
desc { |tcx| "retrieving coverage IDs info from MIR for `{}`", tcx.def_path_str(key.def_id()) }
arena_cache
}

View file

@ -158,8 +158,21 @@ pub struct AdtExpr<'tcx> {
pub user_ty: UserTy<'tcx>,
pub fields: Box<[FieldExpr]>,
/// The base, e.g. `Foo {x: 1, .. base}`.
pub base: Option<FruInfo<'tcx>>,
/// The base, e.g. `Foo {x: 1, ..base}`.
pub base: AdtExprBase<'tcx>,
}
#[derive(Clone, Debug, HashStable)]
pub enum AdtExprBase<'tcx> {
/// A struct expression where all the fields are explicitly enumerated: `Foo { a, b }`.
None,
/// A struct expression with a "base", an expression of the same type as the outer struct that
/// will be used to populate any fields not explicitly mentioned: `Foo { ..base }`
Base(FruInfo<'tcx>),
/// A struct expression with a `..` tail but no "base" expression. The values from the struct
/// fields' default values will be used to populate any fields not explicitly mentioned:
/// `Foo { .. }`.
DefaultFields(Box<[Ty<'tcx>]>),
}
#[derive(Clone, Debug, HashStable)]

View file

@ -2,6 +2,7 @@ use super::{
AdtExpr, Arm, Block, ClosureExpr, Expr, ExprKind, InlineAsmExpr, InlineAsmOperand, Pat,
PatKind, Stmt, StmtKind, Thir,
};
use crate::thir::AdtExprBase;
pub trait Visitor<'thir, 'tcx: 'thir>: Sized {
fn thir(&self) -> &'thir Thir<'tcx>;
@ -127,7 +128,7 @@ pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>(
for field in &**fields {
visitor.visit_expr(&visitor.thir()[field.expr]);
}
if let Some(base) = base {
if let AdtExprBase::Base(base) = base {
visitor.visit_expr(&visitor.thir()[base.base]);
}
}

View file

@ -1479,7 +1479,7 @@ impl<'tcx> TyCtxt<'tcx> {
self.mk_adt_def_from_data(ty::AdtDefData::new(self, did, kind, variants, repr))
}
/// Allocates a read-only byte or string literal for `mir::interpret`.
/// Allocates a read-only byte or string literal for `mir::interpret` with alignment 1.
/// Returns the same `AllocId` if called again with the same bytes.
pub fn allocate_bytes_dedup(self, bytes: &[u8], salt: usize) -> interpret::AllocId {
// Create an allocation that just contains these bytes.

View file

@ -1364,6 +1364,7 @@ pub struct FieldDef {
pub name: Symbol,
pub vis: Visibility<DefId>,
pub safety: hir::Safety,
pub value: Option<DefId>,
}
impl PartialEq for FieldDef {
@ -1376,9 +1377,9 @@ impl PartialEq for FieldDef {
// of `FieldDef` changes, a compile-error will be produced, reminding
// us to revisit this assumption.
let Self { did: lhs_did, name: _, vis: _, safety: _ } = &self;
let Self { did: lhs_did, name: _, vis: _, safety: _, value: _ } = &self;
let Self { did: rhs_did, name: _, vis: _, safety: _ } = other;
let Self { did: rhs_did, name: _, vis: _, safety: _, value: _ } = other;
let res = lhs_did == rhs_did;
@ -1405,7 +1406,7 @@ impl Hash for FieldDef {
// of `FieldDef` changes, a compile-error will be produced, reminding
// us to revisit this assumption.
let Self { did, name: _, vis: _, safety: _ } = &self;
let Self { did, name: _, vis: _, safety: _, value: _ } = &self;
did.hash(s)
}

View file

@ -1020,7 +1020,7 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write {
self.insert_trait_and_projection(
trait_ref,
Some((proj.projection_def_id(), proj.term())),
Some((proj.item_def_id(), proj.term())),
&mut traits,
&mut fn_traits,
);

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