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`).
#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.
#
# 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
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
.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]`
.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"))]
#[naked]
#[unsafe(naked)]
extern "C" fn naked_test() {
unsafe {
naked_asm!("ret");
}
naked_asm!("ret")
}
#[repr(C)]

View file

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

View file

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

View file

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

View file

@ -427,12 +427,21 @@ impl<'a> CrateLocator<'a> {
let (rlibs, rmetas, dylibs) =
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
// ones we've already seeen. This allows us to ignore crates
// we know are exactual equal to ones we've already found.
// Going to the same crate through different symlinks does not change the result.
let path = try_canonicalize(&spf.path)
.unwrap_or_else(|_| spf.path.to_path_buf());
if seen_paths.contains(&path) {
continue;
};
seen_paths.insert(path.clone());
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 {
CrateFlavor::Rlib => rlibs.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 {
asm_macro: AsmMacro::Asm | AsmMacro::NakedAsm,
asm_macro: asm_macro @ (AsmMacro::Asm | AsmMacro::NakedAsm),
ref operands,
template: _,
options: _,
line_spans: _,
}) => {
// 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
// specially.

View file

@ -288,6 +288,21 @@ where
) -> 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>
where
D: SolverDelegate<Interner = I>,
@ -296,6 +311,7 @@ where
pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<D>>(
&mut self,
goal: Goal<I, G>,
assemble_from: AssembleCandidatesFrom,
) -> Vec<Candidate<I>> {
let Ok(normalized_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_object_bound_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
}
@ -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
/// 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
@ -782,47 +803,44 @@ where
///
/// See trait-system-refactor-initiative#124 for more details.
#[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,
proven_via: Option<TraitGoalProvenVia>,
candidates: Vec<Candidate<I>>,
goal: Goal<I, G>,
inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> QueryResult<I> {
let Some(proven_via) = proven_via else {
// 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
// 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 {
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
// 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.
// See tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs.
//
// FIXME(const_trait_impl): should this behavior also be used by
// constness checking. Doing so is *at least theoretically* breaking,
// see github.com/rust-lang/rust/issues/133044#issuecomment-2500709754
if considered_candidates.is_empty() {
considered_candidates.extend(
candidates
// See `tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs`.
let mut considered_candidates: Vec<_> = if candidates_from_env_and_bounds
.iter()
.filter(|c| matches!(c.source, CandidateSource::AliasBound))
.map(|c| c.result),
);
}
.any(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
{
candidates_from_env_and_bounds
.into_iter()
.filter(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
.map(|c| c.result)
.collect()
} 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
// aliases as rigid if there are no applicable projection bounds in the environment.
@ -839,6 +857,9 @@ where
}
}
TraitGoalProvenVia::Misc => {
let candidates =
self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);
// Prefer "orphaned" param-env normalization predicates, which are used
// (for example, and ideally only) when proving item bounds for an impl.
let candidates_from_env: Vec<_> = candidates

View file

@ -399,12 +399,11 @@ where
&mut self,
goal: Goal<I, ty::HostEffectPredicate<I>>,
) -> QueryResult<I> {
let candidates = self.assemble_and_evaluate_candidates(goal);
let (_, proven_via) = self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| {
let trait_goal: Goal<I, ty::TraitPredicate<I>> =
goal.with(ecx.cx(), goal.predicate.trait_ref);
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();
match goal.predicate.alias.kind(cx) {
ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => {
let candidates = self.assemble_and_evaluate_candidates(goal);
let trait_ref = goal.predicate.alias.trait_ref(cx);
let (_, proven_via) =
self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| {
let trait_goal: Goal<I, ty::TraitPredicate<I>> = goal.with(cx, trait_ref);
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| {
this.structurally_instantiate_normalizes_to_term(
goal,

View file

@ -13,7 +13,7 @@ use tracing::{instrument, trace};
use crate::delegate::SolverDelegate;
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::{
BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause,
@ -1365,7 +1365,7 @@ where
&mut self,
goal: Goal<I, TraitPredicate<I>>,
) -> 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)
}
}

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 {
// 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 {
span: unsafe_span,
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}
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 =
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
passes_naked_functions_incompatible_attribute =
attribute incompatible with `#[naked]`
.label = the `{$attr}` attribute is incompatible with `#[naked]`
.naked_attribute = function marked with `#[naked]` here
attribute incompatible with `#[unsafe(naked)]`
.label = the `{$attr}` attribute is incompatible with `#[unsafe(naked)]`
.naked_attribute = function marked with `#[unsafe(naked)]` here
passes_naked_functions_must_naked_asm =
the `asm!` macro is not allowed in naked functions

View file

@ -1232,6 +1232,25 @@ impl DesugaringKind {
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)]

View file

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

View file

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

View file

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

View file

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

View file

@ -1,44 +1,31 @@
use std::iter;
use std::path::PathBuf;
use rustc_ast::MetaItemInner;
use rustc_data_structures::fx::FxHashMap;
use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
use rustc_errors::codes::*;
use rustc_errors::{ErrorGuaranteed, struct_span_code_err};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{AttrArgs, Attribute};
use rustc_macros::LintDiagnostic;
use rustc_middle::bug;
use rustc_middle::ty::print::PrintTraitRefExt as _;
use rustc_middle::ty::{self, GenericArgsRef, GenericParamDefKind, TyCtxt};
use rustc_parse_format::{ParseMode, Parser, Piece, Position};
use rustc_middle::ty::print::PrintTraitRefExt;
use rustc_middle::ty::{self, GenericArgsRef, GenericParamDef, GenericParamDefKind, TyCtxt};
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 {rustc_attr_parsing as attr, rustc_hir as hir};
use super::{ObligationCauseCode, PredicateObligation};
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::{
EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented,
};
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> {
fn impl_similar_to(
&self,
@ -121,86 +108,78 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
.unwrap_or_else(|| (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args));
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,
// but I guess we could synthesize one here. We don't see any errors that rely on
// that yet, though.
let enclosure = self.describe_enclosure(obligation.cause.body_id).map(|t| t.to_owned());
flags.push((sym::ItemContext, enclosure));
let item_context = self.describe_enclosure(obligation.cause.body_id).unwrap_or("");
match obligation.cause.code() {
let direct = match obligation.cause.code() {
ObligationCauseCode::BuiltinDerived(..)
| ObligationCauseCode::ImplDerived(..)
| ObligationCauseCode::WellFormedDerived(..) => {}
| ObligationCauseCode::WellFormedDerived(..) => false,
_ => {
// this is a "direct", user-specified, rather than derived,
// obligation.
flags.push((sym::direct, None));
}
true
}
};
if let Some(k) = obligation.cause.span.desugaring_kind() {
flags.push((sym::from_desugaring, None));
flags.push((sym::from_desugaring, Some(format!("{k:?}"))));
}
let from_desugaring = obligation.cause.span.desugaring_kind();
if let ObligationCauseCode::MainFunctionType = obligation.cause.code() {
flags.push((sym::cause, Some("MainFunctionType".to_string())));
}
flags.push((sym::Trait, Some(trait_pred.trait_ref.print_trait_sugared().to_string())));
let cause = if let ObligationCauseCode::MainFunctionType = obligation.cause.code() {
Some("MainFunctionType".to_string())
} else {
None
};
// Add all types without trimmed paths or visible paths, ensuring they end up with
// their "canonical" def path.
ty::print::with_no_trimmed_paths!(ty::print::with_no_visible_paths!({
let generics = self.tcx.generics_of(def_id);
let self_ty = trait_pred.self_ty();
// This is also included through the generics list as `Self`,
// but the parser won't allow you to use it
flags.push((sym::_Self, Some(self_ty.to_string())));
self_types.push(self_ty.to_string());
if let Some(def) = self_ty.ty_adt_def() {
// We also want to be able to select self's original
// signature with no type arguments resolved
flags.push((
sym::_Self,
Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()),
));
self_types.push(self.tcx.type_of(def.did()).instantiate_identity().to_string());
}
for param in generics.own_params.iter() {
let value = match param.kind {
for GenericParamDef { name, kind, index, .. } in generics.own_params.iter() {
let value = match kind {
GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
args[param.index as usize].to_string()
args[*index as usize].to_string()
}
GenericParamDefKind::Lifetime => continue,
};
let name = param.name;
flags.push((name, Some(value)));
generic_args.push((*name, value));
if let GenericParamDefKind::Type { .. } = param.kind {
let param_ty = args[param.index as usize].expect_ty();
if let GenericParamDefKind::Type { .. } = kind {
let param_ty = args[*index as usize].expect_ty();
if let Some(def) = param_ty.ty_adt_def() {
// We also want to be able to select the parameter's
// original signature with no type arguments resolved
flags.push((
name,
Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()),
generic_args.push((
*name,
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()) {
flags.push((sym::crate_local, None));
crate_local = true;
}
// Allow targeting all integers using `{integral}`, even if the exact type was resolved
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() {
flags.push((sym::_Self, Some("&[]".to_owned())));
self_types.push("&[]".to_owned());
}
if self_ty.is_fn() {
@ -215,53 +194,51 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
hir::Safety::Unsafe => "unsafe fn",
}
};
flags.push((sym::_Self, Some(shortname.to_owned())));
self_types.push(shortname.to_owned());
}
// Slices give us `[]`, `[{ty}]`
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() {
// We also want to be able to select the slice's type's original
// signature with no type arguments resolved
flags.push((
sym::_Self,
Some(format!("[{}]", self.tcx.type_of(def.did()).instantiate_identity())),
));
self_types
.push(format!("[{}]", self.tcx.type_of(def.did()).instantiate_identity()));
}
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]`
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);
flags.push((sym::_Self, Some(format!("[{aty}; _]"))));
self_types.push(format!("[{aty}; _]"));
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() {
// We also want to be able to select the array's type's original
// signature with no type arguments resolved
let def_ty = self.tcx.type_of(def.did()).instantiate_identity();
flags.push((sym::_Self, Some(format!("[{def_ty}; _]"))));
self_types.push(format!("[{def_ty}; _]"));
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() {
flags.push((sym::_Self, Some("[{integral}; _]".to_string())));
self_types.push("[{integral}; _]".to_string());
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() {
for t in traits.iter() {
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()
&& 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) {
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 {
OnUnimplementedNote::default()
}
}
}
/// Represents a format string in a on_unimplemented attribute,
/// like the "content" in `#[diagnostic::on_unimplemented(message = "content")]`
#[derive(Clone, Debug)]
pub struct OnUnimplementedFormatString {
symbol: Symbol,
span: Span,
is_diagnostic_namespace_variant: bool,
/// Symbol of the format string, i.e. `"content"`
pub symbol: Symbol,
///The span of the format string, i.e. `"content"`
pub span: Span,
pub is_diagnostic_namespace_variant: bool,
}
#[derive(Debug)]
pub struct OnUnimplementedDirective {
pub condition: Option<MetaItemInner>,
pub condition: Option<Condition>,
pub subcommands: Vec<OnUnimplementedDirective>,
pub message: Option<OnUnimplementedFormatString>,
pub label: Option<OnUnimplementedFormatString>,
pub message: Option<(Span, OnUnimplementedFormatString)>,
pub label: Option<(Span, OnUnimplementedFormatString)>,
pub notes: Vec<OnUnimplementedFormatString>,
pub parent_label: Option<OnUnimplementedFormatString>,
pub append_const_msg: Option<AppendConstMessage>,
@ -329,7 +351,7 @@ pub struct MalformedOnUnimplementedAttrLint {
}
impl MalformedOnUnimplementedAttrLint {
fn new(span: Span) -> Self {
pub fn new(span: Span) -> Self {
Self { span }
}
}
@ -350,7 +372,7 @@ pub struct IgnoredDiagnosticOption {
}
impl IgnoredDiagnosticOption {
fn maybe_emit_warning<'tcx>(
pub fn maybe_emit_warning<'tcx>(
tcx: TyCtxt<'tcx>,
item_def_id: DefId,
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)]
#[diag(trait_selection_wrapped_parser_error)]
pub struct WrappedParserError {
description: String,
label: String,
pub description: String,
pub label: String,
}
impl<'tcx> OnUnimplementedDirective {
@ -407,12 +411,12 @@ impl<'tcx> OnUnimplementedDirective {
let mut errored = None;
let mut item_iter = items.iter();
let parse_value = |value_str, value_span| {
let parse_value = |value_str, span| {
OnUnimplementedFormatString::try_parse(
tcx,
item_def_id,
value_str,
value_span,
span,
is_diagnostic_namespace_variant,
)
.map(Some)
@ -434,7 +438,7 @@ impl<'tcx> OnUnimplementedDirective {
}
true
});
Some(cond.clone())
Some(Condition { inner: cond.clone() })
};
let mut message = None;
@ -444,24 +448,36 @@ impl<'tcx> OnUnimplementedDirective {
let mut subcommands = vec![];
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 {
if item.has_name(sym::message) && message.is_none() {
if let Some(message_) = item.value_str() {
message = parse_value(message_, item.span())?;
if let Some((message_, span)) = get_value_and_span(item, sym::message)
&& message.is_none()
{
message = parse_value(message_, span)?.map(|l| (item.span(), l));
continue;
}
} else if item.has_name(sym::label) && label.is_none() {
if let Some(label_) = item.value_str() {
label = parse_value(label_, item.span())?;
} 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 item.has_name(sym::note) {
if let Some(note_) = item.value_str() {
if let Some(note) = parse_value(note_, item.span())? {
} 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;
}
}
} else if item.has_name(sym::parent_label)
&& parent_label.is_none()
&& !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> {
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) {
return Self::parse_attribute(attr, false, tcx, item_def_id);
} else {
@ -554,15 +577,15 @@ impl<'tcx> OnUnimplementedDirective {
IgnoredDiagnosticOption::maybe_emit_warning(
tcx,
item_def_id,
directive.message.as_ref().map(|f| f.span),
aggr.message.as_ref().map(|f| f.span),
directive.message.as_ref().map(|f| f.0),
aggr.message.as_ref().map(|f| f.0),
"message",
);
IgnoredDiagnosticOption::maybe_emit_warning(
tcx,
item_def_id,
directive.label.as_ref().map(|f| f.span),
aggr.label.as_ref().map(|f| f.span),
directive.label.as_ref().map(|f| f.0),
aggr.label.as_ref().map(|f| f.0),
"label",
);
IgnoredDiagnosticOption::maybe_emit_warning(
@ -636,13 +659,16 @@ impl<'tcx> OnUnimplementedDirective {
condition: None,
message: None,
subcommands: vec![],
label: Some(OnUnimplementedFormatString::try_parse(
label: Some((
attr.span(),
OnUnimplementedFormatString::try_parse(
tcx,
item_def_id,
value,
attr.span(),
attr.value_span().unwrap_or(attr.span()),
is_diagnostic_namespace_variant,
)?),
)?,
)),
notes: Vec::new(),
parent_label: None,
append_const_msg: None,
@ -702,43 +728,23 @@ impl<'tcx> OnUnimplementedDirective {
&self,
tcx: TyCtxt<'tcx>,
trait_ref: ty::TraitRef<'tcx>,
options: &[(Symbol, Option<String>)],
long_ty_file: &mut Option<PathBuf>,
condition_options: &ConditionOptions,
args: &FormatArgs<'tcx>,
) -> OnUnimplementedNote {
let mut message = None;
let mut label = None;
let mut notes = Vec::new();
let mut parent_label = None;
let mut append_const_msg = None;
info!("evaluate({:?}, trait_ref={:?}, options={:?})", self, trait_ref, options);
let options_map: FxHashMap<Symbol, String> =
options.iter().filter_map(|(k, v)| v.clone().map(|v| (*k, v))).collect();
info!(
"evaluate({:?}, trait_ref={:?}, options={:?}, args ={:?})",
self, trait_ref, condition_options, args
);
for command in self.subcommands.iter().chain(Some(self)).rev() {
debug!(?command);
if let Some(ref condition) = command.condition
&& !attr::eval_condition(condition, &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!(
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))
})
&& !condition.matches_predicate(tcx, condition_options)
{
debug!("evaluate: skipping {:?} due to condition", command);
continue;
@ -762,14 +768,10 @@ impl<'tcx> OnUnimplementedDirective {
}
OnUnimplementedNote {
label: label.map(|l| l.format(tcx, trait_ref, &options_map, long_ty_file)),
message: message.map(|m| m.format(tcx, trait_ref, &options_map, long_ty_file)),
notes: notes
.into_iter()
.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)),
label: label.map(|l| l.1.format(tcx, trait_ref, args)),
message: message.map(|m| m.1.format(tcx, trait_ref, args)),
notes: notes.into_iter().map(|n| n.format(tcx, trait_ref, args)).collect(),
parent_label: parent_label.map(|e_s| e_s.format(tcx, trait_ref, args)),
append_const_msg,
}
}
@ -780,106 +782,81 @@ impl<'tcx> OnUnimplementedFormatString {
tcx: TyCtxt<'tcx>,
item_def_id: DefId,
from: Symbol,
value_span: Span,
span: Span,
is_diagnostic_namespace_variant: bool,
) -> Result<Self, ErrorGuaranteed> {
let result = OnUnimplementedFormatString {
symbol: from,
span: value_span,
is_diagnostic_namespace_variant,
};
let result =
OnUnimplementedFormatString { symbol: from, span, is_diagnostic_namespace_variant };
result.verify(tcx, item_def_id)?;
Ok(result)
}
fn verify(&self, tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<(), ErrorGuaranteed> {
let trait_def_id = if tcx.is_trait(item_def_id) {
item_def_id
} else {
tcx.trait_id_of_impl(item_def_id)
.expect("expected `on_unimplemented` to correspond to a trait")
fn verify(&self, tcx: TyCtxt<'tcx>, trait_def_id: DefId) -> Result<(), ErrorGuaranteed> {
if !tcx.is_trait(trait_def_id) {
return Ok(());
};
let trait_name = tcx.item_ident(trait_def_id);
let generics = tcx.generics_of(item_def_id);
let s = self.symbol.as_str();
let mut parser = Parser::new(s, None, None, false, ParseMode::Format);
let ctx = if self.is_diagnostic_namespace_variant {
Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }
} else {
Ctx::RustcOnUnimplemented { tcx, trait_def_id }
};
let mut result = Ok(());
for token in &mut parser {
match token {
Piece::Lit(_) => (), // Normal string, no need to check it
Piece::NextArgument(a) => {
let format_spec = a.format;
if self.is_diagnostic_namespace_variant
&& (format_spec.ty_span.is_some()
|| format_spec.width_span.is_some()
|| 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 {
Position::ArgumentNamed(s) => {
match Symbol::intern(s) {
// `{ThisTraitsName}` is allowed
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 => {
match FormatString::parse(self.symbol, self.span, &ctx) {
// Warnings about format specifiers, deprecated parameters, wrong parameters etc.
// In other words we'd like to let the author know, but we can still try to format the string later
Ok(FormatString { warnings, .. }) => {
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,
},
);
for w in warnings {
w.emit_warning(tcx, trait_def_id)
}
} else {
result = Err(struct_span_code_err!(
for w in warnings {
match w {
FormatWarning::UnknownParam { argument_name, span } => {
let reported = struct_span_code_err!(
tcx.dcx(),
self.span,
span,
E0230,
"there is no parameter `{}` on {}",
s,
if trait_def_id == item_def_id {
format!("trait `{trait_name}`")
} else {
"impl".to_string()
}
"cannot find parameter {} on this trait",
argument_name,
)
.emit());
.emit();
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 { .. } => {}
}
}
}
}
// `{:1}` and `{}` are not to be used
Position::ArgumentIs(..) | Position::ArgumentImplicitlyIs(_) => {
// Errors from the underlying `rustc_parse_format::Parser`
Err(errors) => {
// 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
for e in errors {
if self.is_diagnostic_namespace_variant {
if let Some(item_def_id) = item_def_id.as_local() {
if let Some(trait_def_id) = trait_def_id.as_local() {
tcx.emit_node_span_lint(
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
tcx.local_def_id_to_hir_id(item_def_id),
tcx.local_def_id_to_hir_id(trait_def_id),
self.span,
DisallowedPositionalArgument,
WrappedParserError { description: e.description, label: e.label },
);
}
} else {
@ -887,7 +864,8 @@ impl<'tcx> OnUnimplementedFormatString {
tcx.dcx(),
self.span,
E0231,
"only named generic parameters are allowed"
"{}",
e.description,
)
.emit();
result = Err(reported);
@ -895,29 +873,6 @@ impl<'tcx> OnUnimplementedFormatString {
}
}
}
}
}
// 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
for e in parser.errors {
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,
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);
}
}
result
}
@ -926,85 +881,18 @@ impl<'tcx> OnUnimplementedFormatString {
&self,
tcx: TyCtxt<'tcx>,
trait_ref: ty::TraitRef<'tcx>,
options: &FxHashMap<Symbol, String>,
long_ty_file: &mut Option<PathBuf>,
args: &FormatArgs<'tcx>,
) -> String {
let name = tcx.item_name(trait_ref.def_id);
let trait_str = tcx.def_path_str(trait_ref.def_id);
let generics = tcx.generics_of(trait_ref.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)
let trait_def_id = trait_ref.def_id;
let ctx = if self.is_diagnostic_namespace_variant {
Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }
} else {
trait_ref.args[param.index as usize].to_string()
}
}
GenericParamDefKind::Lifetime => return None,
Ctx::RustcOnUnimplemented { tcx, trait_def_id }
};
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}")
if let Ok(s) = FormatString::parse(self.symbol, self.span, &ctx) {
s.format(args)
} 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
//
@ -1014,10 +902,7 @@ impl<'tcx> OnUnimplementedFormatString {
//
// 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 {
constructed_message
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
#[stable(feature = "env", since = "1.0.0")]
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.
/// 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 const FAMILY: &str = "unix";
pub const OS: &str = "linux";
pub const OS: &str = "aix";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const DLL_SUFFIX: &str = ".a";
pub const DLL_EXTENSION: &str = "a";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "macos")]
#[cfg(target_os = "android")]
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 = "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 OS: &str = "android";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
@ -119,10 +48,10 @@ pub mod os {
pub const EXE_EXTENSION: &str = "exe";
}
#[cfg(target_os = "android")]
#[cfg(target_os = "dragonfly")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "android";
pub const OS: &str = "dragonfly";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
@ -130,10 +59,21 @@ pub mod os {
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "solaris")]
#[cfg(target_os = "emscripten")]
pub mod os {
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_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
@ -141,10 +81,21 @@ pub mod os {
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "illumos")]
#[cfg(target_os = "freebsd")]
pub mod os {
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_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
@ -163,6 +114,17 @@ pub mod os {
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")]
pub mod os {
pub const FAMILY: &str = "unix";
@ -185,35 +147,24 @@ pub mod os {
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "vita")]
#[cfg(target_os = "illumos")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "vita";
pub const OS: &str = "illumos";
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";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(all(target_os = "emscripten", target_arch = "wasm32"))]
#[cfg(target_os = "ios")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "emscripten";
pub const OS: &str = "ios";
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 = "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 DLL_SUFFIX: &str = ".dylib";
pub const DLL_EXTENSION: &str = "dylib";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
@ -229,6 +180,39 @@ pub mod os {
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")]
pub mod os {
pub const FAMILY: &str = "unix";
@ -240,6 +224,28 @@ pub mod os {
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")]
pub mod os {
pub const FAMILY: &str = "unix";
@ -262,6 +268,83 @@ pub mod os {
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")]
pub mod os {
pub const FAMILY: &str = "unix";
@ -273,35 +356,49 @@ pub mod os {
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 const FAMILY: &str = "unix";
pub const OS: &str = "espidf";
pub const OS: &str = "watchos";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
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 = "aix")]
#[cfg(target_os = "windows")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "aix";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".a";
pub const DLL_EXTENSION: &str = "a";
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";
}
// 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_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 backtrace;
pub mod cmath;
pub mod env_consts;
pub mod exit_guard;
pub mod fd;
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;
pub mod env;
pub mod futex;
pub mod os;
#[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};
pub mod abi;
pub mod env;
mod libunwind_integration;
pub mod os;
#[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;
}
pub mod env;
// `error` is `pub(crate)` so that it can be accessed by `itron/error.rs` as
// `crate::sys::error`
pub(crate) mod error;

View file

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

View file

@ -3,8 +3,6 @@
#[path = "../unsupported/common.rs"]
#[deny(unsafe_op_in_unsafe_fn)]
mod common;
#[path = "../unsupported/env.rs"]
pub mod env;
#[path = "../unsupported/os.rs"]
pub mod os;
#[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
#![forbid(unsafe_op_in_unsafe_fn)]
pub mod env;
pub mod helpers;
pub mod os;
#[path = "../unsupported/pipe.rs"]

View file

@ -6,7 +6,6 @@ use crate::io::ErrorKind;
#[macro_use]
pub mod weak;
pub mod env;
#[cfg(target_os = "fuchsia")]
pub mod fuchsia;
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)]
pub mod env;
pub mod os;
pub mod pipe;
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
//! guaranteed to be a runtime error!
pub mod env;
#[allow(unused)]
#[path = "../wasm/atomics/futex.rs"]
pub mod futex;

View file

@ -6,8 +6,6 @@
//! To begin with, this target mirrors the wasi target 1 to 1, but over
//! time this will change significantly.
#[path = "../wasi/env.rs"]
pub mod env;
#[allow(unused)]
#[path = "../wasm/atomics/futex.rs"]
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)]
pub mod env;
#[path = "../unsupported/os.rs"]
pub mod os;
#[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 c;
pub mod env;
#[cfg(not(target_vendor = "win7"))]
pub mod futex;
pub mod handle;

View file

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

View file

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

View file

@ -6,6 +6,7 @@
use std::cell::{Cell, RefCell};
use std::collections::{BTreeSet, HashMap, HashSet};
use std::fmt::{self, Display};
use std::hash::Hash;
use std::io::IsTerminal;
use std::path::{Path, PathBuf, absolute};
use std::process::Command;
@ -701,6 +702,7 @@ pub(crate) struct TomlConfig {
target: Option<HashMap<String, TomlTarget>>,
dist: Option<Dist>,
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"`.
@ -747,27 +749,35 @@ enum ReplaceOpt {
}
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 {
fn merge(
&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,
) {
fn do_merge<T: Merge>(x: &mut Option<T>, y: Option<T>, replace: ReplaceOpt) {
if let Some(new) = y {
if let Some(original) = x {
original.merge(new, replace);
original.merge(None, &mut Default::default(), new, replace);
} else {
*x = Some(new);
}
}
}
self.change_id.inner.merge(change_id.inner, replace);
self.profile.merge(profile, replace);
self.change_id.inner.merge(None, &mut Default::default(), change_id.inner, replace);
self.profile.merge(None, &mut Default::default(), profile, replace);
do_merge(&mut self.build, build, replace);
do_merge(&mut self.install, install, replace);
@ -782,13 +792,50 @@ impl Merge for TomlConfig {
(Some(original_target), Some(new_target)) => {
for (triple, new) in new_target {
if let Some(original) = original_target.get_mut(&triple) {
original.merge(new, replace);
original.merge(None, &mut Default::default(), new, replace);
} else {
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 {
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 {
ReplaceOpt::IgnoreDuplicate => {
@ -903,7 +956,13 @@ macro_rules! define_config {
}
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 {
ReplaceOpt::IgnoreDuplicate => {
if self.is_none() {
@ -1363,13 +1422,15 @@ impl Config {
Self::get_toml(&builder_config_path)
}
pub(crate) fn get_toml(file: &Path) -> Result<TomlConfig, toml::de::Error> {
#[cfg(test)]
pub(crate) fn get_toml(_: &Path) -> Result<TomlConfig, toml::de::Error> {
Ok(TomlConfig::default())
}
return Ok(TomlConfig::default());
#[cfg(not(test))]
pub(crate) fn get_toml(file: &Path) -> Result<TomlConfig, toml::de::Error> {
Self::get_toml_inner(file)
}
fn get_toml_inner(file: &Path) -> Result<TomlConfig, toml::de::Error> {
let contents =
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
@ -1548,7 +1609,8 @@ impl Config {
// but not if `bootstrap.toml` hasn't been created.
let mut toml = if !using_default_path || toml_path.exists() {
config.config = Some(if cfg!(not(test)) {
toml_path.canonicalize().unwrap()
toml_path = toml_path.canonicalize().unwrap();
toml_path.clone()
} else {
toml_path.clone()
});
@ -1576,6 +1638,26 @@ impl Config {
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 {
// Allows creating alias for profile names, allowing
// profiles to be renamed while maintaining back compatibility
@ -1597,7 +1679,12 @@ impl Config {
);
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();
@ -1608,7 +1695,12 @@ impl Config {
let mut err = match get_table(option) {
Ok(v) => {
override_toml.merge(v, ReplaceOpt::ErrorOnDuplicate);
override_toml.merge(
None,
&mut Default::default(),
v,
ReplaceOpt::ErrorOnDuplicate,
);
continue;
}
Err(e) => e,
@ -1619,7 +1711,12 @@ impl Config {
if !value.contains('"') {
match get_table(&format!(r#"{key}="{value}""#)) {
Ok(v) => {
override_toml.merge(v, ReplaceOpt::ErrorOnDuplicate);
override_toml.merge(
None,
&mut Default::default(),
v,
ReplaceOpt::ErrorOnDuplicate,
);
continue;
}
Err(e) => err = e,
@ -1629,7 +1726,7 @@ impl Config {
eprintln!("failed to parse override `{option}`: `{err}");
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;

View file

@ -1,8 +1,8 @@
use std::collections::BTreeSet;
use std::env;
use std::fs::{File, remove_file};
use std::io::Write;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::{env, fs};
use build_helper::ci::CiEnv;
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]
fn download_ci_llvm() {
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(""));
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,
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"
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]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "build_helper"
version = "0.1.0"
@ -104,6 +155,7 @@ name = "citool"
version = "0.1.0"
dependencies = [
"anyhow",
"askama",
"build_helper",
"clap",
"csv",
@ -646,6 +698,12 @@ dependencies = [
"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]]
name = "rustls"
version = "0.23.23"
@ -1026,6 +1084,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
dependencies = [
"memchr",
]
[[package]]
name = "write16"
version = "1.0.0"

View file

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

View file

@ -8,9 +8,9 @@ use build_helper::metrics::{
};
use crate::github::JobInfoResolver;
use crate::metrics;
use crate::metrics::{JobMetrics, JobName, get_test_suites};
use crate::utils::{output_details, pluralize};
use crate::{metrics, utils};
/// Outputs durations of individual bootstrap steps from the gathered bootstrap invocations,
/// 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
let is_doctest = matches!(suite.metadata, TestSuiteMetadata::CargoPackage { .. })
&& 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());
}
}
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.
fn report_test_diffs(
diff: AggregatedTestDiffs,

View file

@ -4,6 +4,7 @@ mod datadog;
mod github;
mod jobs;
mod metrics;
mod test_dashboard;
mod utils;
use std::collections::{BTreeMap, HashMap};
@ -22,7 +23,8 @@ use crate::datadog::upload_datadog_metric;
use crate::github::JobInfoResolver;
use crate::jobs::RunType;
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 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<()> {
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");
let mut job_info_resolver = JobInfoResolver::new();
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);
Ok(())
@ -234,6 +250,14 @@ enum Args {
/// Current commit that will be compared to `parent`.
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)]
@ -275,7 +299,11 @@ fn main() -> anyhow::Result<()> {
postprocess_metrics(metrics_path, parent, job_name)?;
}
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,14 +46,15 @@ pub struct JobMetrics {
/// `parent` and `current` should be commit SHAs.
pub fn download_auto_job_metrics(
job_db: &JobDatabase,
parent: &str,
parent: Option<&str>,
current: &str,
) -> anyhow::Result<HashMap<JobName, JobMetrics>> {
let mut jobs = HashMap::default();
for job in &job_db.auto_jobs {
eprintln!("Downloading metrics of job {}", job.name);
let metrics_parent = match download_job_metrics(&job.name, parent) {
let metrics_parent =
parent.and_then(|parent| match download_job_metrics(&job.name, parent) {
Ok(metrics) => Some(metrics),
Err(error) => {
eprintln!(
@ -63,7 +64,7 @@ Maybe it was newly added?"#,
);
None
}
};
});
let metrics_current = download_job_metrics(&job.name, current)?;
jobs.insert(
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 anyhow::Context;
@ -28,3 +29,8 @@ where
func();
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`!
## 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`
### Project-local rust-analyzer setup

View file

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

View file

@ -1397,7 +1397,6 @@ ui/issues/auxiliary/issue-13620-1.rs
ui/issues/auxiliary/issue-13620-2.rs
ui/issues/auxiliary/issue-14344-1.rs
ui/issues/auxiliary/issue-14344-2.rs
ui/issues/auxiliary/issue-14421.rs
ui/issues/auxiliary/issue-14422.rs
ui/issues/auxiliary/issue-15562.rs
ui/issues/auxiliary/issue-16643.rs
@ -1564,7 +1563,6 @@ ui/issues/issue-14366.rs
ui/issues/issue-14382.rs
ui/issues/issue-14393.rs
ui/issues/issue-14399.rs
ui/issues/issue-14421.rs
ui/issues/issue-14422.rs
ui/issues/issue-14541.rs
ui/issues/issue-14721.rs
@ -1629,7 +1627,6 @@ ui/issues/issue-16774.rs
ui/issues/issue-16783.rs
ui/issues/issue-16819.rs
ui/issues/issue-16922-rpass.rs
ui/issues/issue-16939.rs
ui/issues/issue-16966.rs
ui/issues/issue-16994.rs
ui/issues/issue-17001.rs
@ -1867,7 +1864,6 @@ ui/issues/issue-23550.rs
ui/issues/issue-23589.rs
ui/issues/issue-23699.rs
ui/issues/issue-2380-b.rs
ui/issues/issue-23808.rs
ui/issues/issue-2383.rs
ui/issues/issue-23891.rs
ui/issues/issue-23898.rs
@ -2607,7 +2603,6 @@ ui/issues/issue-9249.rs
ui/issues/issue-9259.rs
ui/issues/issue-92741.rs
ui/issues/issue-9446.rs
ui/issues/issue-9719.rs
ui/issues/issue-9725.rs
ui/issues/issue-9737.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-57731-ascibed-coupled-types.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-67514-irrefutable-param.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;
// 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] = &[
"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.
// So, we must emit an appropriate instruction instead!
#[no_mangle]
#[naked]
pub unsafe extern "C" fn _hlt() -> ! {
#[unsafe(naked)]
pub extern "C" fn _hlt() -> ! {
// CHECK-NOT: hint #34
// CHECK: hlt #0x1
naked_asm!("hlt #1")

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -28,11 +28,10 @@ fn test(x: u64) {
// CHECK: add rax, 1
// CHECK: add rax, 42
#[naked]
#[unsafe(naked)]
pub extern "C" fn using_const_generics<const N: u64>(x: u64) -> u64 {
const M: u64 = 42;
unsafe {
naked_asm!(
"xor rax, rax",
"add rax, rdi",
@ -43,7 +42,6 @@ pub extern "C" fn using_const_generics<const N: u64>(x: u64) -> u64 {
const M,
)
}
}
trait Invert {
fn invert(self) -> Self;
@ -60,17 +58,15 @@ impl Invert for i64 {
// CHECK: call
// CHECK: ret
#[naked]
#[unsafe(naked)]
#[no_mangle]
pub extern "C" fn generic_function<T: Invert>(x: i64) -> i64 {
unsafe {
naked_asm!(
"call {}",
"ret",
sym <T as Invert>::invert,
)
}
}
#[derive(Copy, Clone)]
#[repr(transparent)]
@ -81,10 +77,10 @@ struct Foo(u64);
// CHECK: mov rax, rdi
impl Foo {
#[naked]
#[unsafe(naked)]
#[no_mangle]
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 {
#[naked]
#[unsafe(naked)]
#[no_mangle]
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]
// this previously ICE'd, see https://github.com/rust-lang/rust/issues/124375
#[naked]
#[unsafe(naked)]
#[no_mangle]
pub unsafe extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize {
naked_asm!("lea rax, [rdi + rsi]", "ret");

View file

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

View file

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

View file

@ -60,8 +60,8 @@ use minicore::*;
// linux,win: .att_syntax
#[no_mangle]
#[naked]
pub unsafe extern "C" fn naked_empty() {
#[unsafe(naked)]
pub extern "C" fn naked_empty() {
#[cfg(not(all(target_arch = "arm", target_feature = "thumb-mode")))]
naked_asm!("ret");
@ -114,8 +114,8 @@ pub unsafe extern "C" fn naked_empty() {
// linux,win: .att_syntax
#[no_mangle]
#[naked]
pub unsafe extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize {
#[unsafe(naked)]
pub extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize {
#[cfg(any(target_os = "windows", target_os = "linux"))]
{
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
// CHECK-LABEL: test_link_section:
#[no_mangle]
#[naked]
#[unsafe(naked)]
#[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")))]
naked_asm!("ret");
@ -159,7 +159,7 @@ pub unsafe extern "C" fn test_link_section() {
// win_i686-LABEL: @fastcall_cc@4:
#[cfg(target_os = "windows")]
#[no_mangle]
#[naked]
pub unsafe extern "fastcall" fn fastcall_cc(x: i32) -> i32 {
#[unsafe(naked)]
pub extern "fastcall" fn fastcall_cc(x: i32) -> i32 {
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
}
#[naked]
#[unsafe(naked)]
extern "C" fn private_naked() -> u32 {
unsafe { naked_asm!("mov rax, 42", "ret") }
naked_asm!("mov rax, 42", "ret")
}
#[no_mangle]
@ -36,19 +36,19 @@ pub extern "C" fn public_vanilla() -> u32 {
42
}
#[naked]
#[unsafe(naked)]
#[no_mangle]
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 {
T::COUNT
}
#[naked]
#[unsafe(naked)]
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"]
@ -56,10 +56,10 @@ extern "C" fn vanilla_external_linkage() -> u32 {
42
}
#[naked]
#[unsafe(naked)]
#[linkage = "external"]
extern "C" fn naked_external_linkage() -> u32 {
unsafe { naked_asm!("mov rax, 42", "ret") }
naked_asm!("mov rax, 42", "ret")
}
#[cfg(not(windows))]
@ -68,11 +68,11 @@ extern "C" fn vanilla_weak_linkage() -> u32 {
42
}
#[naked]
#[unsafe(naked)]
#[cfg(not(windows))]
#[linkage = "weak"]
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

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) {
t(1i32);
//~^ 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()) {
t(1i32);
//~^ 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
--> $DIR/exotic-calls.rs:2:5
--> $DIR/exotic-calls.rs:6:5
|
LL | t(1i32);
| ^ ---- unexpected argument of type `i32`
|
note: callable defined here
--> $DIR/exotic-calls.rs:1:11
--> $DIR/exotic-calls.rs:5:11
|
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
--> $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);
| ^ ---- unexpected argument of type `i32`
|
note: type parameter defined here
--> $DIR/exotic-calls.rs:6:11
--> $DIR/exotic-calls.rs:16:11
|
LL | fn bar(t: impl Fn()) {
| ^^^^^^^^^
@ -33,13 +50,13 @@ LL + t();
|
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)
| ^^^^^ ---- unexpected argument of type `i32`
|
note: opaque type defined here
--> $DIR/exotic-calls.rs:11:13
--> $DIR/exotic-calls.rs:21:13
|
LL | fn baz() -> impl Fn() {
| ^^^^^^^^^
@ -50,13 +67,13 @@ LL + baz()()
|
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);
| ^ ---- unexpected argument of type `i32`
|
note: closure defined here
--> $DIR/exotic-calls.rs:21:13
--> $DIR/exotic-calls.rs:31:13
|
LL | let x = || {};
| ^^
@ -66,6 +83,6 @@ LL - x(1i32);
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`.

View file

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

View file

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

View file

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

View file

@ -4,35 +4,35 @@
use std::arch::naked_asm;
#[naked]
pub unsafe extern "C" fn inline_none() {
#[unsafe(naked)]
pub extern "C" fn inline_none() {
naked_asm!("");
}
#[naked]
#[unsafe(naked)]
#[inline]
//~^ ERROR [E0736]
pub unsafe extern "C" fn inline_hint() {
pub extern "C" fn inline_hint() {
naked_asm!("");
}
#[naked]
#[unsafe(naked)]
#[inline(always)]
//~^ ERROR [E0736]
pub unsafe extern "C" fn inline_always() {
pub extern "C" fn inline_always() {
naked_asm!("");
}
#[naked]
#[unsafe(naked)]
#[inline(never)]
//~^ ERROR [E0736]
pub unsafe extern "C" fn inline_never() {
pub extern "C" fn inline_never() {
naked_asm!("");
}
#[naked]
#[unsafe(naked)]
#[cfg_attr(all(), inline(never))]
//~^ ERROR [E0736]
pub unsafe extern "C" fn conditional_inline_never() {
pub extern "C" fn conditional_inline_never() {
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
|
LL | #[naked]
| -------- function marked with `#[naked]` here
LL | #[unsafe(naked)]
| ---------------- function marked with `#[unsafe(naked)]` here
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
|
LL | #[naked]
| -------- function marked with `#[naked]` here
LL | #[unsafe(naked)]
| ---------------- function marked with `#[unsafe(naked)]` here
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
|
LL | #[naked]
| -------- function marked with `#[naked]` here
LL | #[unsafe(naked)]
| ---------------- function marked with `#[unsafe(naked)]` here
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
|
LL | #[naked]
| -------- function marked with `#[naked]` here
LL | #[unsafe(naked)]
| ---------------- function marked with `#[unsafe(naked)]` here
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

View file

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

View file

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

View file

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

View file

@ -8,31 +8,31 @@
use std::arch::naked_asm;
#[test]
#[naked]
#[unsafe(naked)]
//~^ ERROR [E0736]
extern "C" fn test_naked() {
unsafe { naked_asm!("") };
naked_asm!("")
}
#[should_panic]
#[test]
#[naked]
#[unsafe(naked)]
//~^ ERROR [E0736]
extern "C" fn test_naked_should_panic() {
unsafe { naked_asm!("") };
naked_asm!("")
}
#[ignore]
#[test]
#[naked]
#[unsafe(naked)]
//~^ ERROR [E0736]
extern "C" fn test_naked_ignore() {
unsafe { naked_asm!("") };
naked_asm!("")
}
#[bench]
#[naked]
#[unsafe(naked)]
//~^ ERROR [E0736]
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
|
LL | #[test]
| ------- function marked with testing attribute here
LL | #[naked]
| ^^^^^^^^ `#[naked]` is incompatible with testing attributes
LL | #[unsafe(naked)]
| ^^^^^^^^^^^^^^^^ `#[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
|
LL | #[test]
| ------- function marked with testing attribute here
LL | #[naked]
| ^^^^^^^^ `#[naked]` is incompatible with testing attributes
LL | #[unsafe(naked)]
| ^^^^^^^^^^^^^^^^ `#[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
|
LL | #[test]
| ------- function marked with testing attribute here
LL | #[naked]
| ^^^^^^^^ `#[naked]` is incompatible with testing attributes
LL | #[unsafe(naked)]
| ^^^^^^^^^^^^^^^^ `#[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
|
LL | #[bench]
| -------- function marked with testing attribute here
LL | #[naked]
| ^^^^^^^^ `#[naked]` is incompatible with testing attributes
LL | #[unsafe(naked)]
| ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes
error: aborting due to 4 previous errors

View file

@ -64,44 +64,34 @@ pub mod normal {
pub mod naked {
use std::arch::naked_asm;
#[naked]
#[unsafe(naked)]
pub extern "C" fn function(a: usize, b: usize) -> usize {
unsafe {
naked_asm!("");
}
naked_asm!("")
}
pub struct Naked;
impl Naked {
#[naked]
#[unsafe(naked)]
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 {
unsafe {
naked_asm!("");
}
naked_asm!("")
}
}
impl super::Trait for Naked {
#[naked]
#[unsafe(naked)]
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 {
unsafe {
naked_asm!("");
}
naked_asm!("")
}
}
}

View file

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

View file

@ -11,69 +11,69 @@ LL | in(reg) a,
| ^^ the `in` operand is not meaningful for global-scoped inline assembly, remove it
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));
| ^^^^^^^^ the `noreturn` option is not meaningful for global-scoped inline assembly
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));
| ^^^^^ the `nomem` option is not meaningful for global-scoped inline assembly
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));
| ^^^^^^^^^^^^^^^ the `preserves_flags` option is not meaningful for global-scoped inline assembly
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));
| ^^^^^^^^ the `readonly` option is not meaningful for global-scoped inline assembly
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));
| ^^^^^^^ the `nostack` option is not meaningful for global-scoped inline assembly
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));
| ^^^^ the `pure` option is not meaningful for global-scoped inline assembly
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));
| ^^^^^^^^^^ the `may_unwind` option is not meaningful for global-scoped inline assembly
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")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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)
| ^^^^^^^^^^^^^^
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
error: patterns not allowed in naked function parameters
@ -111,8 +111,8 @@ LL | a + 1
error[E0787]: naked functions must contain a single `naked_asm!` invocation
--> $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 | a + 1
| ----- not allowed in naked functions
@ -120,8 +120,8 @@ LL | a + 1
error[E0787]: naked functions must contain a single `naked_asm!` invocation
--> $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 | (|| a + 1)()
| ------------ not allowed in naked functions
@ -129,8 +129,8 @@ LL | (|| a + 1)()
error[E0787]: naked functions must contain a single `naked_asm!` invocation
--> $DIR/naked-functions.rs:58:1
|
LL | pub unsafe extern "C" fn unsupported_operands() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | pub extern "C" fn unsupported_operands() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL |
LL | let mut a = 0usize;
| ------------------- not allowed in naked functions
@ -159,7 +159,7 @@ LL | naked_asm!("");
| -------------- multiple `naked_asm!` invocations are 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
| ^
@ -167,7 +167,7 @@ LL | *&y
= help: follow the calling convention in asm block to use parameters
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 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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
#![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;
extern "C" {
#[naked] //~ ERROR should be applied to a function definition
#[unsafe(naked)] //~ ERROR should be applied to a function definition
fn f();
}
#[naked] //~ ERROR should be applied to a function definition
#[unsafe(naked)] //~ ERROR should be applied to a function definition
#[repr(C)]
struct S {
a: u32,
@ -19,35 +19,35 @@ struct S {
}
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);
}
impl Invoke for S {
#[naked]
#[unsafe(naked)]
extern "C" fn invoke(&self) {
unsafe { naked_asm!("") }
naked_asm!("")
}
}
#[naked]
#[unsafe(naked)]
extern "C" fn ok() {
unsafe { naked_asm!("") }
naked_asm!("")
}
impl S {
#[naked]
#[unsafe(naked)]
extern "C" fn g() {
unsafe { naked_asm!("") }
naked_asm!("")
}
#[naked]
#[unsafe(naked)]
extern "C" fn h(&self) {
unsafe { naked_asm!("") }
naked_asm!("")
}
}
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
--> $DIR/naked-invalid-attr.rs:14:1
|
LL | #[naked]
| ^^^^^^^^
LL | #[unsafe(naked)]
| ^^^^^^^^^^^^^^^^
LL | #[repr(C)]
LL | / struct S {
LL | | a: u32,
@ -13,32 +13,32 @@ LL | | }
error: attribute should be applied to a function definition
--> $DIR/naked-invalid-attr.rs:51:5
|
LL | #[naked]
| ^^^^^^^^
LL | #[unsafe(naked)]
| ^^^^^^^^^^^^^^^^
LL | || {};
| ----- not a function definition
error: attribute should be applied to a function definition
--> $DIR/naked-invalid-attr.rs:22:5
|
LL | #[naked]
| ^^^^^^^^
LL | #[unsafe(naked)]
| ^^^^^^^^^^^^^^^^
LL | extern "C" fn invoke(&self);
| ---------------------------- not a function definition
error: attribute should be applied to a function definition
--> $DIR/naked-invalid-attr.rs:10:5
|
LL | #[naked]
| ^^^^^^^^
LL | #[unsafe(naked)]
| ^^^^^^^^^^^^^^^^
LL | fn f();
| ------- not a function definition
error: attribute should be applied to a function definition
--> $DIR/naked-invalid-attr.rs:5:1
|
LL | #![naked]
| ^^^^^^^^^ cannot be applied to crates
LL | #![unsafe(naked)]
| ^^^^^^^^^^^^^^^^^ cannot be applied to crates
error: aborting due to 5 previous errors

View file

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