Auto merge of #140043 - ChrisDenton:rollup-vwf0s9j, r=ChrisDenton

Rollup of 8 pull requests

Successful merges:

 - #138934 (support config extensions)
 - #139091 (Rewrite on_unimplemented format string parser.)
 - #139753 (Make `#[naked]` an unsafe attribute)
 - #139762 (Don't assemble non-env/bound candidates if projection is rigid)
 - #139834 (Don't canonicalize crate paths)
 - #139868 (Move `pal::env` to `std::sys::env_consts`)
 - #139978 (Add citool command for generating a test dashboard)
 - #139995 (Clean UI tests 4 of n)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2025-04-20 02:08:02 +00:00
commit 49e5e4e3a5
138 changed files with 2861 additions and 1624 deletions

View file

@ -19,6 +19,14 @@
# Note that this has no default value (x.py uses the defaults in `bootstrap.example.toml`). # Note that this has no default value (x.py uses the defaults in `bootstrap.example.toml`).
#profile = <none> #profile = <none>
# Inherits configuration values from different configuration files (a.k.a. config extensions).
# Supports absolute paths, and uses the current directory (where the bootstrap was invoked)
# as the base if the given path is not absolute.
#
# The overriding logic follows a right-to-left order. For example, in `include = ["a.toml", "b.toml"]`,
# extension `b.toml` overrides `a.toml`. Also, parent extensions always overrides the inner ones.
#include = []
# Keeps track of major changes made to this configuration. # Keeps track of major changes made to this configuration.
# #
# This value also represents ID of the PR that caused major changes. Meaning, # This value also represents ID of the PR that caused major changes. Meaning,

View file

@ -247,9 +247,9 @@ builtin_macros_multiple_defaults = multiple declared defaults
.suggestion = make `{$ident}` default .suggestion = make `{$ident}` default
builtin_macros_naked_functions_testing_attribute = builtin_macros_naked_functions_testing_attribute =
cannot use `#[naked]` with testing attributes cannot use `#[unsafe(naked)]` with testing attributes
.label = function marked with testing attribute here .label = function marked with testing attribute here
.naked_attribute = `#[naked]` is incompatible with testing attributes .naked_attribute = `#[unsafe(naked)]` is incompatible with testing attributes
builtin_macros_no_default_variant = `#[derive(Default)]` on enum with no `#[default]` builtin_macros_no_default_variant = `#[derive(Default)]` on enum with no `#[default]`
.label = this enum needs a unit variant marked with `#[default]` .label = this enum needs a unit variant marked with `#[default]`

View file

@ -387,11 +387,9 @@ global_asm! {
} }
#[cfg(all(not(jit), target_arch = "x86_64"))] #[cfg(all(not(jit), target_arch = "x86_64"))]
#[naked] #[unsafe(naked)]
extern "C" fn naked_test() { extern "C" fn naked_test() {
unsafe { naked_asm!("ret")
naked_asm!("ret");
}
} }
#[repr(C)] #[repr(C)]

View file

@ -11,7 +11,7 @@ Erroneous code example:
```compile_fail,E0736 ```compile_fail,E0736
#[inline] #[inline]
#[naked] #[unsafe(naked)]
fn foo() {} fn foo() {}
``` ```

View file

@ -5,7 +5,7 @@ Erroneous code example:
```compile_fail,E0787 ```compile_fail,E0787
#![feature(naked_functions)] #![feature(naked_functions)]
#[naked] #[unsafe(naked)]
pub extern "C" fn f() -> u32 { pub extern "C" fn f() -> u32 {
42 42
} }

View file

@ -517,7 +517,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
// Linking: // Linking:
gated!( gated!(
naked, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, unsafe naked, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No,
naked_functions, experimental!(naked) naked_functions, experimental!(naked)
), ),

View file

@ -427,12 +427,21 @@ impl<'a> CrateLocator<'a> {
let (rlibs, rmetas, dylibs) = let (rlibs, rmetas, dylibs) =
candidates.entry(hash.to_string()).or_default(); candidates.entry(hash.to_string()).or_default();
let path = {
try_canonicalize(&spf.path).unwrap_or_else(|_| spf.path.to_path_buf()); // As a perforamnce optimisation we canonicalize the path and skip
if seen_paths.contains(&path) { // ones we've already seeen. This allows us to ignore crates
continue; // we know are exactual equal to ones we've already found.
}; // Going to the same crate through different symlinks does not change the result.
seen_paths.insert(path.clone()); let path = try_canonicalize(&spf.path)
.unwrap_or_else(|_| spf.path.to_path_buf());
if seen_paths.contains(&path) {
continue;
};
seen_paths.insert(path);
}
// Use the original path (potentially with unresolved symlinks),
// filesystem code should not care, but this is nicer for diagnostics.
let path = spf.path.to_path_buf();
match kind { match kind {
CrateFlavor::Rlib => rlibs.insert(path, search_path.kind), CrateFlavor::Rlib => rlibs.insert(path, search_path.kind),
CrateFlavor::Rmeta => rmetas.insert(path, search_path.kind), CrateFlavor::Rmeta => rmetas.insert(path, search_path.kind),

View file

@ -564,13 +564,17 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
} }
} }
ExprKind::InlineAsm(box InlineAsmExpr { ExprKind::InlineAsm(box InlineAsmExpr {
asm_macro: AsmMacro::Asm | AsmMacro::NakedAsm, asm_macro: asm_macro @ (AsmMacro::Asm | AsmMacro::NakedAsm),
ref operands, ref operands,
template: _, template: _,
options: _, options: _,
line_spans: _, line_spans: _,
}) => { }) => {
self.requires_unsafe(expr.span, UseOfInlineAssembly); // The `naked` attribute and the `naked_asm!` block form one atomic unit of
// unsafety, and `naked_asm!` does not itself need to be wrapped in an unsafe block.
if let AsmMacro::Asm = asm_macro {
self.requires_unsafe(expr.span, UseOfInlineAssembly);
}
// For inline asm, do not use `walk_expr`, since we want to handle the label block // For inline asm, do not use `walk_expr`, since we want to handle the label block
// specially. // specially.

View file

@ -288,6 +288,21 @@ where
) -> Vec<Candidate<I>>; ) -> Vec<Candidate<I>>;
} }
/// Allows callers of `assemble_and_evaluate_candidates` to choose whether to limit
/// candidate assembly to param-env and alias-bound candidates.
///
/// On top of being a micro-optimization, as it avoids doing unnecessary work when
/// a param-env trait bound candidate shadows impls for normalization, this is also
/// required to prevent query cycles due to RPITIT inference. See the issue at:
/// <https://github.com/rust-lang/trait-system-refactor-initiative/issues/173>.
pub(super) enum AssembleCandidatesFrom {
All,
/// Only assemble candidates from the environment and alias bounds, ignoring
/// user-written and built-in impls. We only expect `ParamEnv` and `AliasBound`
/// candidates to be assembled.
EnvAndBounds,
}
impl<D, I> EvalCtxt<'_, D> impl<D, I> EvalCtxt<'_, D>
where where
D: SolverDelegate<Interner = I>, D: SolverDelegate<Interner = I>,
@ -296,6 +311,7 @@ where
pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<D>>( pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<D>>(
&mut self, &mut self,
goal: Goal<I, G>, goal: Goal<I, G>,
assemble_from: AssembleCandidatesFrom,
) -> Vec<Candidate<I>> { ) -> Vec<Candidate<I>> {
let Ok(normalized_self_ty) = let Ok(normalized_self_ty) =
self.structurally_normalize_ty(goal.param_env, goal.predicate.self_ty()) self.structurally_normalize_ty(goal.param_env, goal.predicate.self_ty())
@ -322,16 +338,18 @@ where
} }
} }
self.assemble_impl_candidates(goal, &mut candidates);
self.assemble_builtin_impl_candidates(goal, &mut candidates);
self.assemble_alias_bound_candidates(goal, &mut candidates); self.assemble_alias_bound_candidates(goal, &mut candidates);
self.assemble_object_bound_candidates(goal, &mut candidates);
self.assemble_param_env_candidates(goal, &mut candidates); self.assemble_param_env_candidates(goal, &mut candidates);
match assemble_from {
AssembleCandidatesFrom::All => {
self.assemble_impl_candidates(goal, &mut candidates);
self.assemble_builtin_impl_candidates(goal, &mut candidates);
self.assemble_object_bound_candidates(goal, &mut candidates);
}
AssembleCandidatesFrom::EnvAndBounds => {}
}
candidates candidates
} }
@ -754,6 +772,9 @@ where
}) })
} }
/// Assemble and merge candidates for goals which are related to an underlying trait
/// goal. Right now, this is normalizes-to and host effect goals.
///
/// We sadly can't simply take all possible candidates for normalization goals /// We sadly can't simply take all possible candidates for normalization goals
/// and check whether they result in the same constraints. We want to make sure /// and check whether they result in the same constraints. We want to make sure
/// that trying to normalize an alias doesn't result in constraints which aren't /// that trying to normalize an alias doesn't result in constraints which aren't
@ -782,47 +803,44 @@ where
/// ///
/// See trait-system-refactor-initiative#124 for more details. /// See trait-system-refactor-initiative#124 for more details.
#[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)] #[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
pub(super) fn merge_candidates( pub(super) fn assemble_and_merge_candidates<G: GoalKind<D>>(
&mut self, &mut self,
proven_via: Option<TraitGoalProvenVia>, proven_via: Option<TraitGoalProvenVia>,
candidates: Vec<Candidate<I>>, goal: Goal<I, G>,
inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>, inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> QueryResult<I> { ) -> QueryResult<I> {
let Some(proven_via) = proven_via else { let Some(proven_via) = proven_via else {
// We don't care about overflow. If proving the trait goal overflowed, then // We don't care about overflow. If proving the trait goal overflowed, then
// it's enough to report an overflow error for that, we don't also have to // it's enough to report an overflow error for that, we don't also have to
// overflow during normalization. // overflow during normalization.
return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Ambiguity)); //
// We use `forced_ambiguity` here over `make_ambiguous_response_no_constraints`
// because the former will also record a built-in candidate in the inspector.
return self.forced_ambiguity(MaybeCause::Ambiguity).map(|cand| cand.result);
}; };
match proven_via { match proven_via {
TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound => { TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound => {
let mut considered_candidates = Vec::new();
considered_candidates.extend(
candidates
.iter()
.filter(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
.map(|c| c.result),
);
// Even when a trait bound has been proven using a where-bound, we // Even when a trait bound has been proven using a where-bound, we
// still need to consider alias-bounds for normalization, see // still need to consider alias-bounds for normalization, see
// tests/ui/next-solver/alias-bound-shadowed-by-env.rs. // `tests/ui/next-solver/alias-bound-shadowed-by-env.rs`.
// let candidates_from_env_and_bounds: Vec<_> = self
.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::EnvAndBounds);
// We still need to prefer where-bounds over alias-bounds however. // We still need to prefer where-bounds over alias-bounds however.
// See tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs. // See `tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs`.
// let mut considered_candidates: Vec<_> = if candidates_from_env_and_bounds
// FIXME(const_trait_impl): should this behavior also be used by .iter()
// constness checking. Doing so is *at least theoretically* breaking, .any(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
// see github.com/rust-lang/rust/issues/133044#issuecomment-2500709754 {
if considered_candidates.is_empty() { candidates_from_env_and_bounds
considered_candidates.extend( .into_iter()
candidates .filter(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
.iter() .map(|c| c.result)
.filter(|c| matches!(c.source, CandidateSource::AliasBound)) .collect()
.map(|c| c.result), } else {
); candidates_from_env_and_bounds.into_iter().map(|c| c.result).collect()
} };
// If the trait goal has been proven by using the environment, we want to treat // If the trait goal has been proven by using the environment, we want to treat
// aliases as rigid if there are no applicable projection bounds in the environment. // aliases as rigid if there are no applicable projection bounds in the environment.
@ -839,6 +857,9 @@ where
} }
} }
TraitGoalProvenVia::Misc => { TraitGoalProvenVia::Misc => {
let candidates =
self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);
// Prefer "orphaned" param-env normalization predicates, which are used // Prefer "orphaned" param-env normalization predicates, which are used
// (for example, and ideally only) when proving item bounds for an impl. // (for example, and ideally only) when proving item bounds for an impl.
let candidates_from_env: Vec<_> = candidates let candidates_from_env: Vec<_> = candidates

View file

@ -399,12 +399,11 @@ where
&mut self, &mut self,
goal: Goal<I, ty::HostEffectPredicate<I>>, goal: Goal<I, ty::HostEffectPredicate<I>>,
) -> QueryResult<I> { ) -> QueryResult<I> {
let candidates = self.assemble_and_evaluate_candidates(goal);
let (_, proven_via) = self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| { let (_, proven_via) = self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| {
let trait_goal: Goal<I, ty::TraitPredicate<I>> = let trait_goal: Goal<I, ty::TraitPredicate<I>> =
goal.with(ecx.cx(), goal.predicate.trait_ref); goal.with(ecx.cx(), goal.predicate.trait_ref);
ecx.compute_trait_goal(trait_goal) ecx.compute_trait_goal(trait_goal)
})?; })?;
self.merge_candidates(proven_via, candidates, |_ecx| Err(NoSolution)) self.assemble_and_merge_candidates(proven_via, goal, |_ecx| Err(NoSolution))
} }
} }

View file

@ -32,14 +32,13 @@ where
let cx = self.cx(); let cx = self.cx();
match goal.predicate.alias.kind(cx) { match goal.predicate.alias.kind(cx) {
ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => { ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => {
let candidates = self.assemble_and_evaluate_candidates(goal);
let trait_ref = goal.predicate.alias.trait_ref(cx); let trait_ref = goal.predicate.alias.trait_ref(cx);
let (_, proven_via) = let (_, proven_via) =
self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| { self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| {
let trait_goal: Goal<I, ty::TraitPredicate<I>> = goal.with(cx, trait_ref); let trait_goal: Goal<I, ty::TraitPredicate<I>> = goal.with(cx, trait_ref);
ecx.compute_trait_goal(trait_goal) ecx.compute_trait_goal(trait_goal)
})?; })?;
self.merge_candidates(proven_via, candidates, |ecx| { self.assemble_and_merge_candidates(proven_via, goal, |ecx| {
ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| { ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| {
this.structurally_instantiate_normalizes_to_term( this.structurally_instantiate_normalizes_to_term(
goal, goal,

View file

@ -13,7 +13,7 @@ use tracing::{instrument, trace};
use crate::delegate::SolverDelegate; use crate::delegate::SolverDelegate;
use crate::solve::assembly::structural_traits::{self, AsyncCallableRelevantTypes}; use crate::solve::assembly::structural_traits::{self, AsyncCallableRelevantTypes};
use crate::solve::assembly::{self, Candidate}; use crate::solve::assembly::{self, AssembleCandidatesFrom, Candidate};
use crate::solve::inspect::ProbeKind; use crate::solve::inspect::ProbeKind;
use crate::solve::{ use crate::solve::{
BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause, BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause,
@ -1365,7 +1365,7 @@ where
&mut self, &mut self,
goal: Goal<I, TraitPredicate<I>>, goal: Goal<I, TraitPredicate<I>>,
) -> Result<(CanonicalResponse<I>, Option<TraitGoalProvenVia>), NoSolution> { ) -> Result<(CanonicalResponse<I>, Option<TraitGoalProvenVia>), NoSolution> {
let candidates = self.assemble_and_evaluate_candidates(goal); let candidates = self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);
self.merge_trait_candidates(goal, candidates) self.merge_trait_candidates(goal, candidates)
} }
} }

View file

@ -194,12 +194,6 @@ pub fn check_attribute_safety(psess: &ParseSess, safety: AttributeSafety, attr:
} }
} }
} else if let Safety::Unsafe(unsafe_span) = attr_item.unsafety { } else if let Safety::Unsafe(unsafe_span) = attr_item.unsafety {
// Allow (but don't require) `#[unsafe(naked)]` so that compiler-builtins can upgrade to it.
// FIXME(#139797): remove this special case when compiler-builtins has upgraded.
if attr.has_name(sym::naked) {
return;
}
psess.dcx().emit_err(errors::InvalidAttrUnsafe { psess.dcx().emit_err(errors::InvalidAttrUnsafe {
span: unsafe_span, span: unsafe_span,
name: attr_item.path.clone(), name: attr_item.path.clone(),

View file

@ -508,7 +508,7 @@ passes_must_use_no_effect =
`#[must_use]` has no effect when applied to {$article} {$target} `#[must_use]` has no effect when applied to {$article} {$target}
passes_naked_asm_outside_naked_fn = passes_naked_asm_outside_naked_fn =
the `naked_asm!` macro can only be used in functions marked with `#[naked]` the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]`
passes_naked_functions_asm_block = passes_naked_functions_asm_block =
naked functions must contain a single `naked_asm!` invocation naked functions must contain a single `naked_asm!` invocation
@ -516,9 +516,9 @@ passes_naked_functions_asm_block =
.label_non_asm = not allowed in naked functions .label_non_asm = not allowed in naked functions
passes_naked_functions_incompatible_attribute = passes_naked_functions_incompatible_attribute =
attribute incompatible with `#[naked]` attribute incompatible with `#[unsafe(naked)]`
.label = the `{$attr}` attribute is incompatible with `#[naked]` .label = the `{$attr}` attribute is incompatible with `#[unsafe(naked)]`
.naked_attribute = function marked with `#[naked]` here .naked_attribute = function marked with `#[unsafe(naked)]` here
passes_naked_functions_must_naked_asm = passes_naked_functions_must_naked_asm =
the `asm!` macro is not allowed in naked functions the `asm!` macro is not allowed in naked functions

View file

@ -1232,6 +1232,25 @@ impl DesugaringKind {
DesugaringKind::PatTyRange => "pattern type", DesugaringKind::PatTyRange => "pattern type",
} }
} }
/// For use with `rustc_unimplemented` to support conditions
/// like `from_desugaring = "QuestionMark"`
pub fn matches(&self, value: &str) -> bool {
match self {
DesugaringKind::CondTemporary => value == "CondTemporary",
DesugaringKind::Async => value == "Async",
DesugaringKind::Await => value == "Await",
DesugaringKind::QuestionMark => value == "QuestionMark",
DesugaringKind::TryBlock => value == "TryBlock",
DesugaringKind::YeetExpr => value == "YeetExpr",
DesugaringKind::OpaqueTy => value == "OpaqueTy",
DesugaringKind::ForLoop => value == "ForLoop",
DesugaringKind::WhileLoop => value == "WhileLoop",
DesugaringKind::BoundModifier => value == "BoundModifier",
DesugaringKind::Contract => value == "Contract",
DesugaringKind::PatTyRange => value == "PatTyRange",
}
}
} }
#[derive(Default)] #[derive(Default)]

View file

@ -372,6 +372,7 @@ symbols! {
SyncUnsafeCell, SyncUnsafeCell,
T, T,
Target, Target,
This,
ToOwned, ToOwned,
ToString, ToString,
TokenStream, TokenStream,

View file

@ -1,12 +1,11 @@
use crate::spec::{LinkSelfContainedDefault, TargetOptions, base, crt_objects}; use crate::spec::{LinkSelfContainedDefault, TargetOptions, base, crt_objects};
pub(crate) fn opts() -> TargetOptions { pub(crate) fn opts() -> TargetOptions {
let mut base = base::linux::opts(); TargetOptions {
env: "musl".into(),
base.env = "musl".into(); pre_link_objects_self_contained: crt_objects::pre_musl_self_contained(),
base.pre_link_objects_self_contained = crt_objects::pre_musl_self_contained(); post_link_objects_self_contained: crt_objects::post_musl_self_contained(),
base.post_link_objects_self_contained = crt_objects::post_musl_self_contained(); link_self_contained: LinkSelfContainedDefault::InferredForMusl,
base.link_self_contained = LinkSelfContainedDefault::InferredForMusl; ..base::linux::opts()
}
base
} }

View file

@ -1,12 +1,11 @@
use crate::spec::{TargetOptions, TlsModel, base}; use crate::spec::{TargetOptions, TlsModel, base};
pub(crate) fn opts() -> TargetOptions { pub(crate) fn opts() -> TargetOptions {
let mut base = base::linux::opts(); TargetOptions {
env: "ohos".into(),
base.env = "ohos".into(); crt_static_default: false,
base.crt_static_default = false; tls_model: TlsModel::Emulated,
base.tls_model = TlsModel::Emulated; has_thread_local: false,
base.has_thread_local = false; ..base::linux::opts()
}
base
} }

View file

@ -2,6 +2,8 @@ pub mod ambiguity;
pub mod call_kind; pub mod call_kind;
mod fulfillment_errors; mod fulfillment_errors;
pub mod on_unimplemented; pub mod on_unimplemented;
pub mod on_unimplemented_condition;
pub mod on_unimplemented_format;
mod overflow; mod overflow;
pub mod suggestions; pub mod suggestions;

View file

@ -1,44 +1,31 @@
use std::iter; use std::iter;
use std::path::PathBuf; use std::path::PathBuf;
use rustc_ast::MetaItemInner; use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::codes::*; use rustc_errors::codes::*;
use rustc_errors::{ErrorGuaranteed, struct_span_code_err}; use rustc_errors::{ErrorGuaranteed, struct_span_code_err};
use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{AttrArgs, Attribute}; use rustc_hir::{AttrArgs, Attribute};
use rustc_macros::LintDiagnostic; use rustc_macros::LintDiagnostic;
use rustc_middle::bug; use rustc_middle::bug;
use rustc_middle::ty::print::PrintTraitRefExt as _; use rustc_middle::ty::print::PrintTraitRefExt;
use rustc_middle::ty::{self, GenericArgsRef, GenericParamDefKind, TyCtxt}; use rustc_middle::ty::{self, GenericArgsRef, GenericParamDef, GenericParamDefKind, TyCtxt};
use rustc_parse_format::{ParseMode, Parser, Piece, Position};
use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES; use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
use rustc_span::{Ident, Span, Symbol, kw, sym}; use rustc_span::{Span, Symbol, sym};
use tracing::{debug, info}; use tracing::{debug, info};
use {rustc_attr_parsing as attr, rustc_hir as hir}; use {rustc_attr_parsing as attr, rustc_hir as hir};
use super::{ObligationCauseCode, PredicateObligation}; use super::{ObligationCauseCode, PredicateObligation};
use crate::error_reporting::TypeErrCtxt; use crate::error_reporting::TypeErrCtxt;
use crate::error_reporting::traits::on_unimplemented_condition::{Condition, ConditionOptions};
use crate::error_reporting::traits::on_unimplemented_format::{
Ctx, FormatArgs, FormatString, FormatWarning,
};
use crate::errors::{ use crate::errors::{
EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented, EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented,
}; };
use crate::infer::InferCtxtExt; use crate::infer::InferCtxtExt;
/// The symbols which are always allowed in a format string
static ALLOWED_FORMAT_SYMBOLS: &[Symbol] = &[
kw::SelfUpper,
sym::ItemContext,
sym::from_desugaring,
sym::direct,
sym::cause,
sym::integral,
sym::integer_,
sym::float,
sym::_Self,
sym::crate_local,
sym::Trait,
];
impl<'tcx> TypeErrCtxt<'_, 'tcx> { impl<'tcx> TypeErrCtxt<'_, 'tcx> {
fn impl_similar_to( fn impl_similar_to(
&self, &self,
@ -121,86 +108,78 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
.unwrap_or_else(|| (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args)); .unwrap_or_else(|| (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args));
let trait_pred = trait_pred.skip_binder(); let trait_pred = trait_pred.skip_binder();
let mut flags = vec![]; let mut self_types = vec![];
let mut generic_args: Vec<(Symbol, String)> = vec![];
let mut crate_local = false;
// FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty): HIR is not present for RPITITs, // FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty): HIR is not present for RPITITs,
// but I guess we could synthesize one here. We don't see any errors that rely on // but I guess we could synthesize one here. We don't see any errors that rely on
// that yet, though. // that yet, though.
let enclosure = self.describe_enclosure(obligation.cause.body_id).map(|t| t.to_owned()); let item_context = self.describe_enclosure(obligation.cause.body_id).unwrap_or("");
flags.push((sym::ItemContext, enclosure));
match obligation.cause.code() { let direct = match obligation.cause.code() {
ObligationCauseCode::BuiltinDerived(..) ObligationCauseCode::BuiltinDerived(..)
| ObligationCauseCode::ImplDerived(..) | ObligationCauseCode::ImplDerived(..)
| ObligationCauseCode::WellFormedDerived(..) => {} | ObligationCauseCode::WellFormedDerived(..) => false,
_ => { _ => {
// this is a "direct", user-specified, rather than derived, // this is a "direct", user-specified, rather than derived,
// obligation. // obligation.
flags.push((sym::direct, None)); true
} }
} };
if let Some(k) = obligation.cause.span.desugaring_kind() { let from_desugaring = obligation.cause.span.desugaring_kind();
flags.push((sym::from_desugaring, None));
flags.push((sym::from_desugaring, Some(format!("{k:?}"))));
}
if let ObligationCauseCode::MainFunctionType = obligation.cause.code() { let cause = if let ObligationCauseCode::MainFunctionType = obligation.cause.code() {
flags.push((sym::cause, Some("MainFunctionType".to_string()))); Some("MainFunctionType".to_string())
} } else {
None
flags.push((sym::Trait, Some(trait_pred.trait_ref.print_trait_sugared().to_string()))); };
// Add all types without trimmed paths or visible paths, ensuring they end up with // Add all types without trimmed paths or visible paths, ensuring they end up with
// their "canonical" def path. // their "canonical" def path.
ty::print::with_no_trimmed_paths!(ty::print::with_no_visible_paths!({ ty::print::with_no_trimmed_paths!(ty::print::with_no_visible_paths!({
let generics = self.tcx.generics_of(def_id); let generics = self.tcx.generics_of(def_id);
let self_ty = trait_pred.self_ty(); let self_ty = trait_pred.self_ty();
// This is also included through the generics list as `Self`, self_types.push(self_ty.to_string());
// but the parser won't allow you to use it
flags.push((sym::_Self, Some(self_ty.to_string())));
if let Some(def) = self_ty.ty_adt_def() { if let Some(def) = self_ty.ty_adt_def() {
// We also want to be able to select self's original // We also want to be able to select self's original
// signature with no type arguments resolved // signature with no type arguments resolved
flags.push(( self_types.push(self.tcx.type_of(def.did()).instantiate_identity().to_string());
sym::_Self,
Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()),
));
} }
for param in generics.own_params.iter() { for GenericParamDef { name, kind, index, .. } in generics.own_params.iter() {
let value = match param.kind { let value = match kind {
GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
args[param.index as usize].to_string() args[*index as usize].to_string()
} }
GenericParamDefKind::Lifetime => continue, GenericParamDefKind::Lifetime => continue,
}; };
let name = param.name; generic_args.push((*name, value));
flags.push((name, Some(value)));
if let GenericParamDefKind::Type { .. } = param.kind { if let GenericParamDefKind::Type { .. } = kind {
let param_ty = args[param.index as usize].expect_ty(); let param_ty = args[*index as usize].expect_ty();
if let Some(def) = param_ty.ty_adt_def() { if let Some(def) = param_ty.ty_adt_def() {
// We also want to be able to select the parameter's // We also want to be able to select the parameter's
// original signature with no type arguments resolved // original signature with no type arguments resolved
flags.push(( generic_args.push((
name, *name,
Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()), self.tcx.type_of(def.did()).instantiate_identity().to_string(),
)); ));
} }
} }
} }
if let Some(true) = self_ty.ty_adt_def().map(|def| def.did().is_local()) { if let Some(true) = self_ty.ty_adt_def().map(|def| def.did().is_local()) {
flags.push((sym::crate_local, None)); crate_local = true;
} }
// Allow targeting all integers using `{integral}`, even if the exact type was resolved // Allow targeting all integers using `{integral}`, even if the exact type was resolved
if self_ty.is_integral() { if self_ty.is_integral() {
flags.push((sym::_Self, Some("{integral}".to_owned()))); self_types.push("{integral}".to_owned());
} }
if self_ty.is_array_slice() { if self_ty.is_array_slice() {
flags.push((sym::_Self, Some("&[]".to_owned()))); self_types.push("&[]".to_owned());
} }
if self_ty.is_fn() { if self_ty.is_fn() {
@ -215,53 +194,51 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
hir::Safety::Unsafe => "unsafe fn", hir::Safety::Unsafe => "unsafe fn",
} }
}; };
flags.push((sym::_Self, Some(shortname.to_owned()))); self_types.push(shortname.to_owned());
} }
// Slices give us `[]`, `[{ty}]` // Slices give us `[]`, `[{ty}]`
if let ty::Slice(aty) = self_ty.kind() { if let ty::Slice(aty) = self_ty.kind() {
flags.push((sym::_Self, Some("[]".to_string()))); self_types.push("[]".to_owned());
if let Some(def) = aty.ty_adt_def() { if let Some(def) = aty.ty_adt_def() {
// We also want to be able to select the slice's type's original // We also want to be able to select the slice's type's original
// signature with no type arguments resolved // signature with no type arguments resolved
flags.push(( self_types
sym::_Self, .push(format!("[{}]", self.tcx.type_of(def.did()).instantiate_identity()));
Some(format!("[{}]", self.tcx.type_of(def.did()).instantiate_identity())),
));
} }
if aty.is_integral() { if aty.is_integral() {
flags.push((sym::_Self, Some("[{integral}]".to_string()))); self_types.push("[{integral}]".to_string());
} }
} }
// Arrays give us `[]`, `[{ty}; _]` and `[{ty}; N]` // Arrays give us `[]`, `[{ty}; _]` and `[{ty}; N]`
if let ty::Array(aty, len) = self_ty.kind() { if let ty::Array(aty, len) = self_ty.kind() {
flags.push((sym::_Self, Some("[]".to_string()))); self_types.push("[]".to_string());
let len = len.try_to_target_usize(self.tcx); let len = len.try_to_target_usize(self.tcx);
flags.push((sym::_Self, Some(format!("[{aty}; _]")))); self_types.push(format!("[{aty}; _]"));
if let Some(n) = len { if let Some(n) = len {
flags.push((sym::_Self, Some(format!("[{aty}; {n}]")))); self_types.push(format!("[{aty}; {n}]"));
} }
if let Some(def) = aty.ty_adt_def() { if let Some(def) = aty.ty_adt_def() {
// We also want to be able to select the array's type's original // We also want to be able to select the array's type's original
// signature with no type arguments resolved // signature with no type arguments resolved
let def_ty = self.tcx.type_of(def.did()).instantiate_identity(); let def_ty = self.tcx.type_of(def.did()).instantiate_identity();
flags.push((sym::_Self, Some(format!("[{def_ty}; _]")))); self_types.push(format!("[{def_ty}; _]"));
if let Some(n) = len { if let Some(n) = len {
flags.push((sym::_Self, Some(format!("[{def_ty}; {n}]")))); self_types.push(format!("[{def_ty}; {n}]"));
} }
} }
if aty.is_integral() { if aty.is_integral() {
flags.push((sym::_Self, Some("[{integral}; _]".to_string()))); self_types.push("[{integral}; _]".to_string());
if let Some(n) = len { if let Some(n) = len {
flags.push((sym::_Self, Some(format!("[{{integral}}; {n}]")))); self_types.push(format!("[{{integral}}; {n}]"));
} }
} }
} }
if let ty::Dynamic(traits, _, _) = self_ty.kind() { if let ty::Dynamic(traits, _, _) = self_ty.kind() {
for t in traits.iter() { for t in traits.iter() {
if let ty::ExistentialPredicate::Trait(trait_ref) = t.skip_binder() { if let ty::ExistentialPredicate::Trait(trait_ref) = t.skip_binder() {
flags.push((sym::_Self, Some(self.tcx.def_path_str(trait_ref.def_id)))) self_types.push(self.tcx.def_path_str(trait_ref.def_id));
} }
} }
} }
@ -271,31 +248,76 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
&& let ty::Slice(sty) = ref_ty.kind() && let ty::Slice(sty) = ref_ty.kind()
&& sty.is_integral() && sty.is_integral()
{ {
flags.push((sym::_Self, Some("&[{integral}]".to_owned()))); self_types.push("&[{integral}]".to_owned());
} }
})); }));
let this = self.tcx.def_path_str(trait_pred.trait_ref.def_id);
let trait_sugared = trait_pred.trait_ref.print_trait_sugared();
let condition_options = ConditionOptions {
self_types,
from_desugaring,
cause,
crate_local,
direct,
generic_args,
};
// Unlike the generic_args earlier,
// this one is *not* collected under `with_no_trimmed_paths!`
// for printing the type to the user
//
// This includes `Self`, as it is the first parameter in `own_params`.
let generic_args = self
.tcx
.generics_of(trait_pred.trait_ref.def_id)
.own_params
.iter()
.filter_map(|param| {
let value = match param.kind {
GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
if let Some(ty) = trait_pred.trait_ref.args[param.index as usize].as_type()
{
self.tcx.short_string(ty, long_ty_file)
} else {
trait_pred.trait_ref.args[param.index as usize].to_string()
}
}
GenericParamDefKind::Lifetime => return None,
};
let name = param.name;
Some((name, value))
})
.collect();
let format_args = FormatArgs { this, trait_sugared, generic_args, item_context };
if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, def_id) { if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, def_id) {
command.evaluate(self.tcx, trait_pred.trait_ref, &flags, long_ty_file) command.evaluate(self.tcx, trait_pred.trait_ref, &condition_options, &format_args)
} else { } else {
OnUnimplementedNote::default() OnUnimplementedNote::default()
} }
} }
} }
/// Represents a format string in a on_unimplemented attribute,
/// like the "content" in `#[diagnostic::on_unimplemented(message = "content")]`
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct OnUnimplementedFormatString { pub struct OnUnimplementedFormatString {
symbol: Symbol, /// Symbol of the format string, i.e. `"content"`
span: Span, pub symbol: Symbol,
is_diagnostic_namespace_variant: bool, ///The span of the format string, i.e. `"content"`
pub span: Span,
pub is_diagnostic_namespace_variant: bool,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct OnUnimplementedDirective { pub struct OnUnimplementedDirective {
pub condition: Option<MetaItemInner>, pub condition: Option<Condition>,
pub subcommands: Vec<OnUnimplementedDirective>, pub subcommands: Vec<OnUnimplementedDirective>,
pub message: Option<OnUnimplementedFormatString>, pub message: Option<(Span, OnUnimplementedFormatString)>,
pub label: Option<OnUnimplementedFormatString>, pub label: Option<(Span, OnUnimplementedFormatString)>,
pub notes: Vec<OnUnimplementedFormatString>, pub notes: Vec<OnUnimplementedFormatString>,
pub parent_label: Option<OnUnimplementedFormatString>, pub parent_label: Option<OnUnimplementedFormatString>,
pub append_const_msg: Option<AppendConstMessage>, pub append_const_msg: Option<AppendConstMessage>,
@ -329,7 +351,7 @@ pub struct MalformedOnUnimplementedAttrLint {
} }
impl MalformedOnUnimplementedAttrLint { impl MalformedOnUnimplementedAttrLint {
fn new(span: Span) -> Self { pub fn new(span: Span) -> Self {
Self { span } Self { span }
} }
} }
@ -350,7 +372,7 @@ pub struct IgnoredDiagnosticOption {
} }
impl IgnoredDiagnosticOption { impl IgnoredDiagnosticOption {
fn maybe_emit_warning<'tcx>( pub fn maybe_emit_warning<'tcx>(
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
item_def_id: DefId, item_def_id: DefId,
new: Option<Span>, new: Option<Span>,
@ -370,29 +392,11 @@ impl IgnoredDiagnosticOption {
} }
} }
#[derive(LintDiagnostic)]
#[diag(trait_selection_unknown_format_parameter_for_on_unimplemented_attr)]
#[help]
pub struct UnknownFormatParameterForOnUnimplementedAttr {
argument_name: Symbol,
trait_name: Ident,
}
#[derive(LintDiagnostic)]
#[diag(trait_selection_disallowed_positional_argument)]
#[help]
pub struct DisallowedPositionalArgument;
#[derive(LintDiagnostic)]
#[diag(trait_selection_invalid_format_specifier)]
#[help]
pub struct InvalidFormatSpecifier;
#[derive(LintDiagnostic)] #[derive(LintDiagnostic)]
#[diag(trait_selection_wrapped_parser_error)] #[diag(trait_selection_wrapped_parser_error)]
pub struct WrappedParserError { pub struct WrappedParserError {
description: String, pub description: String,
label: String, pub label: String,
} }
impl<'tcx> OnUnimplementedDirective { impl<'tcx> OnUnimplementedDirective {
@ -407,12 +411,12 @@ impl<'tcx> OnUnimplementedDirective {
let mut errored = None; let mut errored = None;
let mut item_iter = items.iter(); let mut item_iter = items.iter();
let parse_value = |value_str, value_span| { let parse_value = |value_str, span| {
OnUnimplementedFormatString::try_parse( OnUnimplementedFormatString::try_parse(
tcx, tcx,
item_def_id, item_def_id,
value_str, value_str,
value_span, span,
is_diagnostic_namespace_variant, is_diagnostic_namespace_variant,
) )
.map(Some) .map(Some)
@ -434,7 +438,7 @@ impl<'tcx> OnUnimplementedDirective {
} }
true true
}); });
Some(cond.clone()) Some(Condition { inner: cond.clone() })
}; };
let mut message = None; let mut message = None;
@ -444,24 +448,36 @@ impl<'tcx> OnUnimplementedDirective {
let mut subcommands = vec![]; let mut subcommands = vec![];
let mut append_const_msg = None; let mut append_const_msg = None;
let get_value_and_span = |item: &_, key| {
if let MetaItemInner::MetaItem(MetaItem {
path,
kind: MetaItemKind::NameValue(MetaItemLit { span, kind: LitKind::Str(s, _), .. }),
..
}) = item
&& *path == key
{
Some((*s, *span))
} else {
None
}
};
for item in item_iter { for item in item_iter {
if item.has_name(sym::message) && message.is_none() { if let Some((message_, span)) = get_value_and_span(item, sym::message)
if let Some(message_) = item.value_str() { && message.is_none()
message = parse_value(message_, item.span())?; {
message = parse_value(message_, span)?.map(|l| (item.span(), l));
continue;
} else if let Some((label_, span)) = get_value_and_span(item, sym::label)
&& label.is_none()
{
label = parse_value(label_, span)?.map(|l| (item.span(), l));
continue;
} else if let Some((note_, span)) = get_value_and_span(item, sym::note) {
if let Some(note) = parse_value(note_, span)? {
notes.push(note);
continue; continue;
} }
} else if item.has_name(sym::label) && label.is_none() {
if let Some(label_) = item.value_str() {
label = parse_value(label_, item.span())?;
continue;
}
} else if item.has_name(sym::note) {
if let Some(note_) = item.value_str() {
if let Some(note) = parse_value(note_, item.span())? {
notes.push(note);
continue;
}
}
} else if item.has_name(sym::parent_label) } else if item.has_name(sym::parent_label)
&& parent_label.is_none() && parent_label.is_none()
&& !is_diagnostic_namespace_variant && !is_diagnostic_namespace_variant
@ -539,6 +555,13 @@ impl<'tcx> OnUnimplementedDirective {
} }
pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<Option<Self>, ErrorGuaranteed> { pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<Option<Self>, ErrorGuaranteed> {
if !tcx.is_trait(item_def_id) {
// It could be a trait_alias (`trait MyTrait = SomeOtherTrait`)
// or an implementation (`impl MyTrait for Foo {}`)
//
// We don't support those.
return Ok(None);
}
if let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) { if let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) {
return Self::parse_attribute(attr, false, tcx, item_def_id); return Self::parse_attribute(attr, false, tcx, item_def_id);
} else { } else {
@ -554,15 +577,15 @@ impl<'tcx> OnUnimplementedDirective {
IgnoredDiagnosticOption::maybe_emit_warning( IgnoredDiagnosticOption::maybe_emit_warning(
tcx, tcx,
item_def_id, item_def_id,
directive.message.as_ref().map(|f| f.span), directive.message.as_ref().map(|f| f.0),
aggr.message.as_ref().map(|f| f.span), aggr.message.as_ref().map(|f| f.0),
"message", "message",
); );
IgnoredDiagnosticOption::maybe_emit_warning( IgnoredDiagnosticOption::maybe_emit_warning(
tcx, tcx,
item_def_id, item_def_id,
directive.label.as_ref().map(|f| f.span), directive.label.as_ref().map(|f| f.0),
aggr.label.as_ref().map(|f| f.span), aggr.label.as_ref().map(|f| f.0),
"label", "label",
); );
IgnoredDiagnosticOption::maybe_emit_warning( IgnoredDiagnosticOption::maybe_emit_warning(
@ -636,13 +659,16 @@ impl<'tcx> OnUnimplementedDirective {
condition: None, condition: None,
message: None, message: None,
subcommands: vec![], subcommands: vec![],
label: Some(OnUnimplementedFormatString::try_parse( label: Some((
tcx,
item_def_id,
value,
attr.span(), attr.span(),
is_diagnostic_namespace_variant, OnUnimplementedFormatString::try_parse(
)?), tcx,
item_def_id,
value,
attr.value_span().unwrap_or(attr.span()),
is_diagnostic_namespace_variant,
)?,
)),
notes: Vec::new(), notes: Vec::new(),
parent_label: None, parent_label: None,
append_const_msg: None, append_const_msg: None,
@ -702,43 +728,23 @@ impl<'tcx> OnUnimplementedDirective {
&self, &self,
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
trait_ref: ty::TraitRef<'tcx>, trait_ref: ty::TraitRef<'tcx>,
options: &[(Symbol, Option<String>)], condition_options: &ConditionOptions,
long_ty_file: &mut Option<PathBuf>, args: &FormatArgs<'tcx>,
) -> OnUnimplementedNote { ) -> OnUnimplementedNote {
let mut message = None; let mut message = None;
let mut label = None; let mut label = None;
let mut notes = Vec::new(); let mut notes = Vec::new();
let mut parent_label = None; let mut parent_label = None;
let mut append_const_msg = None; let mut append_const_msg = None;
info!("evaluate({:?}, trait_ref={:?}, options={:?})", self, trait_ref, options); info!(
"evaluate({:?}, trait_ref={:?}, options={:?}, args ={:?})",
let options_map: FxHashMap<Symbol, String> = self, trait_ref, condition_options, args
options.iter().filter_map(|(k, v)| v.clone().map(|v| (*k, v))).collect(); );
for command in self.subcommands.iter().chain(Some(self)).rev() { for command in self.subcommands.iter().chain(Some(self)).rev() {
debug!(?command); debug!(?command);
if let Some(ref condition) = command.condition if let Some(ref condition) = command.condition
&& !attr::eval_condition(condition, &tcx.sess, Some(tcx.features()), &mut |cfg| { && !condition.matches_predicate(tcx, condition_options)
let value = cfg.value.map(|v| {
// `with_no_visible_paths` is also used when generating the options,
// so we need to match it here.
ty::print::with_no_visible_paths!(
OnUnimplementedFormatString {
symbol: v,
span: cfg.span,
is_diagnostic_namespace_variant: false
}
.format(
tcx,
trait_ref,
&options_map,
long_ty_file
)
)
});
options.contains(&(cfg.name, value))
})
{ {
debug!("evaluate: skipping {:?} due to condition", command); debug!("evaluate: skipping {:?} due to condition", command);
continue; continue;
@ -762,14 +768,10 @@ impl<'tcx> OnUnimplementedDirective {
} }
OnUnimplementedNote { OnUnimplementedNote {
label: label.map(|l| l.format(tcx, trait_ref, &options_map, long_ty_file)), label: label.map(|l| l.1.format(tcx, trait_ref, args)),
message: message.map(|m| m.format(tcx, trait_ref, &options_map, long_ty_file)), message: message.map(|m| m.1.format(tcx, trait_ref, args)),
notes: notes notes: notes.into_iter().map(|n| n.format(tcx, trait_ref, args)).collect(),
.into_iter() parent_label: parent_label.map(|e_s| e_s.format(tcx, trait_ref, args)),
.map(|n| n.format(tcx, trait_ref, &options_map, long_ty_file))
.collect(),
parent_label: parent_label
.map(|e_s| e_s.format(tcx, trait_ref, &options_map, long_ty_file)),
append_const_msg, append_const_msg,
} }
} }
@ -780,142 +782,95 @@ impl<'tcx> OnUnimplementedFormatString {
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
item_def_id: DefId, item_def_id: DefId,
from: Symbol, from: Symbol,
value_span: Span, span: Span,
is_diagnostic_namespace_variant: bool, is_diagnostic_namespace_variant: bool,
) -> Result<Self, ErrorGuaranteed> { ) -> Result<Self, ErrorGuaranteed> {
let result = OnUnimplementedFormatString { let result =
symbol: from, OnUnimplementedFormatString { symbol: from, span, is_diagnostic_namespace_variant };
span: value_span,
is_diagnostic_namespace_variant,
};
result.verify(tcx, item_def_id)?; result.verify(tcx, item_def_id)?;
Ok(result) Ok(result)
} }
fn verify(&self, tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<(), ErrorGuaranteed> { fn verify(&self, tcx: TyCtxt<'tcx>, trait_def_id: DefId) -> Result<(), ErrorGuaranteed> {
let trait_def_id = if tcx.is_trait(item_def_id) { if !tcx.is_trait(trait_def_id) {
item_def_id return Ok(());
} else {
tcx.trait_id_of_impl(item_def_id)
.expect("expected `on_unimplemented` to correspond to a trait")
}; };
let trait_name = tcx.item_ident(trait_def_id);
let generics = tcx.generics_of(item_def_id); let ctx = if self.is_diagnostic_namespace_variant {
let s = self.symbol.as_str(); Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }
let mut parser = Parser::new(s, None, None, false, ParseMode::Format); } else {
Ctx::RustcOnUnimplemented { tcx, trait_def_id }
};
let mut result = Ok(()); let mut result = Ok(());
for token in &mut parser {
match token { match FormatString::parse(self.symbol, self.span, &ctx) {
Piece::Lit(_) => (), // Normal string, no need to check it // Warnings about format specifiers, deprecated parameters, wrong parameters etc.
Piece::NextArgument(a) => { // In other words we'd like to let the author know, but we can still try to format the string later
let format_spec = a.format; Ok(FormatString { warnings, .. }) => {
if self.is_diagnostic_namespace_variant if self.is_diagnostic_namespace_variant {
&& (format_spec.ty_span.is_some() for w in warnings {
|| format_spec.width_span.is_some() w.emit_warning(tcx, trait_def_id)
|| format_spec.precision_span.is_some()
|| format_spec.fill_span.is_some())
{
if let Some(item_def_id) = item_def_id.as_local() {
tcx.emit_node_span_lint(
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
tcx.local_def_id_to_hir_id(item_def_id),
self.span,
InvalidFormatSpecifier,
);
}
} }
match a.position { } else {
Position::ArgumentNamed(s) => { for w in warnings {
match Symbol::intern(s) { match w {
// `{ThisTraitsName}` is allowed FormatWarning::UnknownParam { argument_name, span } => {
s if s == trait_name.name
&& !self.is_diagnostic_namespace_variant =>
{
()
}
s if ALLOWED_FORMAT_SYMBOLS.contains(&s)
&& !self.is_diagnostic_namespace_variant =>
{
()
}
// So is `{A}` if A is a type parameter
s if generics.own_params.iter().any(|param| param.name == s) => (),
s => {
if self.is_diagnostic_namespace_variant {
if let Some(item_def_id) = item_def_id.as_local() {
tcx.emit_node_span_lint(
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
tcx.local_def_id_to_hir_id(item_def_id),
self.span,
UnknownFormatParameterForOnUnimplementedAttr {
argument_name: s,
trait_name,
},
);
}
} else {
result = Err(struct_span_code_err!(
tcx.dcx(),
self.span,
E0230,
"there is no parameter `{}` on {}",
s,
if trait_def_id == item_def_id {
format!("trait `{trait_name}`")
} else {
"impl".to_string()
}
)
.emit());
}
}
}
}
// `{:1}` and `{}` are not to be used
Position::ArgumentIs(..) | Position::ArgumentImplicitlyIs(_) => {
if self.is_diagnostic_namespace_variant {
if let Some(item_def_id) = item_def_id.as_local() {
tcx.emit_node_span_lint(
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
tcx.local_def_id_to_hir_id(item_def_id),
self.span,
DisallowedPositionalArgument,
);
}
} else {
let reported = struct_span_code_err!( let reported = struct_span_code_err!(
tcx.dcx(), tcx.dcx(),
self.span, span,
E0231, E0230,
"only named generic parameters are allowed" "cannot find parameter {} on this trait",
argument_name,
) )
.emit(); .emit();
result = Err(reported); result = Err(reported);
} }
FormatWarning::PositionalArgument { span, .. } => {
let reported = struct_span_code_err!(
tcx.dcx(),
span,
E0231,
"positional format arguments are not allowed here"
)
.emit();
result = Err(reported);
}
FormatWarning::InvalidSpecifier { .. }
| FormatWarning::FutureIncompat { .. } => {}
} }
} }
} }
} }
} // Errors from the underlying `rustc_parse_format::Parser`
// we cannot return errors from processing the format string as hard error here Err(errors) => {
// as the diagnostic namespace guarantees that malformed input cannot cause an error // we cannot return errors from processing the format string as hard error here
// // as the diagnostic namespace guarantees that malformed input cannot cause an error
// if we encounter any error while processing we nevertheless want to show it as warning //
// so that users are aware that something is not correct // if we encounter any error while processing we nevertheless want to show it as warning
for e in parser.errors { // so that users are aware that something is not correct
if self.is_diagnostic_namespace_variant { for e in errors {
if let Some(item_def_id) = item_def_id.as_local() { if self.is_diagnostic_namespace_variant {
tcx.emit_node_span_lint( if let Some(trait_def_id) = trait_def_id.as_local() {
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, tcx.emit_node_span_lint(
tcx.local_def_id_to_hir_id(item_def_id), UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
self.span, tcx.local_def_id_to_hir_id(trait_def_id),
WrappedParserError { description: e.description, label: e.label }, self.span,
); WrappedParserError { description: e.description, label: e.label },
);
}
} else {
let reported = struct_span_code_err!(
tcx.dcx(),
self.span,
E0231,
"{}",
e.description,
)
.emit();
result = Err(reported);
}
} }
} else {
let reported =
struct_span_code_err!(tcx.dcx(), self.span, E0231, "{}", e.description,).emit();
result = Err(reported);
} }
} }
@ -926,98 +881,28 @@ impl<'tcx> OnUnimplementedFormatString {
&self, &self,
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
trait_ref: ty::TraitRef<'tcx>, trait_ref: ty::TraitRef<'tcx>,
options: &FxHashMap<Symbol, String>, args: &FormatArgs<'tcx>,
long_ty_file: &mut Option<PathBuf>,
) -> String { ) -> String {
let name = tcx.item_name(trait_ref.def_id); let trait_def_id = trait_ref.def_id;
let trait_str = tcx.def_path_str(trait_ref.def_id); let ctx = if self.is_diagnostic_namespace_variant {
let generics = tcx.generics_of(trait_ref.def_id); Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }
let generic_map = generics
.own_params
.iter()
.filter_map(|param| {
let value = match param.kind {
GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
if let Some(ty) = trait_ref.args[param.index as usize].as_type() {
tcx.short_string(ty, long_ty_file)
} else {
trait_ref.args[param.index as usize].to_string()
}
}
GenericParamDefKind::Lifetime => return None,
};
let name = param.name;
Some((name, value))
})
.collect::<FxHashMap<Symbol, String>>();
let empty_string = String::new();
let s = self.symbol.as_str();
let mut parser = Parser::new(s, None, None, false, ParseMode::Format);
let item_context = (options.get(&sym::ItemContext)).unwrap_or(&empty_string);
let constructed_message = (&mut parser)
.map(|p| match p {
Piece::Lit(s) => s.to_owned(),
Piece::NextArgument(a) => match a.position {
Position::ArgumentNamed(arg) => {
let s = Symbol::intern(arg);
match generic_map.get(&s) {
Some(val) => val.to_string(),
None if self.is_diagnostic_namespace_variant => {
format!("{{{arg}}}")
}
None if s == name => trait_str.clone(),
None => {
if let Some(val) = options.get(&s) {
val.clone()
} else if s == sym::from_desugaring {
// don't break messages using these two arguments incorrectly
String::new()
} else if s == sym::ItemContext
&& !self.is_diagnostic_namespace_variant
{
item_context.clone()
} else if s == sym::integral {
String::from("{integral}")
} else if s == sym::integer_ {
String::from("{integer}")
} else if s == sym::float {
String::from("{float}")
} else {
bug!(
"broken on_unimplemented {:?} for {:?}: \
no argument matching {:?}",
self.symbol,
trait_ref,
s
)
}
}
}
}
Position::ArgumentImplicitlyIs(_) if self.is_diagnostic_namespace_variant => {
String::from("{}")
}
Position::ArgumentIs(idx) if self.is_diagnostic_namespace_variant => {
format!("{{{idx}}}")
}
_ => bug!("broken on_unimplemented {:?} - bad format arg", self.symbol),
},
})
.collect();
// we cannot return errors from processing the format string as hard error here
// as the diagnostic namespace guarantees that malformed input cannot cause an error
//
// if we encounter any error while processing the format string
// we don't want to show the potentially half assembled formatted string,
// therefore we fall back to just showing the input string in this case
//
// The actual parser errors are emitted earlier
// as lint warnings in OnUnimplementedFormatString::verify
if self.is_diagnostic_namespace_variant && !parser.errors.is_empty() {
String::from(s)
} else { } else {
constructed_message Ctx::RustcOnUnimplemented { tcx, trait_def_id }
};
if let Ok(s) = FormatString::parse(self.symbol, self.span, &ctx) {
s.format(args)
} else {
// we cannot return errors from processing the format string as hard error here
// as the diagnostic namespace guarantees that malformed input cannot cause an error
//
// if we encounter any error while processing the format string
// we don't want to show the potentially half assembled formatted string,
// therefore we fall back to just showing the input string in this case
//
// The actual parser errors are emitted earlier
// as lint warnings in OnUnimplementedFormatString::verify
self.symbol.as_str().into()
} }
} }
} }

View file

@ -0,0 +1,120 @@
use rustc_ast::MetaItemInner;
use rustc_attr_parsing as attr;
use rustc_middle::ty::{self, TyCtxt};
use rustc_parse_format::{ParseMode, Parser, Piece, Position};
use rustc_span::{DesugaringKind, Span, Symbol, kw, sym};
/// A predicate in an attribute using on, all, any,
/// similar to a cfg predicate.
#[derive(Debug)]
pub struct Condition {
pub inner: MetaItemInner,
}
impl Condition {
pub fn span(&self) -> Span {
self.inner.span()
}
pub fn matches_predicate<'tcx>(&self, tcx: TyCtxt<'tcx>, options: &ConditionOptions) -> bool {
attr::eval_condition(&self.inner, tcx.sess, Some(tcx.features()), &mut |cfg| {
let value = cfg.value.map(|v| {
// `with_no_visible_paths` is also used when generating the options,
// so we need to match it here.
ty::print::with_no_visible_paths!({
Parser::new(v.as_str(), None, None, false, ParseMode::Format)
.map(|p| match p {
Piece::Lit(s) => s.to_owned(),
Piece::NextArgument(a) => match a.position {
Position::ArgumentNamed(arg) => {
let s = Symbol::intern(arg);
match options.generic_args.iter().find(|(k, _)| *k == s) {
Some((_, val)) => val.to_string(),
None => format!("{{{arg}}}"),
}
}
Position::ArgumentImplicitlyIs(_) => String::from("{}"),
Position::ArgumentIs(idx) => format!("{{{idx}}}"),
},
})
.collect()
})
});
options.contains(cfg.name, &value)
})
}
}
/// Used with `Condition::matches_predicate` to test whether the condition applies
///
/// For example, given a
/// ```rust,ignore (just an example)
/// #[rustc_on_unimplemented(
/// on(all(from_desugaring = "QuestionMark"),
/// message = "the `?` operator can only be used in {ItemContext} \
/// that returns `Result` or `Option` \
/// (or another type that implements `{FromResidual}`)",
/// label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`",
/// parent_label = "this function should return `Result` or `Option` to accept `?`"
/// ),
/// )]
/// pub trait FromResidual<R = <Self as Try>::Residual> {
/// ...
/// }
///
/// async fn an_async_function() -> u32 {
/// let x: Option<u32> = None;
/// x?; //~ ERROR the `?` operator
/// 22
/// }
/// ```
/// it will look like this:
///
/// ```rust,ignore (just an example)
/// ConditionOptions {
/// self_types: ["u32", "{integral}"],
/// from_desugaring: Some("QuestionMark"),
/// cause: None,
/// crate_local: false,
/// direct: true,
/// generic_args: [("Self","u32"),
/// ("R", "core::option::Option<core::convert::Infallible>"),
/// ("R", "core::option::Option<T>" ),
/// ],
/// }
/// ```
#[derive(Debug)]
pub struct ConditionOptions {
/// All the self types that may apply.
/// for example
pub self_types: Vec<String>,
// The kind of compiler desugaring.
pub from_desugaring: Option<DesugaringKind>,
/// Match on a variant of [rustc_infer::traits::ObligationCauseCode]
pub cause: Option<String>,
pub crate_local: bool,
/// Is the obligation "directly" user-specified, rather than derived?
pub direct: bool,
// A list of the generic arguments and their reified types
pub generic_args: Vec<(Symbol, String)>,
}
impl ConditionOptions {
pub fn contains(&self, key: Symbol, value: &Option<String>) -> bool {
match (key, value) {
(sym::_Self | kw::SelfUpper, Some(value)) => self.self_types.contains(&value),
// from_desugaring as a flag
(sym::from_desugaring, None) => self.from_desugaring.is_some(),
// from_desugaring as key == value
(sym::from_desugaring, Some(v)) if let Some(ds) = self.from_desugaring => ds.matches(v),
(sym::cause, Some(value)) => self.cause.as_deref() == Some(value),
(sym::crate_local, None) => self.crate_local,
(sym::direct, None) => self.direct,
(other, Some(value)) => {
self.generic_args.iter().any(|(k, v)| *k == other && v == value)
}
_ => false,
}
}
}

View file

@ -0,0 +1,414 @@
use std::fmt;
use errors::*;
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::print::TraitRefPrintSugared;
use rustc_parse_format::{
Alignment, Argument, Count, FormatSpec, InnerSpan, ParseError, ParseMode, Parser,
Piece as RpfPiece, Position,
};
use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
use rustc_span::def_id::DefId;
use rustc_span::{BytePos, Pos, Span, Symbol, kw, sym};
/// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces",
/// either as string pieces or dynamic arguments.
#[derive(Debug)]
pub struct FormatString {
#[allow(dead_code, reason = "Debug impl")]
input: Symbol,
span: Span,
pieces: Vec<Piece>,
/// The formatting string was parsed succesfully but with warnings
pub warnings: Vec<FormatWarning>,
}
#[derive(Debug)]
enum Piece {
Lit(String),
Arg(FormatArg),
}
#[derive(Debug)]
enum FormatArg {
// A generic parameter, like `{T}` if we're on the `From<T>` trait.
GenericParam {
generic_param: Symbol,
},
// `{Self}`
SelfUpper,
/// `{This}` or `{TraitName}`
This,
/// The sugared form of the trait
Trait,
/// what we're in, like a function, method, closure etc.
ItemContext,
/// What the user typed, if it doesn't match anything we can use.
AsIs(String),
}
pub enum Ctx<'tcx> {
// `#[rustc_on_unimplemented]`
RustcOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId },
// `#[diagnostic::...]`
DiagnosticOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId },
}
#[derive(Debug)]
pub enum FormatWarning {
UnknownParam { argument_name: Symbol, span: Span },
PositionalArgument { span: Span, help: String },
InvalidSpecifier { name: String, span: Span },
FutureIncompat { span: Span, help: String },
}
impl FormatWarning {
pub fn emit_warning<'tcx>(&self, tcx: TyCtxt<'tcx>, item_def_id: DefId) {
match *self {
FormatWarning::UnknownParam { argument_name, span } => {
let this = tcx.item_ident(item_def_id);
if let Some(item_def_id) = item_def_id.as_local() {
tcx.emit_node_span_lint(
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
tcx.local_def_id_to_hir_id(item_def_id),
span,
UnknownFormatParameterForOnUnimplementedAttr {
argument_name,
trait_name: this,
},
);
}
}
FormatWarning::PositionalArgument { span, .. } => {
if let Some(item_def_id) = item_def_id.as_local() {
tcx.emit_node_span_lint(
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
tcx.local_def_id_to_hir_id(item_def_id),
span,
DisallowedPositionalArgument,
);
}
}
FormatWarning::InvalidSpecifier { span, .. } => {
if let Some(item_def_id) = item_def_id.as_local() {
tcx.emit_node_span_lint(
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
tcx.local_def_id_to_hir_id(item_def_id),
span,
InvalidFormatSpecifier,
);
}
}
FormatWarning::FutureIncompat { .. } => {
// We've never deprecated anything in diagnostic namespace format strings
// but if we do we will emit a warning here
// FIXME(mejrs) in a couple releases, start emitting warnings for
// #[rustc_on_unimplemented] deprecated args
}
}
}
}
/// Arguments to fill a [FormatString] with.
///
/// For example, given a
/// ```rust,ignore (just an example)
///
/// #[rustc_on_unimplemented(
/// on(all(from_desugaring = "QuestionMark"),
/// message = "the `?` operator can only be used in {ItemContext} \
/// that returns `Result` or `Option` \
/// (or another type that implements `{FromResidual}`)",
/// label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`",
/// parent_label = "this function should return `Result` or `Option` to accept `?`"
/// ),
/// )]
/// pub trait FromResidual<R = <Self as Try>::Residual> {
/// ...
/// }
///
/// async fn an_async_function() -> u32 {
/// let x: Option<u32> = None;
/// x?; //~ ERROR the `?` operator
/// 22
/// }
/// ```
/// it will look like this:
///
/// ```rust,ignore (just an example)
/// FormatArgs {
/// this: "FromResidual",
/// trait_sugared: "FromResidual<Option<Infallible>>",
/// item_context: "an async function",
/// generic_args: [("Self", "u32"), ("R", "Option<Infallible>")],
/// }
/// ```
#[derive(Debug)]
pub struct FormatArgs<'tcx> {
pub this: String,
pub trait_sugared: TraitRefPrintSugared<'tcx>,
pub item_context: &'static str,
pub generic_args: Vec<(Symbol, String)>,
}
impl FormatString {
pub fn span(&self) -> Span {
self.span
}
pub fn parse<'tcx>(
input: Symbol,
span: Span,
ctx: &Ctx<'tcx>,
) -> Result<Self, Vec<ParseError>> {
let s = input.as_str();
let mut parser = Parser::new(s, None, None, false, ParseMode::Format);
let mut pieces = Vec::new();
let mut warnings = Vec::new();
for piece in &mut parser {
match piece {
RpfPiece::Lit(lit) => {
pieces.push(Piece::Lit(lit.into()));
}
RpfPiece::NextArgument(arg) => {
warn_on_format_spec(arg.format, &mut warnings, span);
let arg = parse_arg(&arg, ctx, &mut warnings, span);
pieces.push(Piece::Arg(arg));
}
}
}
if parser.errors.is_empty() {
Ok(FormatString { input, pieces, span, warnings })
} else {
Err(parser.errors)
}
}
pub fn format(&self, args: &FormatArgs<'_>) -> String {
let mut ret = String::new();
for piece in &self.pieces {
match piece {
Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(&s),
// `A` if we have `trait Trait<A> {}` and `note = "i'm the actual type of {A}"`
Piece::Arg(FormatArg::GenericParam { generic_param }) => {
// Should always be some but we can't raise errors here
let value = match args.generic_args.iter().find(|(p, _)| p == generic_param) {
Some((_, val)) => val.to_string(),
None => generic_param.to_string(),
};
ret.push_str(&value);
}
// `{Self}`
Piece::Arg(FormatArg::SelfUpper) => {
let slf = match args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) {
Some((_, val)) => val.to_string(),
None => "Self".to_string(),
};
ret.push_str(&slf);
}
// It's only `rustc_onunimplemented` from here
Piece::Arg(FormatArg::This) => ret.push_str(&args.this),
Piece::Arg(FormatArg::Trait) => {
let _ = fmt::write(&mut ret, format_args!("{}", &args.trait_sugared));
}
Piece::Arg(FormatArg::ItemContext) => ret.push_str(args.item_context),
}
}
ret
}
}
fn parse_arg<'tcx>(
arg: &Argument<'_>,
ctx: &Ctx<'tcx>,
warnings: &mut Vec<FormatWarning>,
input_span: Span,
) -> FormatArg {
let (Ctx::RustcOnUnimplemented { tcx, trait_def_id }
| Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }) = ctx;
let trait_name = tcx.item_ident(*trait_def_id);
let generics = tcx.generics_of(trait_def_id);
let span = slice_span(input_span, arg.position_span);
match arg.position {
// Something like "hello {name}"
Position::ArgumentNamed(name) => match (ctx, Symbol::intern(name)) {
// accepted, but deprecated
(Ctx::RustcOnUnimplemented { .. }, sym::_Self) => {
warnings
.push(FormatWarning::FutureIncompat { span, help: String::from("use {Self}") });
FormatArg::SelfUpper
}
(
Ctx::RustcOnUnimplemented { .. },
sym::from_desugaring
| sym::crate_local
| sym::direct
| sym::cause
| sym::float
| sym::integer_
| sym::integral,
) => {
warnings.push(FormatWarning::FutureIncompat {
span,
help: String::from("don't use this in a format string"),
});
FormatArg::AsIs(String::new())
}
// Only `#[rustc_on_unimplemented]` can use these
(Ctx::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext,
(Ctx::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This,
(Ctx::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait,
// `{ThisTraitsName}`. Some attrs in std use this, but I'd like to change it to the more general `{This}`
// because that'll be simpler to parse and extend in the future
(Ctx::RustcOnUnimplemented { .. }, name) if name == trait_name.name => {
warnings
.push(FormatWarning::FutureIncompat { span, help: String::from("use {This}") });
FormatArg::This
}
// Any attribute can use these
(
Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
kw::SelfUpper,
) => FormatArg::SelfUpper,
(
Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
generic_param,
) if generics.own_params.iter().any(|param| param.name == generic_param) => {
FormatArg::GenericParam { generic_param }
}
(_, argument_name) => {
warnings.push(FormatWarning::UnknownParam { argument_name, span });
FormatArg::AsIs(format!("{{{}}}", argument_name.as_str()))
}
},
// `{:1}` and `{}` are ignored
Position::ArgumentIs(idx) => {
warnings.push(FormatWarning::PositionalArgument {
span,
help: format!("use `{{{idx}}}` to print a number in braces"),
});
FormatArg::AsIs(format!("{{{idx}}}"))
}
Position::ArgumentImplicitlyIs(_) => {
warnings.push(FormatWarning::PositionalArgument {
span,
help: String::from("use `{{}}` to print empty braces"),
});
FormatArg::AsIs(String::from("{}"))
}
}
}
/// `#[rustc_on_unimplemented]` and `#[diagnostic::...]` don't actually do anything
/// with specifiers, so emit a warning if they are used.
fn warn_on_format_spec(spec: FormatSpec<'_>, warnings: &mut Vec<FormatWarning>, input_span: Span) {
if !matches!(
spec,
FormatSpec {
fill: None,
fill_span: None,
align: Alignment::AlignUnknown,
sign: None,
alternate: false,
zero_pad: false,
debug_hex: None,
precision: Count::CountImplied,
precision_span: None,
width: Count::CountImplied,
width_span: None,
ty: _,
ty_span: _,
},
) {
let span = spec.ty_span.map(|inner| slice_span(input_span, inner)).unwrap_or(input_span);
warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() })
}
}
/// Helper function because `Span` and `rustc_parse_format::InnerSpan` don't know about each other
fn slice_span(input: Span, inner: InnerSpan) -> Span {
let InnerSpan { start, end } = inner;
let span = input.data();
Span::new(
span.lo + BytePos::from_usize(start),
span.lo + BytePos::from_usize(end),
span.ctxt,
span.parent,
)
}
pub mod errors {
use rustc_macros::LintDiagnostic;
use rustc_span::Ident;
use super::*;
#[derive(LintDiagnostic)]
#[diag(trait_selection_unknown_format_parameter_for_on_unimplemented_attr)]
#[help]
pub struct UnknownFormatParameterForOnUnimplementedAttr {
pub argument_name: Symbol,
pub trait_name: Ident,
}
#[derive(LintDiagnostic)]
#[diag(trait_selection_disallowed_positional_argument)]
#[help]
pub struct DisallowedPositionalArgument;
#[derive(LintDiagnostic)]
#[diag(trait_selection_invalid_format_specifier)]
#[help]
pub struct InvalidFormatSpecifier;
#[derive(LintDiagnostic)]
#[diag(trait_selection_missing_options_for_on_unimplemented_attr)]
#[help]
pub struct MissingOptionsForOnUnimplementedAttr;
#[derive(LintDiagnostic)]
#[diag(trait_selection_ignored_diagnostic_option)]
pub struct IgnoredDiagnosticOption {
pub option_name: &'static str,
#[label]
pub span: Span,
#[label(trait_selection_other_label)]
pub prev_span: Span,
}
impl IgnoredDiagnosticOption {
pub fn maybe_emit_warning<'tcx>(
tcx: TyCtxt<'tcx>,
item_def_id: DefId,
new: Option<Span>,
old: Option<Span>,
option_name: &'static str,
) {
if let (Some(new_item), Some(old_item)) = (new, old) {
if let Some(item_def_id) = item_def_id.as_local() {
tcx.emit_node_span_lint(
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
tcx.local_def_id_to_hir_id(item_def_id),
new_item,
IgnoredDiagnosticOption {
span: new_item,
prev_span: old_item,
option_name,
},
);
}
}
}
}
}

View file

@ -950,7 +950,7 @@ impl fmt::Debug for ArgsOs {
/// Constants associated with the current target /// Constants associated with the current target
#[stable(feature = "env", since = "1.0.0")] #[stable(feature = "env", since = "1.0.0")]
pub mod consts { pub mod consts {
use crate::sys::env::os; use crate::sys::env_consts::os;
/// A string describing the architecture of the CPU that is currently in use. /// A string describing the architecture of the CPU that is currently in use.
/// An example value may be: `"x86"`, `"arm"` or `"riscv64"`. /// An example value may be: `"x86"`, `"arm"` or `"riscv64"`.

View file

@ -1,106 +1,35 @@
#[cfg(target_os = "linux")] //! Constants associated with each target.
// Replaces the #[else] gate with #[cfg(not(any(…)))] of all the other gates.
// This ensures that they must be mutually exclusive and do not have precedence
// like cfg_if!.
macro cfg_unordered(
$(#[cfg($cfg:meta)] $os:item)*
#[else] $fallback:item
) {
$(#[cfg($cfg)] $os)*
#[cfg(not(any($($cfg),*)))] $fallback
}
// Keep entries sorted alphabetically and mutually exclusive.
cfg_unordered! {
#[cfg(target_os = "aix")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "unix";
pub const OS: &str = "linux"; pub const OS: &str = "aix";
pub const DLL_PREFIX: &str = "lib"; pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so"; pub const DLL_SUFFIX: &str = ".a";
pub const DLL_EXTENSION: &str = "so"; pub const DLL_EXTENSION: &str = "a";
pub const EXE_SUFFIX: &str = ""; pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = ""; pub const EXE_EXTENSION: &str = "";
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "android")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "unix";
pub const OS: &str = "macos"; pub const OS: &str = "android";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".dylib";
pub const DLL_EXTENSION: &str = "dylib";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "ios")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "ios";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".dylib";
pub const DLL_EXTENSION: &str = "dylib";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "tvos")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "tvos";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".dylib";
pub const DLL_EXTENSION: &str = "dylib";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "watchos")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "watchos";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".dylib";
pub const DLL_EXTENSION: &str = "dylib";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "visionos")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "visionos";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".dylib";
pub const DLL_EXTENSION: &str = "dylib";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "freebsd")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "freebsd";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "dragonfly")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "dragonfly";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "netbsd")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "netbsd";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "openbsd")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "openbsd";
pub const DLL_PREFIX: &str = "lib"; pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so"; pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so"; pub const DLL_EXTENSION: &str = "so";
@ -119,10 +48,10 @@ pub mod os {
pub const EXE_EXTENSION: &str = "exe"; pub const EXE_EXTENSION: &str = "exe";
} }
#[cfg(target_os = "android")] #[cfg(target_os = "dragonfly")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "unix";
pub const OS: &str = "android"; pub const OS: &str = "dragonfly";
pub const DLL_PREFIX: &str = "lib"; pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so"; pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so"; pub const DLL_EXTENSION: &str = "so";
@ -130,10 +59,21 @@ pub mod os {
pub const EXE_EXTENSION: &str = ""; pub const EXE_EXTENSION: &str = "";
} }
#[cfg(target_os = "solaris")] #[cfg(target_os = "emscripten")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "unix";
pub const OS: &str = "solaris"; pub const OS: &str = "emscripten";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = ".js";
pub const EXE_EXTENSION: &str = "js";
}
#[cfg(target_os = "espidf")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "espidf";
pub const DLL_PREFIX: &str = "lib"; pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so"; pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so"; pub const DLL_EXTENSION: &str = "so";
@ -141,10 +81,21 @@ pub mod os {
pub const EXE_EXTENSION: &str = ""; pub const EXE_EXTENSION: &str = "";
} }
#[cfg(target_os = "illumos")] #[cfg(target_os = "freebsd")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "unix";
pub const OS: &str = "illumos"; pub const OS: &str = "freebsd";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "fuchsia")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "fuchsia";
pub const DLL_PREFIX: &str = "lib"; pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so"; pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so"; pub const DLL_EXTENSION: &str = "so";
@ -163,6 +114,17 @@ pub mod os {
pub const EXE_EXTENSION: &str = ""; pub const EXE_EXTENSION: &str = "";
} }
#[cfg(target_os = "hermit")]
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "hermit";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = "";
pub const DLL_EXTENSION: &str = "";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "horizon")] #[cfg(target_os = "horizon")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "unix";
@ -185,35 +147,24 @@ pub mod os {
pub const EXE_EXTENSION: &str = ""; pub const EXE_EXTENSION: &str = "";
} }
#[cfg(target_os = "vita")] #[cfg(target_os = "illumos")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "unix";
pub const OS: &str = "vita"; pub const OS: &str = "illumos";
pub const DLL_PREFIX: &str = "lib"; pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so"; pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so"; pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = ".elf"; pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "elf"; pub const EXE_EXTENSION: &str = "";
} }
#[cfg(all(target_os = "emscripten", target_arch = "wasm32"))] #[cfg(target_os = "ios")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "unix";
pub const OS: &str = "emscripten"; pub const OS: &str = "ios";
pub const DLL_PREFIX: &str = "lib"; pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so"; pub const DLL_SUFFIX: &str = ".dylib";
pub const DLL_EXTENSION: &str = "so"; pub const DLL_EXTENSION: &str = "dylib";
pub const EXE_SUFFIX: &str = ".js";
pub const EXE_EXTENSION: &str = "js";
}
#[cfg(target_os = "fuchsia")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "fuchsia";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = ""; pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = ""; pub const EXE_EXTENSION: &str = "";
} }
@ -229,6 +180,39 @@ pub mod os {
pub const EXE_EXTENSION: &str = ""; pub const EXE_EXTENSION: &str = "";
} }
#[cfg(target_os = "linux")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "linux";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "macos")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "macos";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".dylib";
pub const DLL_EXTENSION: &str = "dylib";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "netbsd")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "netbsd";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "nto")] #[cfg(target_os = "nto")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "unix";
@ -240,6 +224,28 @@ pub mod os {
pub const EXE_EXTENSION: &str = ""; pub const EXE_EXTENSION: &str = "";
} }
#[cfg(target_os = "nuttx")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "nuttx";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "openbsd")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "openbsd";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "unix";
@ -262,6 +268,83 @@ pub mod os {
pub const EXE_EXTENSION: &str = ""; pub const EXE_EXTENSION: &str = "";
} }
#[cfg(all(target_vendor = "fortanix", target_env = "sgx"))]
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = ".sgxs";
pub const DLL_EXTENSION: &str = "sgxs";
pub const EXE_SUFFIX: &str = ".sgxs";
pub const EXE_EXTENSION: &str = "sgxs";
}
#[cfg(target_os = "solaris")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "solaris";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "solid_asp3")]
pub mod os {
pub const FAMILY: &str = "itron";
pub const OS: &str = "solid";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "tvos")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "tvos";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".dylib";
pub const DLL_EXTENSION: &str = "dylib";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "uefi")]
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "uefi";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = "";
pub const DLL_EXTENSION: &str = "";
pub const EXE_SUFFIX: &str = ".efi";
pub const EXE_EXTENSION: &str = "efi";
}
#[cfg(target_os = "visionos")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "visionos";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".dylib";
pub const DLL_EXTENSION: &str = "dylib";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "vita")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "vita";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = ".elf";
pub const EXE_EXTENSION: &str = "elf";
}
#[cfg(target_os = "vxworks")] #[cfg(target_os = "vxworks")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "unix";
@ -273,35 +356,49 @@ pub mod os {
pub const EXE_EXTENSION: &str = ""; pub const EXE_EXTENSION: &str = "";
} }
#[cfg(target_os = "espidf")] #[cfg(all(target_family = "wasm", not(any(target_os = "emscripten", target_os = "linux"))))]
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = ".wasm";
pub const DLL_EXTENSION: &str = "wasm";
pub const EXE_SUFFIX: &str = ".wasm";
pub const EXE_EXTENSION: &str = "wasm";
}
#[cfg(target_os = "watchos")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "unix";
pub const OS: &str = "espidf"; pub const OS: &str = "watchos";
pub const DLL_PREFIX: &str = "lib"; pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so"; pub const DLL_SUFFIX: &str = ".dylib";
pub const DLL_EXTENSION: &str = "so"; pub const DLL_EXTENSION: &str = "dylib";
pub const EXE_SUFFIX: &str = ""; pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = ""; pub const EXE_EXTENSION: &str = "";
} }
#[cfg(target_os = "aix")] #[cfg(target_os = "windows")]
pub mod os { pub mod os {
pub const FAMILY: &str = "unix"; pub const FAMILY: &str = "windows";
pub const OS: &str = "aix"; pub const OS: &str = "windows";
pub const DLL_PREFIX: &str = "lib"; pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = ".a"; pub const DLL_SUFFIX: &str = ".dll";
pub const DLL_EXTENSION: &str = "a"; pub const DLL_EXTENSION: &str = "dll";
pub const EXE_SUFFIX: &str = ".exe";
pub const EXE_EXTENSION: &str = "exe";
}
// The fallback when none of the other gates match.
#[else]
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = "";
pub const DLL_EXTENSION: &str = "";
pub const EXE_SUFFIX: &str = ""; pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = ""; pub const EXE_EXTENSION: &str = "";
} }
#[cfg(target_os = "nuttx")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "nuttx";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
} }

View file

@ -12,6 +12,7 @@ pub mod anonymous_pipe;
pub mod args; pub mod args;
pub mod backtrace; pub mod backtrace;
pub mod cmath; pub mod cmath;
pub mod env_consts;
pub mod exit_guard; pub mod exit_guard;
pub mod fd; pub mod fd;
pub mod fs; pub mod fs;

View file

@ -1,9 +0,0 @@
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "hermit";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = "";
pub const DLL_EXTENSION: &str = "";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}

View file

@ -18,7 +18,6 @@
use crate::os::raw::c_char; use crate::os::raw::c_char;
pub mod env;
pub mod futex; pub mod futex;
pub mod os; pub mod os;
#[path = "../unsupported/pipe.rs"] #[path = "../unsupported/pipe.rs"]

View file

@ -1,9 +0,0 @@
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = ".sgxs";
pub const DLL_EXTENSION: &str = "sgxs";
pub const EXE_SUFFIX: &str = ".sgxs";
pub const EXE_EXTENSION: &str = "sgxs";
}

View file

@ -9,7 +9,6 @@ use crate::io::ErrorKind;
use crate::sync::atomic::{AtomicBool, Ordering}; use crate::sync::atomic::{AtomicBool, Ordering};
pub mod abi; pub mod abi;
pub mod env;
mod libunwind_integration; mod libunwind_integration;
pub mod os; pub mod os;
#[path = "../unsupported/pipe.rs"] #[path = "../unsupported/pipe.rs"]

View file

@ -1,9 +0,0 @@
pub mod os {
pub const FAMILY: &str = "itron";
pub const OS: &str = "solid";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}

View file

@ -16,7 +16,6 @@ pub mod itron {
use super::unsupported; use super::unsupported;
} }
pub mod env;
// `error` is `pub(crate)` so that it can be accessed by `itron/error.rs` as // `error` is `pub(crate)` so that it can be accessed by `itron/error.rs` as
// `crate::sys::error` // `crate::sys::error`
pub(crate) mod error; pub(crate) mod error;

View file

@ -6,9 +6,6 @@
#![allow(unused_variables)] #![allow(unused_variables)]
#![allow(dead_code)] #![allow(dead_code)]
#[path = "../unsupported/env.rs"]
pub mod env;
//pub mod fd;
pub mod os; pub mod os;
#[path = "../unsupported/pipe.rs"] #[path = "../unsupported/pipe.rs"]
pub mod pipe; pub mod pipe;

View file

@ -3,8 +3,6 @@
#[path = "../unsupported/common.rs"] #[path = "../unsupported/common.rs"]
#[deny(unsafe_op_in_unsafe_fn)] #[deny(unsafe_op_in_unsafe_fn)]
mod common; mod common;
#[path = "../unsupported/env.rs"]
pub mod env;
#[path = "../unsupported/os.rs"] #[path = "../unsupported/os.rs"]
pub mod os; pub mod os;
#[path = "../unsupported/pipe.rs"] #[path = "../unsupported/pipe.rs"]

View file

@ -1,9 +0,0 @@
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "uefi";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = "";
pub const DLL_EXTENSION: &str = "";
pub const EXE_SUFFIX: &str = ".efi";
pub const EXE_EXTENSION: &str = "efi";
}

View file

@ -13,7 +13,6 @@
//! [`OsString`]: crate::ffi::OsString //! [`OsString`]: crate::ffi::OsString
#![forbid(unsafe_op_in_unsafe_fn)] #![forbid(unsafe_op_in_unsafe_fn)]
pub mod env;
pub mod helpers; pub mod helpers;
pub mod os; pub mod os;
#[path = "../unsupported/pipe.rs"] #[path = "../unsupported/pipe.rs"]

View file

@ -6,7 +6,6 @@ use crate::io::ErrorKind;
#[macro_use] #[macro_use]
pub mod weak; pub mod weak;
pub mod env;
#[cfg(target_os = "fuchsia")] #[cfg(target_os = "fuchsia")]
pub mod fuchsia; pub mod fuchsia;
pub mod futex; pub mod futex;

View file

@ -1,9 +0,0 @@
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = "";
pub const DLL_EXTENSION: &str = "";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}

View file

@ -1,6 +1,5 @@
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
pub mod env;
pub mod os; pub mod os;
pub mod pipe; pub mod pipe;
pub mod thread; pub mod thread;

View file

@ -1,11 +0,0 @@
#![forbid(unsafe_op_in_unsafe_fn)]
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = ".wasm";
pub const DLL_EXTENSION: &str = "wasm";
pub const EXE_SUFFIX: &str = ".wasm";
pub const EXE_EXTENSION: &str = "wasm";
}

View file

@ -13,7 +13,6 @@
//! compiling for wasm. That way it's a compile time error for something that's //! compiling for wasm. That way it's a compile time error for something that's
//! guaranteed to be a runtime error! //! guaranteed to be a runtime error!
pub mod env;
#[allow(unused)] #[allow(unused)]
#[path = "../wasm/atomics/futex.rs"] #[path = "../wasm/atomics/futex.rs"]
pub mod futex; pub mod futex;

View file

@ -6,8 +6,6 @@
//! To begin with, this target mirrors the wasi target 1 to 1, but over //! To begin with, this target mirrors the wasi target 1 to 1, but over
//! time this will change significantly. //! time this will change significantly.
#[path = "../wasi/env.rs"]
pub mod env;
#[allow(unused)] #[allow(unused)]
#[path = "../wasm/atomics/futex.rs"] #[path = "../wasm/atomics/futex.rs"]
pub mod futex; pub mod futex;

View file

@ -1,9 +0,0 @@
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = ".wasm";
pub const DLL_EXTENSION: &str = "wasm";
pub const EXE_SUFFIX: &str = ".wasm";
pub const EXE_EXTENSION: &str = "wasm";
}

View file

@ -16,7 +16,6 @@
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
pub mod env;
#[path = "../unsupported/os.rs"] #[path = "../unsupported/os.rs"]
pub mod os; pub mod os;
#[path = "../unsupported/pipe.rs"] #[path = "../unsupported/pipe.rs"]

View file

@ -1,9 +0,0 @@
pub mod os {
pub const FAMILY: &str = "windows";
pub const OS: &str = "windows";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = ".dll";
pub const DLL_EXTENSION: &str = "dll";
pub const EXE_SUFFIX: &str = ".exe";
pub const EXE_EXTENSION: &str = "exe";
}

View file

@ -15,7 +15,6 @@ pub mod compat;
pub mod api; pub mod api;
pub mod c; pub mod c;
pub mod env;
#[cfg(not(target_vendor = "win7"))] #[cfg(not(target_vendor = "win7"))]
pub mod futex; pub mod futex;
pub mod handle; pub mod handle;

View file

@ -1,7 +1,5 @@
#![forbid(unsafe_op_in_unsafe_fn)] #![forbid(unsafe_op_in_unsafe_fn)]
#[path = "../unsupported/env.rs"]
pub mod env;
pub mod os; pub mod os;
#[path = "../unsupported/pipe.rs"] #[path = "../unsupported/pipe.rs"]
pub mod pipe; pub mod pipe;

View file

@ -11,7 +11,6 @@
pub const WORD_SIZE: usize = size_of::<u32>(); pub const WORD_SIZE: usize = size_of::<u32>();
pub mod abi; pub mod abi;
pub mod env;
pub mod os; pub mod os;
#[path = "../unsupported/pipe.rs"] #[path = "../unsupported/pipe.rs"]
pub mod pipe; pub mod pipe;

View file

@ -6,6 +6,7 @@
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::{BTreeSet, HashMap, HashSet}; use std::collections::{BTreeSet, HashMap, HashSet};
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::hash::Hash;
use std::io::IsTerminal; use std::io::IsTerminal;
use std::path::{Path, PathBuf, absolute}; use std::path::{Path, PathBuf, absolute};
use std::process::Command; use std::process::Command;
@ -701,6 +702,7 @@ pub(crate) struct TomlConfig {
target: Option<HashMap<String, TomlTarget>>, target: Option<HashMap<String, TomlTarget>>,
dist: Option<Dist>, dist: Option<Dist>,
profile: Option<String>, profile: Option<String>,
include: Option<Vec<PathBuf>>,
} }
/// This enum is used for deserializing change IDs from TOML, allowing both numeric values and the string `"ignore"`. /// This enum is used for deserializing change IDs from TOML, allowing both numeric values and the string `"ignore"`.
@ -747,27 +749,35 @@ enum ReplaceOpt {
} }
trait Merge { trait Merge {
fn merge(&mut self, other: Self, replace: ReplaceOpt); fn merge(
&mut self,
parent_config_path: Option<PathBuf>,
included_extensions: &mut HashSet<PathBuf>,
other: Self,
replace: ReplaceOpt,
);
} }
impl Merge for TomlConfig { impl Merge for TomlConfig {
fn merge( fn merge(
&mut self, &mut self,
TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id }: Self, parent_config_path: Option<PathBuf>,
included_extensions: &mut HashSet<PathBuf>,
TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id, include }: Self,
replace: ReplaceOpt, replace: ReplaceOpt,
) { ) {
fn do_merge<T: Merge>(x: &mut Option<T>, y: Option<T>, replace: ReplaceOpt) { fn do_merge<T: Merge>(x: &mut Option<T>, y: Option<T>, replace: ReplaceOpt) {
if let Some(new) = y { if let Some(new) = y {
if let Some(original) = x { if let Some(original) = x {
original.merge(new, replace); original.merge(None, &mut Default::default(), new, replace);
} else { } else {
*x = Some(new); *x = Some(new);
} }
} }
} }
self.change_id.inner.merge(change_id.inner, replace); self.change_id.inner.merge(None, &mut Default::default(), change_id.inner, replace);
self.profile.merge(profile, replace); self.profile.merge(None, &mut Default::default(), profile, replace);
do_merge(&mut self.build, build, replace); do_merge(&mut self.build, build, replace);
do_merge(&mut self.install, install, replace); do_merge(&mut self.install, install, replace);
@ -782,13 +792,50 @@ impl Merge for TomlConfig {
(Some(original_target), Some(new_target)) => { (Some(original_target), Some(new_target)) => {
for (triple, new) in new_target { for (triple, new) in new_target {
if let Some(original) = original_target.get_mut(&triple) { if let Some(original) = original_target.get_mut(&triple) {
original.merge(new, replace); original.merge(None, &mut Default::default(), new, replace);
} else { } else {
original_target.insert(triple, new); original_target.insert(triple, new);
} }
} }
} }
} }
let parent_dir = parent_config_path
.as_ref()
.and_then(|p| p.parent().map(ToOwned::to_owned))
.unwrap_or_default();
// `include` handled later since we ignore duplicates using `ReplaceOpt::IgnoreDuplicate` to
// keep the upper-level configuration to take precedence.
for include_path in include.clone().unwrap_or_default().iter().rev() {
let include_path = parent_dir.join(include_path);
let include_path = include_path.canonicalize().unwrap_or_else(|e| {
eprintln!("ERROR: Failed to canonicalize '{}' path: {e}", include_path.display());
exit!(2);
});
let included_toml = Config::get_toml_inner(&include_path).unwrap_or_else(|e| {
eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display());
exit!(2);
});
assert!(
included_extensions.insert(include_path.clone()),
"Cyclic inclusion detected: '{}' is being included again before its previous inclusion was fully processed.",
include_path.display()
);
self.merge(
Some(include_path.clone()),
included_extensions,
included_toml,
// Ensures that parent configuration always takes precedence
// over child configurations.
ReplaceOpt::IgnoreDuplicate,
);
included_extensions.remove(&include_path);
}
} }
} }
@ -803,7 +850,13 @@ macro_rules! define_config {
} }
impl Merge for $name { impl Merge for $name {
fn merge(&mut self, other: Self, replace: ReplaceOpt) { fn merge(
&mut self,
_parent_config_path: Option<PathBuf>,
_included_extensions: &mut HashSet<PathBuf>,
other: Self,
replace: ReplaceOpt
) {
$( $(
match replace { match replace {
ReplaceOpt::IgnoreDuplicate => { ReplaceOpt::IgnoreDuplicate => {
@ -903,7 +956,13 @@ macro_rules! define_config {
} }
impl<T> Merge for Option<T> { impl<T> Merge for Option<T> {
fn merge(&mut self, other: Self, replace: ReplaceOpt) { fn merge(
&mut self,
_parent_config_path: Option<PathBuf>,
_included_extensions: &mut HashSet<PathBuf>,
other: Self,
replace: ReplaceOpt,
) {
match replace { match replace {
ReplaceOpt::IgnoreDuplicate => { ReplaceOpt::IgnoreDuplicate => {
if self.is_none() { if self.is_none() {
@ -1363,13 +1422,15 @@ impl Config {
Self::get_toml(&builder_config_path) Self::get_toml(&builder_config_path)
} }
#[cfg(test)] pub(crate) fn get_toml(file: &Path) -> Result<TomlConfig, toml::de::Error> {
pub(crate) fn get_toml(_: &Path) -> Result<TomlConfig, toml::de::Error> { #[cfg(test)]
Ok(TomlConfig::default()) return Ok(TomlConfig::default());
#[cfg(not(test))]
Self::get_toml_inner(file)
} }
#[cfg(not(test))] fn get_toml_inner(file: &Path) -> Result<TomlConfig, toml::de::Error> {
pub(crate) fn get_toml(file: &Path) -> Result<TomlConfig, toml::de::Error> {
let contents = let contents =
t!(fs::read_to_string(file), format!("config file {} not found", file.display())); t!(fs::read_to_string(file), format!("config file {} not found", file.display()));
// Deserialize to Value and then TomlConfig to prevent the Deserialize impl of // Deserialize to Value and then TomlConfig to prevent the Deserialize impl of
@ -1548,7 +1609,8 @@ impl Config {
// but not if `bootstrap.toml` hasn't been created. // but not if `bootstrap.toml` hasn't been created.
let mut toml = if !using_default_path || toml_path.exists() { let mut toml = if !using_default_path || toml_path.exists() {
config.config = Some(if cfg!(not(test)) { config.config = Some(if cfg!(not(test)) {
toml_path.canonicalize().unwrap() toml_path = toml_path.canonicalize().unwrap();
toml_path.clone()
} else { } else {
toml_path.clone() toml_path.clone()
}); });
@ -1576,6 +1638,26 @@ impl Config {
toml.profile = Some("dist".into()); toml.profile = Some("dist".into());
} }
// Reverse the list to ensure the last added config extension remains the most dominant.
// For example, given ["a.toml", "b.toml"], "b.toml" should take precedence over "a.toml".
//
// This must be handled before applying the `profile` since `include`s should always take
// precedence over `profile`s.
for include_path in toml.include.clone().unwrap_or_default().iter().rev() {
let include_path = toml_path.parent().unwrap().join(include_path);
let included_toml = get_toml(&include_path).unwrap_or_else(|e| {
eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display());
exit!(2);
});
toml.merge(
Some(include_path),
&mut Default::default(),
included_toml,
ReplaceOpt::IgnoreDuplicate,
);
}
if let Some(include) = &toml.profile { if let Some(include) = &toml.profile {
// Allows creating alias for profile names, allowing // Allows creating alias for profile names, allowing
// profiles to be renamed while maintaining back compatibility // profiles to be renamed while maintaining back compatibility
@ -1597,7 +1679,12 @@ impl Config {
); );
exit!(2); exit!(2);
}); });
toml.merge(included_toml, ReplaceOpt::IgnoreDuplicate); toml.merge(
Some(include_path),
&mut Default::default(),
included_toml,
ReplaceOpt::IgnoreDuplicate,
);
} }
let mut override_toml = TomlConfig::default(); let mut override_toml = TomlConfig::default();
@ -1608,7 +1695,12 @@ impl Config {
let mut err = match get_table(option) { let mut err = match get_table(option) {
Ok(v) => { Ok(v) => {
override_toml.merge(v, ReplaceOpt::ErrorOnDuplicate); override_toml.merge(
None,
&mut Default::default(),
v,
ReplaceOpt::ErrorOnDuplicate,
);
continue; continue;
} }
Err(e) => e, Err(e) => e,
@ -1619,7 +1711,12 @@ impl Config {
if !value.contains('"') { if !value.contains('"') {
match get_table(&format!(r#"{key}="{value}""#)) { match get_table(&format!(r#"{key}="{value}""#)) {
Ok(v) => { Ok(v) => {
override_toml.merge(v, ReplaceOpt::ErrorOnDuplicate); override_toml.merge(
None,
&mut Default::default(),
v,
ReplaceOpt::ErrorOnDuplicate,
);
continue; continue;
} }
Err(e) => err = e, Err(e) => err = e,
@ -1629,7 +1726,7 @@ impl Config {
eprintln!("failed to parse override `{option}`: `{err}"); eprintln!("failed to parse override `{option}`: `{err}");
exit!(2) exit!(2)
} }
toml.merge(override_toml, ReplaceOpt::Override); toml.merge(None, &mut Default::default(), override_toml, ReplaceOpt::Override);
config.change_id = toml.change_id.inner; config.change_id = toml.change_id.inner;

View file

@ -1,8 +1,8 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::env;
use std::fs::{File, remove_file}; use std::fs::{File, remove_file};
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::{Path, PathBuf};
use std::{env, fs};
use build_helper::ci::CiEnv; use build_helper::ci::CiEnv;
use clap::CommandFactory; use clap::CommandFactory;
@ -23,6 +23,27 @@ pub(crate) fn parse(config: &str) -> Config {
) )
} }
fn get_toml(file: &Path) -> Result<TomlConfig, toml::de::Error> {
let contents = std::fs::read_to_string(file).unwrap();
toml::from_str(&contents).and_then(|table: toml::Value| TomlConfig::deserialize(table))
}
/// Helps with debugging by using consistent test-specific directories instead of
/// random temporary directories.
fn prepare_test_specific_dir() -> PathBuf {
let current = std::thread::current();
// Replace "::" with "_" to make it safe for directory names on Windows systems
let test_path = current.name().unwrap().replace("::", "_");
let testdir = parse("").tempdir().join(test_path);
// clean up any old test files
let _ = fs::remove_dir_all(&testdir);
let _ = fs::create_dir_all(&testdir);
testdir
}
#[test] #[test]
fn download_ci_llvm() { fn download_ci_llvm() {
let config = parse("llvm.download-ci-llvm = false"); let config = parse("llvm.download-ci-llvm = false");
@ -539,3 +560,189 @@ fn test_ci_flag() {
let config = Config::parse_inner(Flags::parse(&["check".into()]), |&_| toml::from_str("")); let config = Config::parse_inner(Flags::parse(&["check".into()]), |&_| toml::from_str(""));
assert_eq!(config.is_running_on_ci, CiEnv::is_ci()); assert_eq!(config.is_running_on_ci, CiEnv::is_ci());
} }
#[test]
fn test_precedence_of_includes() {
let testdir = prepare_test_specific_dir();
let root_config = testdir.join("config.toml");
let root_config_content = br#"
include = ["./extension.toml"]
[llvm]
link-jobs = 2
"#;
File::create(&root_config).unwrap().write_all(root_config_content).unwrap();
let extension = testdir.join("extension.toml");
let extension_content = br#"
change-id=543
include = ["./extension2.toml"]
"#;
File::create(extension).unwrap().write_all(extension_content).unwrap();
let extension = testdir.join("extension2.toml");
let extension_content = br#"
change-id=742
[llvm]
link-jobs = 10
[build]
description = "Some creative description"
"#;
File::create(extension).unwrap().write_all(extension_content).unwrap();
let config = Config::parse_inner(
Flags::parse(&["check".to_owned(), format!("--config={}", root_config.to_str().unwrap())]),
get_toml,
);
assert_eq!(config.change_id.unwrap(), ChangeId::Id(543));
assert_eq!(config.llvm_link_jobs.unwrap(), 2);
assert_eq!(config.description.unwrap(), "Some creative description");
}
#[test]
#[should_panic(expected = "Cyclic inclusion detected")]
fn test_cyclic_include_direct() {
let testdir = prepare_test_specific_dir();
let root_config = testdir.join("config.toml");
let root_config_content = br#"
include = ["./extension.toml"]
"#;
File::create(&root_config).unwrap().write_all(root_config_content).unwrap();
let extension = testdir.join("extension.toml");
let extension_content = br#"
include = ["./config.toml"]
"#;
File::create(extension).unwrap().write_all(extension_content).unwrap();
let config = Config::parse_inner(
Flags::parse(&["check".to_owned(), format!("--config={}", root_config.to_str().unwrap())]),
get_toml,
);
}
#[test]
#[should_panic(expected = "Cyclic inclusion detected")]
fn test_cyclic_include_indirect() {
let testdir = prepare_test_specific_dir();
let root_config = testdir.join("config.toml");
let root_config_content = br#"
include = ["./extension.toml"]
"#;
File::create(&root_config).unwrap().write_all(root_config_content).unwrap();
let extension = testdir.join("extension.toml");
let extension_content = br#"
include = ["./extension2.toml"]
"#;
File::create(extension).unwrap().write_all(extension_content).unwrap();
let extension = testdir.join("extension2.toml");
let extension_content = br#"
include = ["./extension3.toml"]
"#;
File::create(extension).unwrap().write_all(extension_content).unwrap();
let extension = testdir.join("extension3.toml");
let extension_content = br#"
include = ["./extension.toml"]
"#;
File::create(extension).unwrap().write_all(extension_content).unwrap();
let config = Config::parse_inner(
Flags::parse(&["check".to_owned(), format!("--config={}", root_config.to_str().unwrap())]),
get_toml,
);
}
#[test]
fn test_include_absolute_paths() {
let testdir = prepare_test_specific_dir();
let extension = testdir.join("extension.toml");
File::create(&extension).unwrap().write_all(&[]).unwrap();
let root_config = testdir.join("config.toml");
let extension_absolute_path =
extension.canonicalize().unwrap().to_str().unwrap().replace('\\', r"\\");
let root_config_content = format!(r#"include = ["{}"]"#, extension_absolute_path);
File::create(&root_config).unwrap().write_all(root_config_content.as_bytes()).unwrap();
let config = Config::parse_inner(
Flags::parse(&["check".to_owned(), format!("--config={}", root_config.to_str().unwrap())]),
get_toml,
);
}
#[test]
fn test_include_relative_paths() {
let testdir = prepare_test_specific_dir();
let _ = fs::create_dir_all(&testdir.join("subdir/another_subdir"));
let root_config = testdir.join("config.toml");
let root_config_content = br#"
include = ["./subdir/extension.toml"]
"#;
File::create(&root_config).unwrap().write_all(root_config_content).unwrap();
let extension = testdir.join("subdir/extension.toml");
let extension_content = br#"
include = ["../extension2.toml"]
"#;
File::create(extension).unwrap().write_all(extension_content).unwrap();
let extension = testdir.join("extension2.toml");
let extension_content = br#"
include = ["./subdir/another_subdir/extension3.toml"]
"#;
File::create(extension).unwrap().write_all(extension_content).unwrap();
let extension = testdir.join("subdir/another_subdir/extension3.toml");
let extension_content = br#"
include = ["../../extension4.toml"]
"#;
File::create(extension).unwrap().write_all(extension_content).unwrap();
let extension = testdir.join("extension4.toml");
File::create(extension).unwrap().write_all(&[]).unwrap();
let config = Config::parse_inner(
Flags::parse(&["check".to_owned(), format!("--config={}", root_config.to_str().unwrap())]),
get_toml,
);
}
#[test]
fn test_include_precedence_over_profile() {
let testdir = prepare_test_specific_dir();
let root_config = testdir.join("config.toml");
let root_config_content = br#"
profile = "dist"
include = ["./extension.toml"]
"#;
File::create(&root_config).unwrap().write_all(root_config_content).unwrap();
let extension = testdir.join("extension.toml");
let extension_content = br#"
[rust]
channel = "dev"
"#;
File::create(extension).unwrap().write_all(extension_content).unwrap();
let config = Config::parse_inner(
Flags::parse(&["check".to_owned(), format!("--config={}", root_config.to_str().unwrap())]),
get_toml,
);
// "dist" profile would normally set the channel to "auto-detect", but includes should
// override profile settings, so we expect this to be "dev" here.
assert_eq!(config.channel, "dev");
}

View file

@ -396,4 +396,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
severity: ChangeSeverity::Info, severity: ChangeSeverity::Info,
summary: "Added a new option `build.compiletest-use-stage0-libtest` to force `compiletest` to use the stage 0 libtest.", summary: "Added a new option `build.compiletest-use-stage0-libtest` to force `compiletest` to use the stage 0 libtest.",
}, },
ChangeInfo {
change_id: 138934,
severity: ChangeSeverity::Info,
summary: "Added new option `include` to create config extensions.",
},
]; ];

View file

@ -64,12 +64,63 @@ version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "askama"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4744ed2eef2645831b441d8f5459689ade2ab27c854488fbab1fbe94fce1a7"
dependencies = [
"askama_derive",
"itoa",
"percent-encoding",
"serde",
"serde_json",
]
[[package]]
name = "askama_derive"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d661e0f57be36a5c14c48f78d09011e67e0cb618f269cca9f2fd8d15b68c46ac"
dependencies = [
"askama_parser",
"basic-toml",
"memchr",
"proc-macro2",
"quote",
"rustc-hash",
"serde",
"serde_derive",
"syn",
]
[[package]]
name = "askama_parser"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf315ce6524c857bb129ff794935cf6d42c82a6cff60526fe2a63593de4d0d4f"
dependencies = [
"memchr",
"serde",
"serde_derive",
"winnow",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "basic-toml"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "build_helper" name = "build_helper"
version = "0.1.0" version = "0.1.0"
@ -104,6 +155,7 @@ name = "citool"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"askama",
"build_helper", "build_helper",
"clap", "clap",
"csv", "csv",
@ -646,6 +698,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.23" version = "0.23.23"
@ -1026,6 +1084,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "write16" name = "write16"
version = "1.0.0" version = "1.0.0"

View file

@ -5,6 +5,7 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
askama = "0.13"
clap = { version = "4.5", features = ["derive"] } clap = { version = "4.5", features = ["derive"] }
csv = "1" csv = "1"
diff = "0.1" diff = "0.1"

View file

@ -8,9 +8,9 @@ use build_helper::metrics::{
}; };
use crate::github::JobInfoResolver; use crate::github::JobInfoResolver;
use crate::metrics;
use crate::metrics::{JobMetrics, JobName, get_test_suites}; use crate::metrics::{JobMetrics, JobName, get_test_suites};
use crate::utils::{output_details, pluralize}; use crate::utils::{output_details, pluralize};
use crate::{metrics, utils};
/// Outputs durations of individual bootstrap steps from the gathered bootstrap invocations, /// Outputs durations of individual bootstrap steps from the gathered bootstrap invocations,
/// and also a table with summarized information about executed tests. /// and also a table with summarized information about executed tests.
@ -394,18 +394,17 @@ fn aggregate_tests(metrics: &JsonRoot) -> TestSuiteData {
// Poor man's detection of doctests based on the "(line XYZ)" suffix // Poor man's detection of doctests based on the "(line XYZ)" suffix
let is_doctest = matches!(suite.metadata, TestSuiteMetadata::CargoPackage { .. }) let is_doctest = matches!(suite.metadata, TestSuiteMetadata::CargoPackage { .. })
&& test.name.contains("(line"); && test.name.contains("(line");
let test_entry = Test { name: generate_test_name(&test.name), stage, is_doctest }; let test_entry = Test {
name: utils::normalize_path_delimiters(&test.name).to_string(),
stage,
is_doctest,
};
tests.insert(test_entry, test.outcome.clone()); tests.insert(test_entry, test.outcome.clone());
} }
} }
TestSuiteData { tests } TestSuiteData { tests }
} }
/// Normalizes Windows-style path delimiters to Unix-style paths.
fn generate_test_name(name: &str) -> String {
name.replace('\\', "/")
}
/// Prints test changes in Markdown format to stdout. /// Prints test changes in Markdown format to stdout.
fn report_test_diffs( fn report_test_diffs(
diff: AggregatedTestDiffs, diff: AggregatedTestDiffs,

View file

@ -4,6 +4,7 @@ mod datadog;
mod github; mod github;
mod jobs; mod jobs;
mod metrics; mod metrics;
mod test_dashboard;
mod utils; mod utils;
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
@ -22,7 +23,8 @@ use crate::datadog::upload_datadog_metric;
use crate::github::JobInfoResolver; use crate::github::JobInfoResolver;
use crate::jobs::RunType; use crate::jobs::RunType;
use crate::metrics::{JobMetrics, download_auto_job_metrics, download_job_metrics, load_metrics}; use crate::metrics::{JobMetrics, download_auto_job_metrics, download_job_metrics, load_metrics};
use crate::utils::load_env_var; use crate::test_dashboard::generate_test_dashboard;
use crate::utils::{load_env_var, output_details};
const CI_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/.."); const CI_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/..");
const DOCKER_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../docker"); const DOCKER_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../docker");
@ -180,12 +182,26 @@ fn postprocess_metrics(
} }
fn post_merge_report(db: JobDatabase, current: String, parent: String) -> anyhow::Result<()> { fn post_merge_report(db: JobDatabase, current: String, parent: String) -> anyhow::Result<()> {
let metrics = download_auto_job_metrics(&db, &parent, &current)?; let metrics = download_auto_job_metrics(&db, Some(&parent), &current)?;
println!("\nComparing {parent} (parent) -> {current} (this PR)\n"); println!("\nComparing {parent} (parent) -> {current} (this PR)\n");
let mut job_info_resolver = JobInfoResolver::new(); let mut job_info_resolver = JobInfoResolver::new();
output_test_diffs(&metrics, &mut job_info_resolver); output_test_diffs(&metrics, &mut job_info_resolver);
output_details("Test dashboard", || {
println!(
r#"\nRun
```bash
cargo run --manifest-path src/ci/citool/Cargo.toml -- \
test-dashboard {current} --output-dir test-dashboard
```
And then open `test-dashboard/index.html` in your browser to see an overview of all executed tests.
"#
);
});
output_largest_duration_changes(&metrics, &mut job_info_resolver); output_largest_duration_changes(&metrics, &mut job_info_resolver);
Ok(()) Ok(())
@ -234,6 +250,14 @@ enum Args {
/// Current commit that will be compared to `parent`. /// Current commit that will be compared to `parent`.
current: String, current: String,
}, },
/// Generate a directory containing a HTML dashboard of test results from a CI run.
TestDashboard {
/// Commit SHA that was tested on CI to analyze.
current: String,
/// Output path for the HTML directory.
#[clap(long)]
output_dir: PathBuf,
},
} }
#[derive(clap::ValueEnum, Clone)] #[derive(clap::ValueEnum, Clone)]
@ -275,7 +299,11 @@ fn main() -> anyhow::Result<()> {
postprocess_metrics(metrics_path, parent, job_name)?; postprocess_metrics(metrics_path, parent, job_name)?;
} }
Args::PostMergeReport { current, parent } => { Args::PostMergeReport { current, parent } => {
post_merge_report(load_db(default_jobs_file)?, current, parent)?; post_merge_report(load_db(&default_jobs_file)?, current, parent)?;
}
Args::TestDashboard { current, output_dir } => {
let db = load_db(&default_jobs_file)?;
generate_test_dashboard(db, &current, &output_dir)?;
} }
} }

View file

@ -46,24 +46,25 @@ pub struct JobMetrics {
/// `parent` and `current` should be commit SHAs. /// `parent` and `current` should be commit SHAs.
pub fn download_auto_job_metrics( pub fn download_auto_job_metrics(
job_db: &JobDatabase, job_db: &JobDatabase,
parent: &str, parent: Option<&str>,
current: &str, current: &str,
) -> anyhow::Result<HashMap<JobName, JobMetrics>> { ) -> anyhow::Result<HashMap<JobName, JobMetrics>> {
let mut jobs = HashMap::default(); let mut jobs = HashMap::default();
for job in &job_db.auto_jobs { for job in &job_db.auto_jobs {
eprintln!("Downloading metrics of job {}", job.name); eprintln!("Downloading metrics of job {}", job.name);
let metrics_parent = match download_job_metrics(&job.name, parent) { let metrics_parent =
Ok(metrics) => Some(metrics), parent.and_then(|parent| match download_job_metrics(&job.name, parent) {
Err(error) => { Ok(metrics) => Some(metrics),
eprintln!( Err(error) => {
r#"Did not find metrics for job `{}` at `{parent}`: {error:?}. eprintln!(
r#"Did not find metrics for job `{}` at `{parent}`: {error:?}.
Maybe it was newly added?"#, Maybe it was newly added?"#,
job.name job.name
); );
None None
} }
}; });
let metrics_current = download_job_metrics(&job.name, current)?; let metrics_current = download_job_metrics(&job.name, current)?;
jobs.insert( jobs.insert(
job.name.clone(), job.name.clone(),

View file

@ -0,0 +1,216 @@
use std::collections::{BTreeMap, HashMap};
use std::fs::File;
use std::io::BufWriter;
use std::path::{Path, PathBuf};
use askama::Template;
use build_helper::metrics::{TestOutcome, TestSuiteMetadata};
use crate::jobs::JobDatabase;
use crate::metrics::{JobMetrics, JobName, download_auto_job_metrics, get_test_suites};
use crate::utils::normalize_path_delimiters;
/// Generate a set of HTML files into a directory that contain a dashboard of test results.
pub fn generate_test_dashboard(
db: JobDatabase,
current: &str,
output_dir: &Path,
) -> anyhow::Result<()> {
let metrics = download_auto_job_metrics(&db, None, current)?;
let suites = gather_test_suites(&metrics);
std::fs::create_dir_all(output_dir)?;
let test_count = suites.test_count();
write_page(output_dir, "index.html", &TestSuitesPage { suites, test_count })?;
Ok(())
}
fn write_page<T: Template>(dir: &Path, name: &str, template: &T) -> anyhow::Result<()> {
let mut file = BufWriter::new(File::create(dir.join(name))?);
Template::write_into(template, &mut file)?;
Ok(())
}
fn gather_test_suites(job_metrics: &HashMap<JobName, JobMetrics>) -> TestSuites {
struct CoarseTestSuite<'a> {
tests: BTreeMap<String, Test<'a>>,
}
let mut suites: HashMap<String, CoarseTestSuite> = HashMap::new();
// First, gather tests from all jobs, stages and targets, and aggregate them per suite
// Only work with compiletest suites.
for (job, metrics) in job_metrics {
let test_suites = get_test_suites(&metrics.current);
for suite in test_suites {
let (suite_name, stage, target) = match &suite.metadata {
TestSuiteMetadata::CargoPackage { .. } => {
continue;
}
TestSuiteMetadata::Compiletest { suite, stage, target, .. } => {
(suite.clone(), *stage, target)
}
};
let suite_entry = suites
.entry(suite_name.clone())
.or_insert_with(|| CoarseTestSuite { tests: Default::default() });
let test_metadata = TestMetadata { job, stage, target };
for test in &suite.tests {
let test_name = normalize_test_name(&test.name, &suite_name);
let (test_name, variant_name) = match test_name.rsplit_once('#') {
Some((name, variant)) => (name.to_string(), variant.to_string()),
None => (test_name, "".to_string()),
};
let test_entry = suite_entry
.tests
.entry(test_name.clone())
.or_insert_with(|| Test { revisions: Default::default() });
let variant_entry = test_entry
.revisions
.entry(variant_name)
.or_insert_with(|| TestResults { passed: vec![], ignored: vec![] });
match test.outcome {
TestOutcome::Passed => {
variant_entry.passed.push(test_metadata);
}
TestOutcome::Ignored { ignore_reason: _ } => {
variant_entry.ignored.push(test_metadata);
}
TestOutcome::Failed => {
eprintln!("Warning: failed test {test_name}");
}
}
}
}
}
// Then, split the suites per directory
let mut suites = suites.into_iter().collect::<Vec<_>>();
suites.sort_by(|a, b| a.0.cmp(&b.0));
let suites = suites
.into_iter()
.map(|(suite_name, suite)| TestSuite { group: build_test_group(&suite_name, suite.tests) })
.collect();
TestSuites { suites }
}
/// Recursively expand a test group based on filesystem hierarchy.
fn build_test_group<'a>(name: &str, tests: BTreeMap<String, Test<'a>>) -> TestGroup<'a> {
let mut root_tests = vec![];
let mut subdirs: BTreeMap<String, BTreeMap<String, Test<'a>>> = Default::default();
// Split tests into root tests and tests located in subdirectories
for (name, test) in tests {
let mut components = Path::new(&name).components().peekable();
let subdir = components.next().unwrap();
if components.peek().is_none() {
// This is a root test
root_tests.push((name, test));
} else {
// This is a test in a nested directory
let subdir_tests =
subdirs.entry(subdir.as_os_str().to_str().unwrap().to_string()).or_default();
let test_name =
components.into_iter().collect::<PathBuf>().to_str().unwrap().to_string();
subdir_tests.insert(test_name, test);
}
}
let dirs = subdirs
.into_iter()
.map(|(name, tests)| {
let group = build_test_group(&name, tests);
(name, group)
})
.collect();
TestGroup { name: name.to_string(), root_tests, groups: dirs }
}
/// Compiletest tests start with `[suite] tests/[suite]/a/b/c...`.
/// Remove the `[suite] tests/[suite]/` prefix so that we can find the filesystem path.
/// Also normalizes path delimiters.
fn normalize_test_name(name: &str, suite_name: &str) -> String {
let name = normalize_path_delimiters(name);
let name = name.as_ref();
let name = name.strip_prefix(&format!("[{suite_name}]")).unwrap_or(name).trim();
let name = name.strip_prefix("tests/").unwrap_or(name);
let name = name.strip_prefix(suite_name).unwrap_or(name);
name.trim_start_matches("/").to_string()
}
struct TestSuites<'a> {
suites: Vec<TestSuite<'a>>,
}
impl<'a> TestSuites<'a> {
fn test_count(&self) -> u64 {
self.suites.iter().map(|suite| suite.group.test_count()).sum::<u64>()
}
}
struct TestSuite<'a> {
group: TestGroup<'a>,
}
struct TestResults<'a> {
passed: Vec<TestMetadata<'a>>,
ignored: Vec<TestMetadata<'a>>,
}
struct Test<'a> {
revisions: BTreeMap<String, TestResults<'a>>,
}
impl<'a> Test<'a> {
/// If this is a test without revisions, it will have a single entry in `revisions` with
/// an empty string as the revision name.
fn single_test(&self) -> Option<&TestResults<'a>> {
if self.revisions.len() == 1 {
self.revisions.iter().next().take_if(|e| e.0.is_empty()).map(|e| e.1)
} else {
None
}
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
struct TestMetadata<'a> {
job: &'a str,
stage: u32,
target: &'a str,
}
// We have to use a template for the TestGroup instead of a macro, because
// macros cannot be recursive in askama at the moment.
#[derive(Template)]
#[template(path = "test_group.askama")]
/// Represents a group of tests
struct TestGroup<'a> {
name: String,
/// Tests located directly in this directory
root_tests: Vec<(String, Test<'a>)>,
/// Nested directories with additional tests
groups: Vec<(String, TestGroup<'a>)>,
}
impl<'a> TestGroup<'a> {
fn test_count(&self) -> u64 {
let root = self.root_tests.len() as u64;
self.groups.iter().map(|(_, group)| group.test_count()).sum::<u64>() + root
}
}
#[derive(Template)]
#[template(path = "test_suites.askama")]
struct TestSuitesPage<'a> {
suites: TestSuites<'a>,
test_count: u64,
}

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::path::Path; use std::path::Path;
use anyhow::Context; use anyhow::Context;
@ -28,3 +29,8 @@ where
func(); func();
println!("</details>\n"); println!("</details>\n");
} }
/// Normalizes Windows-style path delimiters to Unix-style paths.
pub fn normalize_path_delimiters(name: &str) -> Cow<str> {
if name.contains("\\") { name.replace('\\', "/").into() } else { name.into() }
}

View file

@ -0,0 +1,22 @@
<html>
<head>
<meta charset="UTF-8">
<title>Rust CI Test Dashboard</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
max-width: 1500px;
margin: 0 auto;
padding: 20px;
background: #F5F5F5;
}
{% block styles %}{% endblock %}
</style>
</head>
<body>
{% block content %}{% endblock %}
{% block scripts %}{% endblock %}
</body>
</html>

View file

@ -0,0 +1,42 @@
{% macro test_result(r) -%}
passed: {{ r.passed.len() }}, ignored: {{ r.ignored.len() }}
{%- endmacro %}
<li>
<details>
<summary>{{ name }} ({{ test_count() }} test{{ test_count() | pluralize }}{% if !root_tests.is_empty() && root_tests.len() as u64 != test_count() -%}
, {{ root_tests.len() }} root test{{ root_tests.len() | pluralize }}
{%- endif %}{% if !groups.is_empty() -%}
, {{ groups.len() }} subdir{{ groups.len() | pluralize }}
{%- endif %})
</summary>
{% if !groups.is_empty() %}
<ul>
{% for (dir_name, subgroup) in groups %}
{{ subgroup|safe }}
{% endfor %}
</ul>
{% endif %}
{% if !root_tests.is_empty() %}
<ul>
{% for (name, test) in root_tests %}
<li>
{% if let Some(result) = test.single_test() %}
<b>{{ name }}</b> ({% call test_result(result) %})
{% else %}
<b>{{ name }}</b> ({{ test.revisions.len() }} revision{{ test.revisions.len() | pluralize }})
<ul>
{% for (revision, result) in test.revisions %}
<li>#<i>{{ revision }}</i> ({% call test_result(result) %})</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</details>
</li>

View file

@ -0,0 +1,108 @@
{% extends "layout.askama" %}
{% block content %}
<h1>Rust CI test dashboard</h1>
<div>
Here's how to interpret the "passed" and "ignored" counts:
the count includes all combinations of "stage" x "target" x "CI job where the test was executed or ignored".
</div>
<div class="test-suites">
<div class="summary">
<div>
<div class="test-count">Total tests: {{ test_count }}</div>
<div>
To find tests that haven't been executed anywhere, click on "Open all" and search for "passed: 0".
</div>
</div>
<div>
<button onclick="openAll()">Open all</button>
<button onclick="closeAll()">Close all</button>
</div>
</div>
<ul>
{% for suite in suites.suites %}
{{ suite.group|safe }}
{% endfor %}
</ul>
</div>
{% endblock %}
{% block styles %}
h1 {
text-align: center;
color: #333333;
margin-bottom: 30px;
}
.summary {
display: flex;
justify-content: space-between;
}
.test-count {
font-size: 1.2em;
}
.test-suites {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 20px;
}
ul {
padding-left: 0;
}
li {
list-style: none;
padding-left: 20px;
}
summary {
margin-bottom: 5px;
padding: 6px;
background-color: #F4F4F4;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
summary:hover {
background-color: #CFCFCF;
}
/* Style the disclosure triangles */
details > summary {
list-style: none;
position: relative;
}
details > summary::before {
content: "▶";
position: absolute;
left: -15px;
transform: rotate(0);
transition: transform 0.2s;
}
details[open] > summary::before {
transform: rotate(90deg);
}
{% endblock %}
{% block scripts %}
<script type="text/javascript">
function openAll() {
const details = document.getElementsByTagName("details");
for (const elem of details) {
elem.open = true;
}
}
function closeAll() {
const details = document.getElementsByTagName("details");
for (const elem of details) {
elem.open = false;
}
}
</script>
{% endblock %}

View file

@ -20,6 +20,43 @@ your `.git/hooks` folder as `pre-push` (without the `.sh` extension!).
You can also install the hook as a step of running `./x setup`! You can also install the hook as a step of running `./x setup`!
## Config extensions
When working on different tasks, you might need to switch between different bootstrap configurations.
Sometimes you may want to keep an old configuration for future use. But saving raw config values in
random files and manually copying and pasting them can quickly become messy, especially if you have a
long history of different configurations.
To simplify managing multiple configurations, you can create config extensions.
For example, you can create a simple config file named `cross.toml`:
```toml
[build]
build = "x86_64-unknown-linux-gnu"
host = ["i686-unknown-linux-gnu"]
target = ["i686-unknown-linux-gnu"]
[llvm]
download-ci-llvm = false
[target.x86_64-unknown-linux-gnu]
llvm-config = "/path/to/llvm-19/bin/llvm-config"
```
Then, include this in your `bootstrap.toml`:
```toml
include = ["cross.toml"]
```
You can also include extensions within extensions recursively.
**Note:** In the `include` field, the overriding logic follows a right-to-left order. For example,
in `include = ["a.toml", "b.toml"]`, extension `b.toml` overrides `a.toml`. Also, parent extensions
always overrides the inner ones.
## Configuring `rust-analyzer` for `rustc` ## Configuring `rust-analyzer` for `rustc`
### Project-local rust-analyzer setup ### Project-local rust-analyzer setup

View file

@ -247,34 +247,31 @@ See the [Clang ControlFlowIntegrity documentation][clang-cfi] for more details.
```rust,ignore (making doc tests pass cross-platform is hard) ```rust,ignore (making doc tests pass cross-platform is hard)
#![feature(naked_functions)] #![feature(naked_functions)]
use std::arch::asm; use std::arch::naked_asm;
use std::mem; use std::mem;
fn add_one(x: i32) -> i32 { fn add_one(x: i32) -> i32 {
x + 1 x + 1
} }
#[naked] #[unsafe(naked)]
pub extern "C" fn add_two(x: i32) { pub extern "C" fn add_two(x: i32) {
// x + 2 preceded by a landing pad/nop block // x + 2 preceded by a landing pad/nop block
unsafe { naked_asm!(
asm!( "
" nop
nop nop
nop nop
nop nop
nop nop
nop nop
nop nop
nop nop
nop nop
nop lea eax, [rdi+2]
lea eax, [rdi+2] ret
ret "
", );
options(noreturn)
);
}
} }
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {

View file

@ -1397,7 +1397,6 @@ ui/issues/auxiliary/issue-13620-1.rs
ui/issues/auxiliary/issue-13620-2.rs ui/issues/auxiliary/issue-13620-2.rs
ui/issues/auxiliary/issue-14344-1.rs ui/issues/auxiliary/issue-14344-1.rs
ui/issues/auxiliary/issue-14344-2.rs ui/issues/auxiliary/issue-14344-2.rs
ui/issues/auxiliary/issue-14421.rs
ui/issues/auxiliary/issue-14422.rs ui/issues/auxiliary/issue-14422.rs
ui/issues/auxiliary/issue-15562.rs ui/issues/auxiliary/issue-15562.rs
ui/issues/auxiliary/issue-16643.rs ui/issues/auxiliary/issue-16643.rs
@ -1564,7 +1563,6 @@ ui/issues/issue-14366.rs
ui/issues/issue-14382.rs ui/issues/issue-14382.rs
ui/issues/issue-14393.rs ui/issues/issue-14393.rs
ui/issues/issue-14399.rs ui/issues/issue-14399.rs
ui/issues/issue-14421.rs
ui/issues/issue-14422.rs ui/issues/issue-14422.rs
ui/issues/issue-14541.rs ui/issues/issue-14541.rs
ui/issues/issue-14721.rs ui/issues/issue-14721.rs
@ -1629,7 +1627,6 @@ ui/issues/issue-16774.rs
ui/issues/issue-16783.rs ui/issues/issue-16783.rs
ui/issues/issue-16819.rs ui/issues/issue-16819.rs
ui/issues/issue-16922-rpass.rs ui/issues/issue-16922-rpass.rs
ui/issues/issue-16939.rs
ui/issues/issue-16966.rs ui/issues/issue-16966.rs
ui/issues/issue-16994.rs ui/issues/issue-16994.rs
ui/issues/issue-17001.rs ui/issues/issue-17001.rs
@ -1867,7 +1864,6 @@ ui/issues/issue-23550.rs
ui/issues/issue-23589.rs ui/issues/issue-23589.rs
ui/issues/issue-23699.rs ui/issues/issue-23699.rs
ui/issues/issue-2380-b.rs ui/issues/issue-2380-b.rs
ui/issues/issue-23808.rs
ui/issues/issue-2383.rs ui/issues/issue-2383.rs
ui/issues/issue-23891.rs ui/issues/issue-23891.rs
ui/issues/issue-23898.rs ui/issues/issue-23898.rs
@ -2607,7 +2603,6 @@ ui/issues/issue-9249.rs
ui/issues/issue-9259.rs ui/issues/issue-9259.rs
ui/issues/issue-92741.rs ui/issues/issue-92741.rs
ui/issues/issue-9446.rs ui/issues/issue-9446.rs
ui/issues/issue-9719.rs
ui/issues/issue-9725.rs ui/issues/issue-9725.rs
ui/issues/issue-9737.rs ui/issues/issue-9737.rs
ui/issues/issue-9814.rs ui/issues/issue-9814.rs
@ -3138,7 +3133,6 @@ ui/nll/user-annotations/issue-55241.rs
ui/nll/user-annotations/issue-55748-pat-types-constrain-bindings.rs ui/nll/user-annotations/issue-55748-pat-types-constrain-bindings.rs
ui/nll/user-annotations/issue-57731-ascibed-coupled-types.rs ui/nll/user-annotations/issue-57731-ascibed-coupled-types.rs
ui/numbers-arithmetic/issue-8460.rs ui/numbers-arithmetic/issue-8460.rs
ui/on-unimplemented/issue-104140.rs
ui/or-patterns/issue-64879-trailing-before-guard.rs ui/or-patterns/issue-64879-trailing-before-guard.rs
ui/or-patterns/issue-67514-irrefutable-param.rs ui/or-patterns/issue-67514-irrefutable-param.rs
ui/or-patterns/issue-68785-irrefutable-param-with-at.rs ui/or-patterns/issue-68785-irrefutable-param-with-at.rs

View file

@ -17,7 +17,7 @@ use ignore::Walk;
const ENTRY_LIMIT: u32 = 901; const ENTRY_LIMIT: u32 = 901;
// FIXME: The following limits should be reduced eventually. // FIXME: The following limits should be reduced eventually.
const ISSUES_ENTRY_LIMIT: u32 = 1631; const ISSUES_ENTRY_LIMIT: u32 = 1626;
const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[ const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[
"rs", // test source files "rs", // test source files

View file

@ -13,8 +13,8 @@ use std::arch::naked_asm;
// LLVM implements this via making sure of that, even for functions with the naked attribute. // LLVM implements this via making sure of that, even for functions with the naked attribute.
// So, we must emit an appropriate instruction instead! // So, we must emit an appropriate instruction instead!
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
pub unsafe extern "C" fn _hlt() -> ! { pub extern "C" fn _hlt() -> ! {
// CHECK-NOT: hint #34 // CHECK-NOT: hint #34
// CHECK: hlt #0x1 // CHECK: hlt #0x1
naked_asm!("hlt #1") naked_asm!("hlt #1")

View file

@ -29,7 +29,7 @@ use minicore::*;
// CHECK-LABEL: blr: // CHECK-LABEL: blr:
// CHECK: blr // CHECK: blr
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn blr() { extern "C" fn blr() {
naked_asm!("blr") naked_asm!("blr")
} }

View file

@ -22,8 +22,8 @@ use minicore::*;
// CHECK-NOT: .size // CHECK-NOT: .size
// CHECK: end_function // CHECK: end_function
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn nop() { extern "C" fn nop() {
naked_asm!("nop") naked_asm!("nop")
} }
@ -34,11 +34,11 @@ unsafe extern "C" fn nop() {
// CHECK-NOT: .size // CHECK-NOT: .size
// CHECK: end_function // CHECK: end_function
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
#[linkage = "weak"] #[linkage = "weak"]
// wasm functions cannot be aligned, so this has no effect // wasm functions cannot be aligned, so this has no effect
#[repr(align(32))] #[repr(align(32))]
unsafe extern "C" fn weak_aligned_nop() { extern "C" fn weak_aligned_nop() {
naked_asm!("nop") naked_asm!("nop")
} }
@ -51,48 +51,48 @@ unsafe extern "C" fn weak_aligned_nop() {
// //
// CHECK-NEXT: end_function // CHECK-NEXT: end_function
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_i8_i8(num: i8) -> i8 { extern "C" fn fn_i8_i8(num: i8) -> i8 {
naked_asm!("local.get 0", "local.get 0", "i32.mul") naked_asm!("local.get 0", "local.get 0", "i32.mul")
} }
// CHECK-LABEL: fn_i8_i8_i8: // CHECK-LABEL: fn_i8_i8_i8:
// CHECK: .functype fn_i8_i8_i8 (i32, i32) -> (i32) // CHECK: .functype fn_i8_i8_i8 (i32, i32) -> (i32)
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_i8_i8_i8(a: i8, b: i8) -> i8 { extern "C" fn fn_i8_i8_i8(a: i8, b: i8) -> i8 {
naked_asm!("local.get 1", "local.get 0", "i32.mul") naked_asm!("local.get 1", "local.get 0", "i32.mul")
} }
// CHECK-LABEL: fn_unit_i8: // CHECK-LABEL: fn_unit_i8:
// CHECK: .functype fn_unit_i8 () -> (i32) // CHECK: .functype fn_unit_i8 () -> (i32)
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_unit_i8() -> i8 { extern "C" fn fn_unit_i8() -> i8 {
naked_asm!("i32.const 42") naked_asm!("i32.const 42")
} }
// CHECK-LABEL: fn_i8_unit: // CHECK-LABEL: fn_i8_unit:
// CHECK: .functype fn_i8_unit (i32) -> () // CHECK: .functype fn_i8_unit (i32) -> ()
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_i8_unit(_: i8) { extern "C" fn fn_i8_unit(_: i8) {
naked_asm!("nop") naked_asm!("nop")
} }
// CHECK-LABEL: fn_i32_i32: // CHECK-LABEL: fn_i32_i32:
// CHECK: .functype fn_i32_i32 (i32) -> (i32) // CHECK: .functype fn_i32_i32 (i32) -> (i32)
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_i32_i32(num: i32) -> i32 { extern "C" fn fn_i32_i32(num: i32) -> i32 {
naked_asm!("local.get 0", "local.get 0", "i32.mul") naked_asm!("local.get 0", "local.get 0", "i32.mul")
} }
// CHECK-LABEL: fn_i64_i64: // CHECK-LABEL: fn_i64_i64:
// CHECK: .functype fn_i64_i64 (i64) -> (i64) // CHECK: .functype fn_i64_i64 (i64) -> (i64)
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_i64_i64(num: i64) -> i64 { extern "C" fn fn_i64_i64(num: i64) -> i64 {
naked_asm!("local.get 0", "local.get 0", "i64.mul") naked_asm!("local.get 0", "local.get 0", "i64.mul")
} }
@ -101,8 +101,8 @@ unsafe extern "C" fn fn_i64_i64(num: i64) -> i64 {
// wasm64-unknown: .functype fn_i128_i128 (i64, i64, i64) -> () // wasm64-unknown: .functype fn_i128_i128 (i64, i64, i64) -> ()
#[allow(improper_ctypes_definitions)] #[allow(improper_ctypes_definitions)]
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_i128_i128(num: i128) -> i128 { extern "C" fn fn_i128_i128(num: i128) -> i128 {
naked_asm!( naked_asm!(
"local.get 0", "local.get 0",
"local.get 2", "local.get 2",
@ -117,8 +117,8 @@ unsafe extern "C" fn fn_i128_i128(num: i128) -> i128 {
// wasm32-wasip1: .functype fn_f128_f128 (i32, i64, i64) -> () // wasm32-wasip1: .functype fn_f128_f128 (i32, i64, i64) -> ()
// wasm64-unknown: .functype fn_f128_f128 (i64, i64, i64) -> () // wasm64-unknown: .functype fn_f128_f128 (i64, i64, i64) -> ()
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_f128_f128(num: f128) -> f128 { extern "C" fn fn_f128_f128(num: f128) -> f128 {
naked_asm!( naked_asm!(
"local.get 0", "local.get 0",
"local.get 2", "local.get 2",
@ -139,8 +139,8 @@ struct Compound {
// wasm32-wasip1: .functype fn_compound_compound (i32, i32) -> () // wasm32-wasip1: .functype fn_compound_compound (i32, i32) -> ()
// wasm64-unknown: .functype fn_compound_compound (i64, i64) -> () // wasm64-unknown: .functype fn_compound_compound (i64, i64) -> ()
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_compound_compound(_: Compound) -> Compound { extern "C" fn fn_compound_compound(_: Compound) -> Compound {
// this is the wasm32-wasip1 assembly // this is the wasm32-wasip1 assembly
naked_asm!( naked_asm!(
"local.get 0", "local.get 0",
@ -160,8 +160,8 @@ struct WrapperI32(i32);
// CHECK-LABEL: fn_wrapperi32_wrapperi32: // CHECK-LABEL: fn_wrapperi32_wrapperi32:
// CHECK: .functype fn_wrapperi32_wrapperi32 (i32) -> (i32) // CHECK: .functype fn_wrapperi32_wrapperi32 (i32) -> (i32)
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_wrapperi32_wrapperi32(_: WrapperI32) -> WrapperI32 { extern "C" fn fn_wrapperi32_wrapperi32(_: WrapperI32) -> WrapperI32 {
naked_asm!("local.get 0") naked_asm!("local.get 0")
} }
@ -171,8 +171,8 @@ struct WrapperI64(i64);
// CHECK-LABEL: fn_wrapperi64_wrapperi64: // CHECK-LABEL: fn_wrapperi64_wrapperi64:
// CHECK: .functype fn_wrapperi64_wrapperi64 (i64) -> (i64) // CHECK: .functype fn_wrapperi64_wrapperi64 (i64) -> (i64)
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_wrapperi64_wrapperi64(_: WrapperI64) -> WrapperI64 { extern "C" fn fn_wrapperi64_wrapperi64(_: WrapperI64) -> WrapperI64 {
naked_asm!("local.get 0") naked_asm!("local.get 0")
} }
@ -182,8 +182,8 @@ struct WrapperF32(f32);
// CHECK-LABEL: fn_wrapperf32_wrapperf32: // CHECK-LABEL: fn_wrapperf32_wrapperf32:
// CHECK: .functype fn_wrapperf32_wrapperf32 (f32) -> (f32) // CHECK: .functype fn_wrapperf32_wrapperf32 (f32) -> (f32)
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_wrapperf32_wrapperf32(_: WrapperF32) -> WrapperF32 { extern "C" fn fn_wrapperf32_wrapperf32(_: WrapperF32) -> WrapperF32 {
naked_asm!("local.get 0") naked_asm!("local.get 0")
} }
@ -193,7 +193,7 @@ struct WrapperF64(f64);
// CHECK-LABEL: fn_wrapperf64_wrapperf64: // CHECK-LABEL: fn_wrapperf64_wrapperf64:
// CHECK: .functype fn_wrapperf64_wrapperf64 (f64) -> (f64) // CHECK: .functype fn_wrapperf64_wrapperf64 (f64) -> (f64)
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn fn_wrapperf64_wrapperf64(_: WrapperF64) -> WrapperF64 { extern "C" fn fn_wrapperf64_wrapperf64(_: WrapperF64) -> WrapperF64 {
naked_asm!("local.get 0") naked_asm!("local.get 0")
} }

View file

@ -13,8 +13,8 @@ use std::arch::naked_asm;
// works by using an instruction for each possible landing site, // works by using an instruction for each possible landing site,
// and LLVM implements this via making sure of that. // and LLVM implements this via making sure of that.
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
pub unsafe extern "sysv64" fn will_halt() -> ! { pub extern "sysv64" fn will_halt() -> ! {
// CHECK-NOT: endbr{{32|64}} // CHECK-NOT: endbr{{32|64}}
// CHECK: hlt // CHECK: hlt
naked_asm!("hlt") naked_asm!("hlt")

View file

@ -8,11 +8,9 @@
#![feature(naked_functions)] #![feature(naked_functions)]
#![no_std] #![no_std]
#[naked] #[unsafe(naked)]
pub unsafe extern "C" fn c_variadic(_: usize, _: ...) { pub unsafe extern "C" fn c_variadic(_: usize, _: ...) {
// CHECK-NOT: va_start // CHECK-NOT: va_start
// CHECK-NOT: alloca // CHECK-NOT: alloca
core::arch::naked_asm! { core::arch::naked_asm!("ret")
"ret",
}
} }

View file

@ -13,10 +13,10 @@ pub fn caller() {
} }
// CHECK: declare x86_intrcc void @page_fault_handler(ptr {{.*}}, i64{{.*}}){{.*}}#[[ATTRS:[0-9]+]] // CHECK: declare x86_intrcc void @page_fault_handler(ptr {{.*}}, i64{{.*}}){{.*}}#[[ATTRS:[0-9]+]]
#[naked] #[unsafe(naked)]
#[no_mangle] #[no_mangle]
pub extern "x86-interrupt" fn page_fault_handler(_: u64, _: u64) { pub extern "x86-interrupt" fn page_fault_handler(_: u64, _: u64) {
unsafe { core::arch::naked_asm!("ud2") }; core::arch::naked_asm!("ud2")
} }
// CHECK: #[[ATTRS]] = // CHECK: #[[ATTRS]] =

View file

@ -10,8 +10,8 @@ use std::arch::naked_asm;
// CHECK-LABEL: naked_empty: // CHECK-LABEL: naked_empty:
#[repr(align(16))] #[repr(align(16))]
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
pub unsafe extern "C" fn naked_empty() { pub extern "C" fn naked_empty() {
// CHECK: ret // CHECK: ret
naked_asm!("ret"); naked_asm!("ret")
} }

View file

@ -28,21 +28,19 @@ fn test(x: u64) {
// CHECK: add rax, 1 // CHECK: add rax, 1
// CHECK: add rax, 42 // CHECK: add rax, 42
#[naked] #[unsafe(naked)]
pub extern "C" fn using_const_generics<const N: u64>(x: u64) -> u64 { pub extern "C" fn using_const_generics<const N: u64>(x: u64) -> u64 {
const M: u64 = 42; const M: u64 = 42;
unsafe { naked_asm!(
naked_asm!( "xor rax, rax",
"xor rax, rax", "add rax, rdi",
"add rax, rdi", "add rax, {}",
"add rax, {}", "add rax, {}",
"add rax, {}", "ret",
"ret", const N,
const N, const M,
const M, )
)
}
} }
trait Invert { trait Invert {
@ -60,16 +58,14 @@ impl Invert for i64 {
// CHECK: call // CHECK: call
// CHECK: ret // CHECK: ret
#[naked] #[unsafe(naked)]
#[no_mangle] #[no_mangle]
pub extern "C" fn generic_function<T: Invert>(x: i64) -> i64 { pub extern "C" fn generic_function<T: Invert>(x: i64) -> i64 {
unsafe { naked_asm!(
naked_asm!( "call {}",
"call {}", "ret",
"ret", sym <T as Invert>::invert,
sym <T as Invert>::invert, )
)
}
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -81,10 +77,10 @@ struct Foo(u64);
// CHECK: mov rax, rdi // CHECK: mov rax, rdi
impl Foo { impl Foo {
#[naked] #[unsafe(naked)]
#[no_mangle] #[no_mangle]
extern "C" fn method(self) -> u64 { extern "C" fn method(self) -> u64 {
unsafe { naked_asm!("mov rax, rdi", "ret") } naked_asm!("mov rax, rdi", "ret")
} }
} }
@ -97,10 +93,10 @@ trait Bar {
} }
impl Bar for Foo { impl Bar for Foo {
#[naked] #[unsafe(naked)]
#[no_mangle] #[no_mangle]
extern "C" fn trait_method(self) -> u64 { extern "C" fn trait_method(self) -> u64 {
unsafe { naked_asm!("mov rax, rdi", "ret") } naked_asm!("mov rax, rdi", "ret")
} }
} }
@ -109,7 +105,7 @@ impl Bar for Foo {
// CHECK: lea rax, [rdi + rsi] // CHECK: lea rax, [rdi + rsi]
// this previously ICE'd, see https://github.com/rust-lang/rust/issues/124375 // this previously ICE'd, see https://github.com/rust-lang/rust/issues/124375
#[naked] #[unsafe(naked)]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize { pub unsafe extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize {
naked_asm!("lea rax, [rdi + rsi]", "ret"); naked_asm!("lea rax, [rdi + rsi]", "ret");

View file

@ -20,8 +20,8 @@ use minicore::*;
// arm-mode: .arm // arm-mode: .arm
// thumb-mode: .thumb // thumb-mode: .thumb
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
unsafe extern "C" fn test_unspecified() { extern "C" fn test_unspecified() {
naked_asm!("bx lr"); naked_asm!("bx lr");
} }
@ -33,9 +33,9 @@ unsafe extern "C" fn test_unspecified() {
// arm-mode: .arm // arm-mode: .arm
// thumb-mode: .thumb // thumb-mode: .thumb
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
#[instruction_set(arm::t32)] #[instruction_set(arm::t32)]
unsafe extern "C" fn test_thumb() { extern "C" fn test_thumb() {
naked_asm!("bx lr"); naked_asm!("bx lr");
} }
@ -46,8 +46,8 @@ unsafe extern "C" fn test_thumb() {
// arm-mode: .arm // arm-mode: .arm
// thumb-mode: .thumb // thumb-mode: .thumb
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
#[instruction_set(arm::a32)] #[instruction_set(arm::a32)]
unsafe extern "C" fn test_arm() { extern "C" fn test_arm() {
naked_asm!("bx lr"); naked_asm!("bx lr");
} }

View file

@ -9,24 +9,24 @@
// //
// CHECK: .balign 16 // CHECK: .balign 16
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
pub unsafe extern "C" fn naked_no_explicit_align() { pub extern "C" fn naked_no_explicit_align() {
core::arch::naked_asm!("ret") core::arch::naked_asm!("ret")
} }
// CHECK: .balign 16 // CHECK: .balign 16
#[no_mangle] #[no_mangle]
#[repr(align(8))] #[repr(align(8))]
#[naked] #[unsafe(naked)]
pub unsafe extern "C" fn naked_lower_align() { pub extern "C" fn naked_lower_align() {
core::arch::naked_asm!("ret") core::arch::naked_asm!("ret")
} }
// CHECK: .balign 32 // CHECK: .balign 32
#[no_mangle] #[no_mangle]
#[repr(align(32))] #[repr(align(32))]
#[naked] #[unsafe(naked)]
pub unsafe extern "C" fn naked_higher_align() { pub extern "C" fn naked_higher_align() {
core::arch::naked_asm!("ret") core::arch::naked_asm!("ret")
} }
@ -38,7 +38,7 @@ pub unsafe extern "C" fn naked_higher_align() {
// CHECK: .balign 16 // CHECK: .balign 16
#[no_mangle] #[no_mangle]
#[cold] #[cold]
#[naked] #[unsafe(naked)]
pub unsafe extern "C" fn no_explicit_align_cold() { pub extern "C" fn no_explicit_align_cold() {
core::arch::naked_asm!("ret") core::arch::naked_asm!("ret")
} }

View file

@ -60,8 +60,8 @@ use minicore::*;
// linux,win: .att_syntax // linux,win: .att_syntax
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
pub unsafe extern "C" fn naked_empty() { pub extern "C" fn naked_empty() {
#[cfg(not(all(target_arch = "arm", target_feature = "thumb-mode")))] #[cfg(not(all(target_arch = "arm", target_feature = "thumb-mode")))]
naked_asm!("ret"); naked_asm!("ret");
@ -114,8 +114,8 @@ pub unsafe extern "C" fn naked_empty() {
// linux,win: .att_syntax // linux,win: .att_syntax
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
pub unsafe extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize { pub extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize {
#[cfg(any(target_os = "windows", target_os = "linux"))] #[cfg(any(target_os = "windows", target_os = "linux"))]
{ {
naked_asm!("lea rax, [rdi + rsi]", "ret") naked_asm!("lea rax, [rdi + rsi]", "ret")
@ -138,9 +138,9 @@ pub unsafe extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize
// thumb: .pushsection .text.some_different_name,\22ax\22, %progbits // thumb: .pushsection .text.some_different_name,\22ax\22, %progbits
// CHECK-LABEL: test_link_section: // CHECK-LABEL: test_link_section:
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
#[link_section = ".text.some_different_name"] #[link_section = ".text.some_different_name"]
pub unsafe extern "C" fn test_link_section() { pub extern "C" fn test_link_section() {
#[cfg(not(all(target_arch = "arm", target_feature = "thumb-mode")))] #[cfg(not(all(target_arch = "arm", target_feature = "thumb-mode")))]
naked_asm!("ret"); naked_asm!("ret");
@ -159,7 +159,7 @@ pub unsafe extern "C" fn test_link_section() {
// win_i686-LABEL: @fastcall_cc@4: // win_i686-LABEL: @fastcall_cc@4:
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
pub unsafe extern "fastcall" fn fastcall_cc(x: i32) -> i32 { pub extern "fastcall" fn fastcall_cc(x: i32) -> i32 {
naked_asm!("ret"); naked_asm!("ret");
} }

View file

@ -1,20 +0,0 @@
//@ known-bug: #130627
#![feature(trait_alias)]
trait Test {}
#[diagnostic::on_unimplemented(
message="message",
label="label",
note="note"
)]
trait Alias = Test;
// Use trait alias as bound on type parameter.
fn foo<T: Alias>(v: &T) {
}
pub fn main() {
foo(&1);
}

View file

@ -0,0 +1,6 @@
#![crate_name = "crateresolve1"]
#![crate_type = "lib"]
pub fn f() -> isize {
10
}

View file

@ -0,0 +1,6 @@
#![crate_name = "crateresolve1"]
#![crate_type = "lib"]
pub fn f() -> isize {
20
}

View file

@ -0,0 +1,3 @@
extern crate crateresolve1;
fn main() {}

View file

@ -0,0 +1,12 @@
error[E0464]: multiple candidates for `rlib` dependency `crateresolve1` found
--> multiple-candidates.rs:1:1
|
LL | extern crate crateresolve1;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: candidate #1: ./mylibs/libcrateresolve1-1.rlib
= note: candidate #2: ./mylibs/libcrateresolve1-2.rlib
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0464`.

View file

@ -0,0 +1,34 @@
//@ needs-symlink
//@ ignore-cross-compile
// Tests that the multiple candidate dependencies diagnostic prints relative
// paths if a relative library path was passed in.
use run_make_support::{bare_rustc, diff, rfs, rustc};
fn main() {
// Check that relative paths are preserved in the diagnostic
rfs::create_dir("mylibs");
rustc().input("crateresolve1-1.rs").out_dir("mylibs").extra_filename("-1").run();
rustc().input("crateresolve1-2.rs").out_dir("mylibs").extra_filename("-2").run();
check("./mylibs");
// Check that symlinks aren't followed when printing the diagnostic
rfs::rename("mylibs", "original");
rfs::symlink_dir("original", "mylibs");
check("./mylibs");
}
fn check(library_path: &str) {
let out = rustc()
.input("multiple-candidates.rs")
.library_search_path(library_path)
.ui_testing()
.run_fail()
.stderr_utf8();
diff()
.expected_file("multiple-candidates.stderr")
.normalize(r"\\", "/")
.actual_text("(rustc)", &out)
.run();
}

View file

@ -26,9 +26,9 @@ extern "C" fn private_vanilla() -> u32 {
42 42
} }
#[naked] #[unsafe(naked)]
extern "C" fn private_naked() -> u32 { extern "C" fn private_naked() -> u32 {
unsafe { naked_asm!("mov rax, 42", "ret") } naked_asm!("mov rax, 42", "ret")
} }
#[no_mangle] #[no_mangle]
@ -36,19 +36,19 @@ pub extern "C" fn public_vanilla() -> u32 {
42 42
} }
#[naked] #[unsafe(naked)]
#[no_mangle] #[no_mangle]
pub extern "C" fn public_naked_nongeneric() -> u32 { pub extern "C" fn public_naked_nongeneric() -> u32 {
unsafe { naked_asm!("mov rax, 42", "ret") } naked_asm!("mov rax, 42", "ret")
} }
pub extern "C" fn public_vanilla_generic<T: TraitWithConst>() -> u32 { pub extern "C" fn public_vanilla_generic<T: TraitWithConst>() -> u32 {
T::COUNT T::COUNT
} }
#[naked] #[unsafe(naked)]
pub extern "C" fn public_naked_generic<T: TraitWithConst>() -> u32 { pub extern "C" fn public_naked_generic<T: TraitWithConst>() -> u32 {
unsafe { naked_asm!("mov rax, {}", "ret", const T::COUNT) } naked_asm!("mov rax, {}", "ret", const T::COUNT)
} }
#[linkage = "external"] #[linkage = "external"]
@ -56,10 +56,10 @@ extern "C" fn vanilla_external_linkage() -> u32 {
42 42
} }
#[naked] #[unsafe(naked)]
#[linkage = "external"] #[linkage = "external"]
extern "C" fn naked_external_linkage() -> u32 { extern "C" fn naked_external_linkage() -> u32 {
unsafe { naked_asm!("mov rax, 42", "ret") } naked_asm!("mov rax, 42", "ret")
} }
#[cfg(not(windows))] #[cfg(not(windows))]
@ -68,11 +68,11 @@ extern "C" fn vanilla_weak_linkage() -> u32 {
42 42
} }
#[naked] #[unsafe(naked)]
#[cfg(not(windows))] #[cfg(not(windows))]
#[linkage = "weak"] #[linkage = "weak"]
extern "C" fn naked_weak_linkage() -> u32 { extern "C" fn naked_weak_linkage() -> u32 {
unsafe { naked_asm!("mov rax, 42", "ret") } naked_asm!("mov rax, 42", "ret")
} }
// functions that are declared in an `extern "C"` block are currently not exported // functions that are declared in an `extern "C"` block are currently not exported

View file

@ -1,8 +1,18 @@
//! Checks variations of E0057, which is the incorrect number of agruments passed into a closure
//@ check-fail
fn foo<T: Fn()>(t: T) { fn foo<T: Fn()>(t: T) {
t(1i32); t(1i32);
//~^ ERROR function takes 0 arguments but 1 argument was supplied //~^ ERROR function takes 0 arguments but 1 argument was supplied
} }
/// Regression test for <https://github.com/rust-lang/rust/issues/16939>
fn foo2<T: Fn()>(f: T) {
|t| f(t);
//~^ ERROR function takes 0 arguments but 1 argument was supplied
}
fn bar(t: impl Fn()) { fn bar(t: impl Fn()) {
t(1i32); t(1i32);
//~^ ERROR function takes 0 arguments but 1 argument was supplied //~^ ERROR function takes 0 arguments but 1 argument was supplied

View file

@ -1,11 +1,11 @@
error[E0057]: this function takes 0 arguments but 1 argument was supplied error[E0057]: this function takes 0 arguments but 1 argument was supplied
--> $DIR/exotic-calls.rs:2:5 --> $DIR/exotic-calls.rs:6:5
| |
LL | t(1i32); LL | t(1i32);
| ^ ---- unexpected argument of type `i32` | ^ ---- unexpected argument of type `i32`
| |
note: callable defined here note: callable defined here
--> $DIR/exotic-calls.rs:1:11 --> $DIR/exotic-calls.rs:5:11
| |
LL | fn foo<T: Fn()>(t: T) { LL | fn foo<T: Fn()>(t: T) {
| ^^^^ | ^^^^
@ -16,13 +16,30 @@ LL + t();
| |
error[E0057]: this function takes 0 arguments but 1 argument was supplied error[E0057]: this function takes 0 arguments but 1 argument was supplied
--> $DIR/exotic-calls.rs:7:5 --> $DIR/exotic-calls.rs:12:9
|
LL | |t| f(t);
| ^ - unexpected argument
|
note: callable defined here
--> $DIR/exotic-calls.rs:11:12
|
LL | fn foo2<T: Fn()>(f: T) {
| ^^^^
help: remove the extra argument
|
LL - |t| f(t);
LL + |t| f();
|
error[E0057]: this function takes 0 arguments but 1 argument was supplied
--> $DIR/exotic-calls.rs:17:5
| |
LL | t(1i32); LL | t(1i32);
| ^ ---- unexpected argument of type `i32` | ^ ---- unexpected argument of type `i32`
| |
note: type parameter defined here note: type parameter defined here
--> $DIR/exotic-calls.rs:6:11 --> $DIR/exotic-calls.rs:16:11
| |
LL | fn bar(t: impl Fn()) { LL | fn bar(t: impl Fn()) {
| ^^^^^^^^^ | ^^^^^^^^^
@ -33,13 +50,13 @@ LL + t();
| |
error[E0057]: this function takes 0 arguments but 1 argument was supplied error[E0057]: this function takes 0 arguments but 1 argument was supplied
--> $DIR/exotic-calls.rs:16:5 --> $DIR/exotic-calls.rs:26:5
| |
LL | baz()(1i32) LL | baz()(1i32)
| ^^^^^ ---- unexpected argument of type `i32` | ^^^^^ ---- unexpected argument of type `i32`
| |
note: opaque type defined here note: opaque type defined here
--> $DIR/exotic-calls.rs:11:13 --> $DIR/exotic-calls.rs:21:13
| |
LL | fn baz() -> impl Fn() { LL | fn baz() -> impl Fn() {
| ^^^^^^^^^ | ^^^^^^^^^
@ -50,13 +67,13 @@ LL + baz()()
| |
error[E0057]: this function takes 0 arguments but 1 argument was supplied error[E0057]: this function takes 0 arguments but 1 argument was supplied
--> $DIR/exotic-calls.rs:22:5 --> $DIR/exotic-calls.rs:32:5
| |
LL | x(1i32); LL | x(1i32);
| ^ ---- unexpected argument of type `i32` | ^ ---- unexpected argument of type `i32`
| |
note: closure defined here note: closure defined here
--> $DIR/exotic-calls.rs:21:13 --> $DIR/exotic-calls.rs:31:13
| |
LL | let x = || {}; LL | let x = || {};
| ^^ | ^^
@ -66,6 +83,6 @@ LL - x(1i32);
LL + x(); LL + x();
| |
error: aborting due to 4 previous errors error: aborting due to 5 previous errors
For more information about this error, try `rustc --explain E0057`. For more information about this error, try `rustc --explain E0057`.

View file

@ -12,24 +12,24 @@ fn main() {
test1(); test1();
} }
#[naked] #[unsafe(naked)]
extern "C" fn test1() { extern "C" fn test1() {
unsafe { naked_asm!("") } naked_asm!("")
} }
extern "C" fn test2() { extern "C" fn test2() {
unsafe { naked_asm!("") } naked_asm!("")
//~^ ERROR the `naked_asm!` macro can only be used in functions marked with `#[naked]` //~^ ERROR the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]`
} }
extern "C" fn test3() { extern "C" fn test3() {
unsafe { (|| naked_asm!(""))() } (|| naked_asm!(""))()
//~^ ERROR the `naked_asm!` macro can only be used in functions marked with `#[naked]` //~^ ERROR the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]`
} }
fn test4() { fn test4() {
async move { async move {
unsafe { naked_asm!("") } ; naked_asm!("");
//~^ ERROR the `naked_asm!` macro can only be used in functions marked with `#[naked]` //~^ ERROR the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]`
}; };
} }

View file

@ -1,20 +1,20 @@
error: the `naked_asm!` macro can only be used in functions marked with `#[naked]` error: the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]`
--> $DIR/naked-asm-outside-naked-fn.rs:21:14 --> $DIR/naked-asm-outside-naked-fn.rs:21:5
| |
LL | unsafe { naked_asm!("") } LL | naked_asm!("")
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
error: the `naked_asm!` macro can only be used in functions marked with `#[naked]` error: the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]`
--> $DIR/naked-asm-outside-naked-fn.rs:26:18 --> $DIR/naked-asm-outside-naked-fn.rs:26:9
| |
LL | unsafe { (|| naked_asm!(""))() } LL | (|| naked_asm!(""))()
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
error: the `naked_asm!` macro can only be used in functions marked with `#[naked]` error: the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]`
--> $DIR/naked-asm-outside-naked-fn.rs:32:19 --> $DIR/naked-asm-outside-naked-fn.rs:32:9
| |
LL | unsafe { naked_asm!("") } ; LL | naked_asm!("");
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
error: aborting due to 3 previous errors error: aborting due to 3 previous errors

View file

@ -5,11 +5,9 @@
use std::arch::naked_asm; use std::arch::naked_asm;
#[naked] #[unsafe(naked)]
pub extern "C" fn naked(p: char) -> u128 { pub extern "C" fn naked(p: char) -> u128 {
//~^ WARN uses type `char` //~^ WARN uses type `char`
//~| WARN uses type `u128` //~| WARN uses type `u128`
unsafe { naked_asm!("")
naked_asm!("");
}
} }

View file

@ -4,35 +4,35 @@
use std::arch::naked_asm; use std::arch::naked_asm;
#[naked] #[unsafe(naked)]
pub unsafe extern "C" fn inline_none() { pub extern "C" fn inline_none() {
naked_asm!(""); naked_asm!("");
} }
#[naked] #[unsafe(naked)]
#[inline] #[inline]
//~^ ERROR [E0736] //~^ ERROR [E0736]
pub unsafe extern "C" fn inline_hint() { pub extern "C" fn inline_hint() {
naked_asm!(""); naked_asm!("");
} }
#[naked] #[unsafe(naked)]
#[inline(always)] #[inline(always)]
//~^ ERROR [E0736] //~^ ERROR [E0736]
pub unsafe extern "C" fn inline_always() { pub extern "C" fn inline_always() {
naked_asm!(""); naked_asm!("");
} }
#[naked] #[unsafe(naked)]
#[inline(never)] #[inline(never)]
//~^ ERROR [E0736] //~^ ERROR [E0736]
pub unsafe extern "C" fn inline_never() { pub extern "C" fn inline_never() {
naked_asm!(""); naked_asm!("");
} }
#[naked] #[unsafe(naked)]
#[cfg_attr(all(), inline(never))] #[cfg_attr(all(), inline(never))]
//~^ ERROR [E0736] //~^ ERROR [E0736]
pub unsafe extern "C" fn conditional_inline_never() { pub extern "C" fn conditional_inline_never() {
naked_asm!(""); naked_asm!("");
} }

View file

@ -1,34 +1,34 @@
error[E0736]: attribute incompatible with `#[naked]` error[E0736]: attribute incompatible with `#[unsafe(naked)]`
--> $DIR/naked-functions-inline.rs:13:1 --> $DIR/naked-functions-inline.rs:13:1
| |
LL | #[naked] LL | #[unsafe(naked)]
| -------- function marked with `#[naked]` here | ---------------- function marked with `#[unsafe(naked)]` here
LL | #[inline] LL | #[inline]
| ^^^^^^^^^ the `inline` attribute is incompatible with `#[naked]` | ^^^^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]`
error[E0736]: attribute incompatible with `#[naked]` error[E0736]: attribute incompatible with `#[unsafe(naked)]`
--> $DIR/naked-functions-inline.rs:20:1 --> $DIR/naked-functions-inline.rs:20:1
| |
LL | #[naked] LL | #[unsafe(naked)]
| -------- function marked with `#[naked]` here | ---------------- function marked with `#[unsafe(naked)]` here
LL | #[inline(always)] LL | #[inline(always)]
| ^^^^^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[naked]` | ^^^^^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]`
error[E0736]: attribute incompatible with `#[naked]` error[E0736]: attribute incompatible with `#[unsafe(naked)]`
--> $DIR/naked-functions-inline.rs:27:1 --> $DIR/naked-functions-inline.rs:27:1
| |
LL | #[naked] LL | #[unsafe(naked)]
| -------- function marked with `#[naked]` here | ---------------- function marked with `#[unsafe(naked)]` here
LL | #[inline(never)] LL | #[inline(never)]
| ^^^^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[naked]` | ^^^^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]`
error[E0736]: attribute incompatible with `#[naked]` error[E0736]: attribute incompatible with `#[unsafe(naked)]`
--> $DIR/naked-functions-inline.rs:34:19 --> $DIR/naked-functions-inline.rs:34:19
| |
LL | #[naked] LL | #[unsafe(naked)]
| -------- function marked with `#[naked]` here | ---------------- function marked with `#[unsafe(naked)]` here
LL | #[cfg_attr(all(), inline(never))] LL | #[cfg_attr(all(), inline(never))]
| ^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[naked]` | ^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]`
error: aborting due to 4 previous errors error: aborting due to 4 previous errors

View file

@ -12,15 +12,15 @@ extern crate minicore;
use minicore::*; use minicore::*;
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
#[instruction_set(arm::t32)] #[instruction_set(arm::t32)]
unsafe extern "C" fn test_thumb() { extern "C" fn test_thumb() {
naked_asm!("bx lr"); naked_asm!("bx lr");
} }
#[no_mangle] #[no_mangle]
#[naked] #[unsafe(naked)]
#[instruction_set(arm::a32)] #[instruction_set(arm::a32)]
unsafe extern "C" fn test_arm() { extern "C" fn test_arm() {
naked_asm!("bx lr"); naked_asm!("bx lr");
} }

View file

@ -11,17 +11,17 @@
use std::arch::{asm, naked_asm}; use std::arch::{asm, naked_asm};
#[naked] #[unsafe(naked)]
pub unsafe fn rust_implicit() { pub fn rust_implicit() {
naked_asm!("ret"); naked_asm!("ret");
} }
#[naked] #[unsafe(naked)]
pub unsafe extern "Rust" fn rust_explicit() { pub extern "Rust" fn rust_explicit() {
naked_asm!("ret"); naked_asm!("ret");
} }
#[naked] #[unsafe(naked)]
pub unsafe extern "rust-cold" fn rust_cold() { pub extern "rust-cold" fn rust_cold() {
naked_asm!("ret"); naked_asm!("ret");
} }

View file

@ -8,14 +8,14 @@ use std::arch::{asm, naked_asm};
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse2")] #[target_feature(enable = "sse2")]
#[naked] #[unsafe(naked)]
pub unsafe extern "C" fn compatible_target_feature() { pub extern "C" fn compatible_target_feature() {
naked_asm!(""); naked_asm!("ret");
} }
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")] #[target_feature(enable = "neon")]
#[naked] #[unsafe(naked)]
pub unsafe extern "C" fn compatible_target_feature() { pub extern "C" fn compatible_target_feature() {
naked_asm!(""); naked_asm!("ret");
} }

View file

@ -8,31 +8,31 @@
use std::arch::naked_asm; use std::arch::naked_asm;
#[test] #[test]
#[naked] #[unsafe(naked)]
//~^ ERROR [E0736] //~^ ERROR [E0736]
extern "C" fn test_naked() { extern "C" fn test_naked() {
unsafe { naked_asm!("") }; naked_asm!("")
} }
#[should_panic] #[should_panic]
#[test] #[test]
#[naked] #[unsafe(naked)]
//~^ ERROR [E0736] //~^ ERROR [E0736]
extern "C" fn test_naked_should_panic() { extern "C" fn test_naked_should_panic() {
unsafe { naked_asm!("") }; naked_asm!("")
} }
#[ignore] #[ignore]
#[test] #[test]
#[naked] #[unsafe(naked)]
//~^ ERROR [E0736] //~^ ERROR [E0736]
extern "C" fn test_naked_ignore() { extern "C" fn test_naked_ignore() {
unsafe { naked_asm!("") }; naked_asm!("")
} }
#[bench] #[bench]
#[naked] #[unsafe(naked)]
//~^ ERROR [E0736] //~^ ERROR [E0736]
extern "C" fn bench_naked() { extern "C" fn bench_naked() {
unsafe { naked_asm!("") }; naked_asm!("")
} }

View file

@ -1,34 +1,34 @@
error[E0736]: cannot use `#[naked]` with testing attributes error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes
--> $DIR/naked-functions-testattrs.rs:11:1 --> $DIR/naked-functions-testattrs.rs:11:1
| |
LL | #[test] LL | #[test]
| ------- function marked with testing attribute here | ------- function marked with testing attribute here
LL | #[naked] LL | #[unsafe(naked)]
| ^^^^^^^^ `#[naked]` is incompatible with testing attributes | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes
error[E0736]: cannot use `#[naked]` with testing attributes error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes
--> $DIR/naked-functions-testattrs.rs:19:1 --> $DIR/naked-functions-testattrs.rs:19:1
| |
LL | #[test] LL | #[test]
| ------- function marked with testing attribute here | ------- function marked with testing attribute here
LL | #[naked] LL | #[unsafe(naked)]
| ^^^^^^^^ `#[naked]` is incompatible with testing attributes | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes
error[E0736]: cannot use `#[naked]` with testing attributes error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes
--> $DIR/naked-functions-testattrs.rs:27:1 --> $DIR/naked-functions-testattrs.rs:27:1
| |
LL | #[test] LL | #[test]
| ------- function marked with testing attribute here | ------- function marked with testing attribute here
LL | #[naked] LL | #[unsafe(naked)]
| ^^^^^^^^ `#[naked]` is incompatible with testing attributes | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes
error[E0736]: cannot use `#[naked]` with testing attributes error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes
--> $DIR/naked-functions-testattrs.rs:34:1 --> $DIR/naked-functions-testattrs.rs:34:1
| |
LL | #[bench] LL | #[bench]
| -------- function marked with testing attribute here | -------- function marked with testing attribute here
LL | #[naked] LL | #[unsafe(naked)]
| ^^^^^^^^ `#[naked]` is incompatible with testing attributes | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes
error: aborting due to 4 previous errors error: aborting due to 4 previous errors

View file

@ -64,44 +64,34 @@ pub mod normal {
pub mod naked { pub mod naked {
use std::arch::naked_asm; use std::arch::naked_asm;
#[naked] #[unsafe(naked)]
pub extern "C" fn function(a: usize, b: usize) -> usize { pub extern "C" fn function(a: usize, b: usize) -> usize {
unsafe { naked_asm!("")
naked_asm!("");
}
} }
pub struct Naked; pub struct Naked;
impl Naked { impl Naked {
#[naked] #[unsafe(naked)]
pub extern "C" fn associated(a: usize, b: usize) -> usize { pub extern "C" fn associated(a: usize, b: usize) -> usize {
unsafe { naked_asm!("")
naked_asm!("");
}
} }
#[naked] #[unsafe(naked)]
pub extern "C" fn method(&self, a: usize, b: usize) -> usize { pub extern "C" fn method(&self, a: usize, b: usize) -> usize {
unsafe { naked_asm!("")
naked_asm!("");
}
} }
} }
impl super::Trait for Naked { impl super::Trait for Naked {
#[naked] #[unsafe(naked)]
extern "C" fn trait_associated(a: usize, b: usize) -> usize { extern "C" fn trait_associated(a: usize, b: usize) -> usize {
unsafe { naked_asm!("")
naked_asm!("");
}
} }
#[naked] #[unsafe(naked)]
extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { extern "C" fn trait_method(&self, a: usize, b: usize) -> usize {
unsafe { naked_asm!("")
naked_asm!("");
}
} }
} }
} }

View file

@ -9,8 +9,8 @@
use std::arch::{asm, naked_asm}; use std::arch::{asm, naked_asm};
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn inline_asm_macro() { pub extern "C" fn inline_asm_macro() {
asm!("", options(raw)); unsafe { asm!("", options(raw)) };
//~^ERROR the `asm!` macro is not allowed in naked functions //~^ERROR the `asm!` macro is not allowed in naked functions
} }
@ -21,7 +21,7 @@ pub struct P {
} }
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn patterns( pub extern "C" fn patterns(
mut a: u32, mut a: u32,
//~^ ERROR patterns not allowed in naked function parameters //~^ ERROR patterns not allowed in naked function parameters
&b: &i32, &b: &i32,
@ -35,7 +35,7 @@ pub unsafe extern "C" fn patterns(
} }
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn inc(a: u32) -> u32 { pub extern "C" fn inc(a: u32) -> u32 {
//~^ ERROR naked functions must contain a single `naked_asm!` invocation //~^ ERROR naked functions must contain a single `naked_asm!` invocation
a + 1 a + 1
//~^ ERROR referencing function parameters is not allowed in naked functions //~^ ERROR referencing function parameters is not allowed in naked functions
@ -43,19 +43,19 @@ pub unsafe extern "C" fn inc(a: u32) -> u32 {
#[unsafe(naked)] #[unsafe(naked)]
#[allow(asm_sub_register)] #[allow(asm_sub_register)]
pub unsafe extern "C" fn inc_asm(a: u32) -> u32 { pub extern "C" fn inc_asm(a: u32) -> u32 {
naked_asm!("/* {0} */", in(reg) a) naked_asm!("/* {0} */", in(reg) a)
//~^ ERROR the `in` operand cannot be used with `naked_asm!` //~^ ERROR the `in` operand cannot be used with `naked_asm!`
} }
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn inc_closure(a: u32) -> u32 { pub extern "C" fn inc_closure(a: u32) -> u32 {
//~^ ERROR naked functions must contain a single `naked_asm!` invocation //~^ ERROR naked functions must contain a single `naked_asm!` invocation
(|| a + 1)() (|| a + 1)()
} }
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn unsupported_operands() { pub extern "C" fn unsupported_operands() {
//~^ ERROR naked functions must contain a single `naked_asm!` invocation //~^ ERROR naked functions must contain a single `naked_asm!` invocation
let mut a = 0usize; let mut a = 0usize;
let mut b = 0usize; let mut b = 0usize;
@ -84,11 +84,10 @@ pub extern "C" fn missing_assembly() {
#[unsafe(naked)] #[unsafe(naked)]
pub extern "C" fn too_many_asm_blocks() { pub extern "C" fn too_many_asm_blocks() {
//~^ ERROR naked functions must contain a single `naked_asm!` invocation //~^ ERROR naked functions must contain a single `naked_asm!` invocation
unsafe {
naked_asm!("", options(noreturn)); naked_asm!("", options(noreturn));
//~^ ERROR the `noreturn` option cannot be used with `naked_asm!` //~^ ERROR the `noreturn` option cannot be used with `naked_asm!`
naked_asm!(""); naked_asm!("");
}
} }
pub fn outer(x: u32) -> extern "C" fn(usize) -> usize { pub fn outer(x: u32) -> extern "C" fn(usize) -> usize {
@ -124,49 +123,44 @@ unsafe extern "C" fn invalid_may_unwind() {
#[unsafe(naked)] #[unsafe(naked)]
pub extern "C" fn valid_a<T>() -> T { pub extern "C" fn valid_a<T>() -> T {
unsafe { naked_asm!("");
naked_asm!("");
}
} }
#[unsafe(naked)] #[unsafe(naked)]
pub extern "C" fn valid_b() { pub extern "C" fn valid_b() {
unsafe { {
{ {
{ naked_asm!("");
naked_asm!("");
};
}; };
} };
} }
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn valid_c() { pub extern "C" fn valid_c() {
naked_asm!(""); naked_asm!("");
} }
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn valid_att_syntax() { pub extern "C" fn valid_att_syntax() {
naked_asm!("", options(att_syntax)); naked_asm!("", options(att_syntax));
} }
#[unsafe(naked)] #[unsafe(naked)]
#[unsafe(naked)] pub extern "C" fn allow_compile_error(a: u32) -> u32 {
pub unsafe extern "C" fn allow_compile_error(a: u32) -> u32 {
compile_error!("this is a user specified error") compile_error!("this is a user specified error")
//~^ ERROR this is a user specified error //~^ ERROR this is a user specified error
} }
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn allow_compile_error_and_asm(a: u32) -> u32 { pub extern "C" fn allow_compile_error_and_asm(a: u32) -> u32 {
compile_error!("this is a user specified error"); compile_error!("this is a user specified error");
//~^ ERROR this is a user specified error //~^ ERROR this is a user specified error
naked_asm!("") naked_asm!("")
} }
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn invalid_asm_syntax(a: u32) -> u32 { pub extern "C" fn invalid_asm_syntax(a: u32) -> u32 {
naked_asm!(invalid_syntax) naked_asm!(invalid_syntax)
//~^ ERROR asm template must be a string literal //~^ ERROR asm template must be a string literal
} }
@ -174,7 +168,7 @@ pub unsafe extern "C" fn invalid_asm_syntax(a: u32) -> u32 {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
#[cfg_attr(target_pointer_width = "64", no_mangle)] #[cfg_attr(target_pointer_width = "64", no_mangle)]
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn compatible_cfg_attributes() { pub extern "C" fn compatible_cfg_attributes() {
naked_asm!("", options(att_syntax)); naked_asm!("", options(att_syntax));
} }
@ -183,20 +177,20 @@ pub unsafe extern "C" fn compatible_cfg_attributes() {
#[deny(dead_code)] #[deny(dead_code)]
#[forbid(dead_code)] #[forbid(dead_code)]
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn compatible_diagnostic_attributes() { pub extern "C" fn compatible_diagnostic_attributes() {
naked_asm!("", options(raw)); naked_asm!("", options(raw));
} }
#[deprecated = "test"] #[deprecated = "test"]
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn compatible_deprecated_attributes() { pub extern "C" fn compatible_deprecated_attributes() {
naked_asm!("", options(raw)); naked_asm!("", options(raw));
} }
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
#[must_use] #[must_use]
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn compatible_must_use_attributes() -> u64 { pub extern "C" fn compatible_must_use_attributes() -> u64 {
naked_asm!( naked_asm!(
" "
mov rax, 42 mov rax, 42
@ -208,13 +202,13 @@ pub unsafe extern "C" fn compatible_must_use_attributes() -> u64 {
#[export_name = "exported_function_name"] #[export_name = "exported_function_name"]
#[link_section = ".custom_section"] #[link_section = ".custom_section"]
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn compatible_ffi_attributes_1() { pub extern "C" fn compatible_ffi_attributes_1() {
naked_asm!("", options(raw)); naked_asm!("", options(raw));
} }
#[cold] #[cold]
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn compatible_codegen_attributes() { pub extern "C" fn compatible_codegen_attributes() {
naked_asm!("", options(raw)); naked_asm!("", options(raw));
} }
@ -223,12 +217,12 @@ pub unsafe extern "C" fn compatible_codegen_attributes() {
// a normal comment // a normal comment
#[doc(alias = "ADocAlias")] #[doc(alias = "ADocAlias")]
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn compatible_doc_attributes() { pub extern "C" fn compatible_doc_attributes() {
naked_asm!("", options(raw)); naked_asm!("", options(raw));
} }
#[linkage = "external"] #[linkage = "external"]
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn compatible_linkage() { pub extern "C" fn compatible_linkage() {
naked_asm!("", options(raw)); naked_asm!("", options(raw));
} }

View file

@ -11,70 +11,70 @@ LL | in(reg) a,
| ^^ the `in` operand is not meaningful for global-scoped inline assembly, remove it | ^^ the `in` operand is not meaningful for global-scoped inline assembly, remove it
error: the `noreturn` option cannot be used with `naked_asm!` error: the `noreturn` option cannot be used with `naked_asm!`
--> $DIR/naked-functions.rs:88:32 --> $DIR/naked-functions.rs:88:28
| |
LL | naked_asm!("", options(noreturn)); LL | naked_asm!("", options(noreturn));
| ^^^^^^^^ the `noreturn` option is not meaningful for global-scoped inline assembly | ^^^^^^^^ the `noreturn` option is not meaningful for global-scoped inline assembly
error: the `nomem` option cannot be used with `naked_asm!` error: the `nomem` option cannot be used with `naked_asm!`
--> $DIR/naked-functions.rs:106:28 --> $DIR/naked-functions.rs:105:28
| |
LL | naked_asm!("", options(nomem, preserves_flags)); LL | naked_asm!("", options(nomem, preserves_flags));
| ^^^^^ the `nomem` option is not meaningful for global-scoped inline assembly | ^^^^^ the `nomem` option is not meaningful for global-scoped inline assembly
error: the `preserves_flags` option cannot be used with `naked_asm!` error: the `preserves_flags` option cannot be used with `naked_asm!`
--> $DIR/naked-functions.rs:106:35 --> $DIR/naked-functions.rs:105:35
| |
LL | naked_asm!("", options(nomem, preserves_flags)); LL | naked_asm!("", options(nomem, preserves_flags));
| ^^^^^^^^^^^^^^^ the `preserves_flags` option is not meaningful for global-scoped inline assembly | ^^^^^^^^^^^^^^^ the `preserves_flags` option is not meaningful for global-scoped inline assembly
error: the `readonly` option cannot be used with `naked_asm!` error: the `readonly` option cannot be used with `naked_asm!`
--> $DIR/naked-functions.rs:113:28 --> $DIR/naked-functions.rs:112:28
| |
LL | naked_asm!("", options(readonly, nostack), options(pure)); LL | naked_asm!("", options(readonly, nostack), options(pure));
| ^^^^^^^^ the `readonly` option is not meaningful for global-scoped inline assembly | ^^^^^^^^ the `readonly` option is not meaningful for global-scoped inline assembly
error: the `nostack` option cannot be used with `naked_asm!` error: the `nostack` option cannot be used with `naked_asm!`
--> $DIR/naked-functions.rs:113:38 --> $DIR/naked-functions.rs:112:38
| |
LL | naked_asm!("", options(readonly, nostack), options(pure)); LL | naked_asm!("", options(readonly, nostack), options(pure));
| ^^^^^^^ the `nostack` option is not meaningful for global-scoped inline assembly | ^^^^^^^ the `nostack` option is not meaningful for global-scoped inline assembly
error: the `pure` option cannot be used with `naked_asm!` error: the `pure` option cannot be used with `naked_asm!`
--> $DIR/naked-functions.rs:113:56 --> $DIR/naked-functions.rs:112:56
| |
LL | naked_asm!("", options(readonly, nostack), options(pure)); LL | naked_asm!("", options(readonly, nostack), options(pure));
| ^^^^ the `pure` option is not meaningful for global-scoped inline assembly | ^^^^ the `pure` option is not meaningful for global-scoped inline assembly
error: the `may_unwind` option cannot be used with `naked_asm!` error: the `may_unwind` option cannot be used with `naked_asm!`
--> $DIR/naked-functions.rs:121:28 --> $DIR/naked-functions.rs:120:28
| |
LL | naked_asm!("", options(may_unwind)); LL | naked_asm!("", options(may_unwind));
| ^^^^^^^^^^ the `may_unwind` option is not meaningful for global-scoped inline assembly | ^^^^^^^^^^ the `may_unwind` option is not meaningful for global-scoped inline assembly
error: this is a user specified error error: this is a user specified error
--> $DIR/naked-functions.rs:157:5 --> $DIR/naked-functions.rs:151:5
| |
LL | compile_error!("this is a user specified error") LL | compile_error!("this is a user specified error")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this is a user specified error error: this is a user specified error
--> $DIR/naked-functions.rs:163:5 --> $DIR/naked-functions.rs:157:5
| |
LL | compile_error!("this is a user specified error"); LL | compile_error!("this is a user specified error");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: asm template must be a string literal error: asm template must be a string literal
--> $DIR/naked-functions.rs:170:16 --> $DIR/naked-functions.rs:164:16
| |
LL | naked_asm!(invalid_syntax) LL | naked_asm!(invalid_syntax)
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
error[E0787]: the `asm!` macro is not allowed in naked functions error[E0787]: the `asm!` macro is not allowed in naked functions
--> $DIR/naked-functions.rs:13:5 --> $DIR/naked-functions.rs:13:14
| |
LL | asm!("", options(raw)); LL | unsafe { asm!("", options(raw)) };
| ^^^^^^^^^^^^^^^^^^^^^^ consider using the `naked_asm!` macro instead | ^^^^^^^^^^^^^^^^^^^^^^ consider using the `naked_asm!` macro instead
error: patterns not allowed in naked function parameters error: patterns not allowed in naked function parameters
--> $DIR/naked-functions.rs:25:5 --> $DIR/naked-functions.rs:25:5
@ -111,8 +111,8 @@ LL | a + 1
error[E0787]: naked functions must contain a single `naked_asm!` invocation error[E0787]: naked functions must contain a single `naked_asm!` invocation
--> $DIR/naked-functions.rs:38:1 --> $DIR/naked-functions.rs:38:1
| |
LL | pub unsafe extern "C" fn inc(a: u32) -> u32 { LL | pub extern "C" fn inc(a: u32) -> u32 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | LL |
LL | a + 1 LL | a + 1
| ----- not allowed in naked functions | ----- not allowed in naked functions
@ -120,8 +120,8 @@ LL | a + 1
error[E0787]: naked functions must contain a single `naked_asm!` invocation error[E0787]: naked functions must contain a single `naked_asm!` invocation
--> $DIR/naked-functions.rs:52:1 --> $DIR/naked-functions.rs:52:1
| |
LL | pub unsafe extern "C" fn inc_closure(a: u32) -> u32 { LL | pub extern "C" fn inc_closure(a: u32) -> u32 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | LL |
LL | (|| a + 1)() LL | (|| a + 1)()
| ------------ not allowed in naked functions | ------------ not allowed in naked functions
@ -129,8 +129,8 @@ LL | (|| a + 1)()
error[E0787]: naked functions must contain a single `naked_asm!` invocation error[E0787]: naked functions must contain a single `naked_asm!` invocation
--> $DIR/naked-functions.rs:58:1 --> $DIR/naked-functions.rs:58:1
| |
LL | pub unsafe extern "C" fn unsupported_operands() { LL | pub extern "C" fn unsupported_operands() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | LL |
LL | let mut a = 0usize; LL | let mut a = 0usize;
| ------------------- not allowed in naked functions | ------------------- not allowed in naked functions
@ -155,11 +155,11 @@ error[E0787]: naked functions must contain a single `naked_asm!` invocation
LL | pub extern "C" fn too_many_asm_blocks() { LL | pub extern "C" fn too_many_asm_blocks() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
... ...
LL | naked_asm!(""); LL | naked_asm!("");
| -------------- multiple `naked_asm!` invocations are not allowed in naked functions | -------------- multiple `naked_asm!` invocations are not allowed in naked functions
error: referencing function parameters is not allowed in naked functions error: referencing function parameters is not allowed in naked functions
--> $DIR/naked-functions.rs:98:11 --> $DIR/naked-functions.rs:97:11
| |
LL | *&y LL | *&y
| ^ | ^
@ -167,7 +167,7 @@ LL | *&y
= help: follow the calling convention in asm block to use parameters = help: follow the calling convention in asm block to use parameters
error[E0787]: naked functions must contain a single `naked_asm!` invocation error[E0787]: naked functions must contain a single `naked_asm!` invocation
--> $DIR/naked-functions.rs:96:5 --> $DIR/naked-functions.rs:95:5
| |
LL | pub extern "C" fn inner(y: usize) -> usize { LL | pub extern "C" fn inner(y: usize) -> usize {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,17 +1,17 @@
// Checks that #[naked] attribute can be placed on function definitions only. // Checks that #[unsafe(naked)] attribute can be placed on function definitions only.
// //
//@ needs-asm-support //@ needs-asm-support
#![feature(naked_functions)] #![feature(naked_functions)]
#![naked] //~ ERROR should be applied to a function definition #![unsafe(naked)] //~ ERROR should be applied to a function definition
use std::arch::naked_asm; use std::arch::naked_asm;
extern "C" { extern "C" {
#[naked] //~ ERROR should be applied to a function definition #[unsafe(naked)] //~ ERROR should be applied to a function definition
fn f(); fn f();
} }
#[naked] //~ ERROR should be applied to a function definition #[unsafe(naked)] //~ ERROR should be applied to a function definition
#[repr(C)] #[repr(C)]
struct S { struct S {
a: u32, a: u32,
@ -19,35 +19,35 @@ struct S {
} }
trait Invoke { trait Invoke {
#[naked] //~ ERROR should be applied to a function definition #[unsafe(naked)] //~ ERROR should be applied to a function definition
extern "C" fn invoke(&self); extern "C" fn invoke(&self);
} }
impl Invoke for S { impl Invoke for S {
#[naked] #[unsafe(naked)]
extern "C" fn invoke(&self) { extern "C" fn invoke(&self) {
unsafe { naked_asm!("") } naked_asm!("")
} }
} }
#[naked] #[unsafe(naked)]
extern "C" fn ok() { extern "C" fn ok() {
unsafe { naked_asm!("") } naked_asm!("")
} }
impl S { impl S {
#[naked] #[unsafe(naked)]
extern "C" fn g() { extern "C" fn g() {
unsafe { naked_asm!("") } naked_asm!("")
} }
#[naked] #[unsafe(naked)]
extern "C" fn h(&self) { extern "C" fn h(&self) {
unsafe { naked_asm!("") } naked_asm!("")
} }
} }
fn main() { fn main() {
#[naked] //~ ERROR should be applied to a function definition #[unsafe(naked)] //~ ERROR should be applied to a function definition
|| {}; || {};
} }

View file

@ -1,8 +1,8 @@
error: attribute should be applied to a function definition error: attribute should be applied to a function definition
--> $DIR/naked-invalid-attr.rs:14:1 --> $DIR/naked-invalid-attr.rs:14:1
| |
LL | #[naked] LL | #[unsafe(naked)]
| ^^^^^^^^ | ^^^^^^^^^^^^^^^^
LL | #[repr(C)] LL | #[repr(C)]
LL | / struct S { LL | / struct S {
LL | | a: u32, LL | | a: u32,
@ -13,32 +13,32 @@ LL | | }
error: attribute should be applied to a function definition error: attribute should be applied to a function definition
--> $DIR/naked-invalid-attr.rs:51:5 --> $DIR/naked-invalid-attr.rs:51:5
| |
LL | #[naked] LL | #[unsafe(naked)]
| ^^^^^^^^ | ^^^^^^^^^^^^^^^^
LL | || {}; LL | || {};
| ----- not a function definition | ----- not a function definition
error: attribute should be applied to a function definition error: attribute should be applied to a function definition
--> $DIR/naked-invalid-attr.rs:22:5 --> $DIR/naked-invalid-attr.rs:22:5
| |
LL | #[naked] LL | #[unsafe(naked)]
| ^^^^^^^^ | ^^^^^^^^^^^^^^^^
LL | extern "C" fn invoke(&self); LL | extern "C" fn invoke(&self);
| ---------------------------- not a function definition | ---------------------------- not a function definition
error: attribute should be applied to a function definition error: attribute should be applied to a function definition
--> $DIR/naked-invalid-attr.rs:10:5 --> $DIR/naked-invalid-attr.rs:10:5
| |
LL | #[naked] LL | #[unsafe(naked)]
| ^^^^^^^^ | ^^^^^^^^^^^^^^^^
LL | fn f(); LL | fn f();
| ------- not a function definition | ------- not a function definition
error: attribute should be applied to a function definition error: attribute should be applied to a function definition
--> $DIR/naked-invalid-attr.rs:5:1 --> $DIR/naked-invalid-attr.rs:5:1
| |
LL | #![naked] LL | #![unsafe(naked)]
| ^^^^^^^^^ cannot be applied to crates | ^^^^^^^^^^^^^^^^^ cannot be applied to crates
error: aborting due to 5 previous errors error: aborting due to 5 previous errors

View file

@ -6,43 +6,43 @@ use std::arch::naked_asm;
#[repr(C)] #[repr(C)]
//~^ ERROR attribute should be applied to a struct, enum, or union [E0517] //~^ ERROR attribute should be applied to a struct, enum, or union [E0517]
#[naked] #[unsafe(naked)]
extern "C" fn example1() { extern "C" fn example1() {
//~^ NOTE not a struct, enum, or union //~^ NOTE not a struct, enum, or union
unsafe { naked_asm!("") } naked_asm!("")
} }
#[repr(transparent)] #[repr(transparent)]
//~^ ERROR attribute should be applied to a struct, enum, or union [E0517] //~^ ERROR attribute should be applied to a struct, enum, or union [E0517]
#[naked] #[unsafe(naked)]
extern "C" fn example2() { extern "C" fn example2() {
//~^ NOTE not a struct, enum, or union //~^ NOTE not a struct, enum, or union
unsafe { naked_asm!("") } naked_asm!("")
} }
#[repr(align(16), C)] #[repr(align(16), C)]
//~^ ERROR attribute should be applied to a struct, enum, or union [E0517] //~^ ERROR attribute should be applied to a struct, enum, or union [E0517]
#[naked] #[unsafe(naked)]
extern "C" fn example3() { extern "C" fn example3() {
//~^ NOTE not a struct, enum, or union //~^ NOTE not a struct, enum, or union
unsafe { naked_asm!("") } naked_asm!("")
} }
// note: two errors because of packed and C // note: two errors because of packed and C
#[repr(C, packed)] #[repr(C, packed)]
//~^ ERROR attribute should be applied to a struct or union [E0517] //~^ ERROR attribute should be applied to a struct or union [E0517]
//~| ERROR attribute should be applied to a struct, enum, or union [E0517] //~| ERROR attribute should be applied to a struct, enum, or union [E0517]
#[naked] #[unsafe(naked)]
extern "C" fn example4() { extern "C" fn example4() {
//~^ NOTE not a struct, enum, or union //~^ NOTE not a struct, enum, or union
//~| NOTE not a struct or union //~| NOTE not a struct or union
unsafe { naked_asm!("") } naked_asm!("")
} }
#[repr(u8)] #[repr(u8)]
//~^ ERROR attribute should be applied to an enum [E0517] //~^ ERROR attribute should be applied to an enum [E0517]
#[naked] #[unsafe(naked)]
extern "C" fn example5() { extern "C" fn example5() {
//~^ NOTE not an enum //~^ NOTE not an enum
unsafe { naked_asm!("") } naked_asm!("")
} }

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