diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 336c644961ab6..ab3c44dac6a02 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -11,6 +11,7 @@ use rustc_hir::{ }; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::span_bug; +use rustc_middle::ty::data_structures::IndexMap; use rustc_middle::ty::{ResolverAstLowering, TyCtxt}; use rustc_span::def_id::DefId; use rustc_span::edit_distance::find_best_match_for_name; @@ -326,7 +327,6 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, (ty, rhs)) = self.lower_generics( generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let ty = this.lower_ty_alloc( @@ -380,7 +380,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ); let itctx = ImplTraitContext::Universal; - let (generics, decl) = this.lower_generics(generics, id, itctx, |this| { + let (generics, decl) = this.lower_generics(generics, itctx, |this| { this.lower_fn_decl(decl, id, *fn_sig_span, FnDeclKind::Fn, coroutine_kind) }); let sig = hir::FnSig { @@ -434,7 +434,6 @@ impl<'hir> LoweringContext<'_, 'hir> { add_ty_alias_where_clause(&mut generics, after_where_clause, true); let (generics, ty) = self.lower_generics( &generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| match ty { None => { @@ -461,7 +460,6 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, variants) = self.lower_generics( generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { this.arena.alloc_from_iter( @@ -475,7 +473,6 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, struct_def) = self.lower_generics( generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| this.lower_variant_data(hir_id, i, struct_def), ); @@ -485,7 +482,6 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, vdata) = self.lower_generics( generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| this.lower_variant_data(hir_id, i, vdata), ); @@ -513,7 +509,7 @@ impl<'hir> LoweringContext<'_, 'hir> { // parent lifetime. let itctx = ImplTraitContext::Universal; let (generics, (of_trait, lowered_ty)) = - self.lower_generics(ast_generics, id, itctx, |this| { + self.lower_generics(ast_generics, itctx, |this| { let of_trait = of_trait .as_deref() .map(|of_trait| this.lower_trait_impl_header(of_trait)); @@ -555,7 +551,6 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, (safety, items, bounds)) = self.lower_generics( generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let bounds = this.lower_param_bounds( @@ -586,7 +581,6 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, bounds) = self.lower_generics( generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { this.lower_param_bounds( @@ -798,14 +792,13 @@ impl<'hir> LoweringContext<'_, 'hir> { ForeignItemKind::Fn(Fn { sig, ident, generics, define_opaque, .. }) => { let fdec = &sig.decl; let itctx = ImplTraitContext::Universal; - let (generics, (decl, fn_args)) = - self.lower_generics(generics, i.id, itctx, |this| { - ( - // Disallow `impl Trait` in foreign items. - this.lower_fn_decl(fdec, i.id, sig.span, FnDeclKind::ExternFn, None), - this.lower_fn_params_to_idents(fdec), - ) - }); + let (generics, (decl, fn_args)) = self.lower_generics(generics, itctx, |this| { + ( + // Disallow `impl Trait` in foreign items. + this.lower_fn_decl(fdec, i.id, sig.span, FnDeclKind::ExternFn, None), + this.lower_fn_params_to_idents(fdec), + ) + }); // Unmarked safety in unsafe block defaults to unsafe. let header = self.lower_fn_header(sig.header, hir::Safety::Unsafe, attrs); @@ -995,7 +988,6 @@ impl<'hir> LoweringContext<'_, 'hir> { }) => { let (generics, kind) = self.lower_generics( generics, - i.id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let ty = this.lower_ty_alloc( @@ -1097,7 +1089,6 @@ impl<'hir> LoweringContext<'_, 'hir> { add_ty_alias_where_clause(&mut generics, after_where_clause, false); let (generics, kind) = self.lower_generics( &generics, - i.id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let ty = ty.as_ref().map(|x| { @@ -1109,7 +1100,7 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::TraitItemKind::Type( this.lower_param_bounds( bounds, - RelaxedBoundPolicy::Allowed, + RelaxedBoundPolicy::Allowed(&mut Default::default()), ImplTraitContext::Disallowed(ImplTraitPosition::Generic), ), ty, @@ -1260,7 +1251,6 @@ impl<'hir> LoweringContext<'_, 'hir> { *ident, self.lower_generics( generics, - i.id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let ty = this.lower_ty_alloc( @@ -1305,7 +1295,6 @@ impl<'hir> LoweringContext<'_, 'hir> { *ident, self.lower_generics( &generics, - i.id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| match ty { None => { @@ -1744,7 +1733,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ) -> (&'hir hir::Generics<'hir>, hir::FnSig<'hir>) { let header = self.lower_fn_header(sig.header, hir::Safety::Safe, attrs); let itctx = ImplTraitContext::Universal; - let (generics, decl) = self.lower_generics(generics, id, itctx, |this| { + let (generics, decl) = self.lower_generics(generics, itctx, |this| { this.lower_fn_decl(&sig.decl, id, sig.span, kind, coroutine_kind) }); (generics, hir::FnSig { header, decl, span: self.lower_span(sig.span) }) @@ -1903,7 +1892,6 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_generics( &mut self, generics: &Generics, - parent_node_id: NodeId, itctx: ImplTraitContext, f: impl FnOnce(&mut Self) -> T, ) -> (&'hir hir::Generics<'hir>, T) { @@ -1911,6 +1899,9 @@ impl<'hir> LoweringContext<'_, 'hir> { assert!(self.impl_trait_bounds.is_empty()); let mut predicates: SmallVec<[hir::WherePredicate<'hir>; 4]> = SmallVec::new(); + // We need to make sure that generic params don't have multiple relaxed bounds for the same trait + // across generic param bounds and where bounds. + let mut dedup_map: IndexMap = Default::default(); predicates.extend(generics.params.iter().filter_map(|param| { self.lower_generic_bound_predicate( param.ident, @@ -1919,25 +1910,23 @@ impl<'hir> LoweringContext<'_, 'hir> { ¶m.bounds, param.colon_span, generics.span, - RelaxedBoundPolicy::Allowed, + RelaxedBoundPolicy::Allowed( + dedup_map.entry(self.local_def_id(param.id)).or_default(), + ), itctx, PredicateOrigin::GenericParam, ) })); - predicates.extend( - generics - .where_clause - .predicates - .iter() - .map(|predicate| self.lower_where_predicate(predicate, &generics.params)), - ); + predicates.extend(generics.where_clause.predicates.iter().map(|predicate| { + self.lower_where_predicate(predicate, &generics.params, &mut dedup_map) + })); let mut params: SmallVec<[hir::GenericParam<'hir>; 4]> = self .lower_generic_params_mut(&generics.params, hir::GenericParamSource::Generics) .collect(); // Introduce extra lifetimes if late resolution tells us to. - let extra_lifetimes = self.resolver.extra_lifetime_params(parent_node_id); + let extra_lifetimes = self.resolver.extra_lifetime_params(self.owner.id); params.extend(extra_lifetimes.into_iter().map(|&(ident, node_id, kind)| { self.lifetime_res_to_generic_param( ident, @@ -2006,7 +1995,7 @@ impl<'hir> LoweringContext<'_, 'hir> { bounds: &[GenericBound], colon_span: Option, parent_span: Span, - rbp: RelaxedBoundPolicy, + rbp: RelaxedBoundPolicy<'_>, itctx: ImplTraitContext, origin: PredicateOrigin, ) -> Option> { @@ -2071,6 +2060,7 @@ impl<'hir> LoweringContext<'_, 'hir> { &mut self, pred: &WherePredicate, params: &[ast::GenericParam], + dedup_map: &mut IndexMap>, ) -> hir::WherePredicate<'hir> { let hir_id = self.lower_node_id(pred.id); let span = self.lower_span(pred.span); @@ -2087,7 +2077,7 @@ impl<'hir> LoweringContext<'_, 'hir> { && let Res::Def(DefKind::TyParam, def_id) = res && params.iter().any(|p| def_id == self.local_def_id(p.id).to_def_id()) { - RelaxedBoundPolicy::Allowed + RelaxedBoundPolicy::Allowed(dedup_map.entry(def_id.expect_local()).or_default()) } else { RelaxedBoundPolicy::Forbidden(RelaxedBoundForbiddenReason::WhereBound) }; @@ -2117,7 +2107,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ), bounds: self.lower_param_bounds( bounds, - RelaxedBoundPolicy::Allowed, + RelaxedBoundPolicy::Allowed(&mut Default::default()), ImplTraitContext::Disallowed(ImplTraitPosition::Bound), ), in_where_clause: true, diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 8b4a2795ec90c..a18f0c2f69548 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -43,14 +43,15 @@ use rustc_ast::visit::Visitor; use rustc_ast::{self as ast, *}; use rustc_attr_parsing::{AttributeParser, OmitDoc, Recovery, ShouldEmit}; use rustc_data_structures::fingerprint::Fingerprint; -use rustc_data_structures::fx::FxIndexSet; +use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::sorted_map::SortedMap; use rustc_data_structures::stable_hash::{StableHash, StableHasher}; use rustc_data_structures::steal::Steal; use rustc_data_structures::tagged_ptr::TaggedRef; +use rustc_errors::codes::*; use rustc_errors::{DiagArgFromDisplay, DiagCtxtHandle}; use rustc_hir::def::{DefKind, LifetimeRes, Namespace, PartialRes, PerNS, Res}; -use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId}; +use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::definitions::PerParentDisambiguatorState; use rustc_hir::lints::DelayedLint; use rustc_hir::{ @@ -319,11 +320,20 @@ impl<'tcx> ResolverAstLowering<'tcx> { /// /// Relaxed bounds should only be allowed in places where we later /// (namely during HIR ty lowering) perform *sized elaboration*. -#[derive(Clone, Copy, Debug)] -enum RelaxedBoundPolicy { - Allowed, +#[derive(Debug)] +enum RelaxedBoundPolicy<'a> { + /// The `DefId` refers to the trait that is being relaxed. + Allowed(&'a mut FxIndexMap), Forbidden(RelaxedBoundForbiddenReason), } +impl RelaxedBoundPolicy<'_> { + fn reborrow(&mut self) -> RelaxedBoundPolicy<'_> { + match self { + RelaxedBoundPolicy::Allowed(m) => RelaxedBoundPolicy::Allowed(m), + RelaxedBoundPolicy::Forbidden(reason) => RelaxedBoundPolicy::Forbidden(*reason), + } + } +} #[derive(Clone, Copy, Debug)] enum RelaxedBoundForbiddenReason { @@ -1551,9 +1561,13 @@ impl<'hir> LoweringContext<'_, 'hir> { } path } - ImplTraitContext::InBinding => hir::TyKind::TraitAscription( - self.lower_param_bounds(bounds, RelaxedBoundPolicy::Allowed, itctx), - ), + ImplTraitContext::InBinding => { + hir::TyKind::TraitAscription(self.lower_param_bounds( + bounds, + RelaxedBoundPolicy::Allowed(&mut Default::default()), + itctx, + )) + } ImplTraitContext::FeatureGated(position, feature) => { let guar = self .tcx @@ -1676,7 +1690,11 @@ impl<'hir> LoweringContext<'_, 'hir> { let opaque_ty_span = self.mark_span_with_reason(DesugaringKind::OpaqueTy, span, None); self.lower_opaque_inner(opaque_ty_node_id, origin, opaque_ty_span, |this| { - this.lower_param_bounds(bounds, RelaxedBoundPolicy::Allowed, itctx) + this.lower_param_bounds( + bounds, + RelaxedBoundPolicy::Allowed(&mut Default::default()), + itctx, + ) }) } @@ -1970,7 +1988,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_param_bound( &mut self, tpb: &GenericBound, - rbp: RelaxedBoundPolicy, + rbp: RelaxedBoundPolicy<'_>, itctx: ImplTraitContext, ) -> hir::GenericBound<'hir> { match tpb { @@ -2209,7 +2227,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_poly_trait_ref( &mut self, PolyTraitRef { bound_generic_params, modifiers, trait_ref, span, parens: _ }: &PolyTraitRef, - rbp: RelaxedBoundPolicy, + rbp: RelaxedBoundPolicy<'_>, itctx: ImplTraitContext, ) -> hir::PolyTraitRef<'hir> { let bound_generic_params = @@ -2233,7 +2251,7 @@ impl<'hir> LoweringContext<'_, 'hir> { &self, trait_ref: hir::TraitRef<'_>, span: Span, - rbp: RelaxedBoundPolicy, + rbp: RelaxedBoundPolicy<'_>, ) { // Even though feature `more_maybe_bounds` enables the user to relax all default bounds // other than `Sized` in a lot more positions (thereby bypassing the given policy), we don't @@ -2245,7 +2263,23 @@ impl<'hir> LoweringContext<'_, 'hir> { // question: E.g., `?Sized` & `?Move` most likely won't be allowed in all the same places). match rbp { - RelaxedBoundPolicy::Allowed => return, + RelaxedBoundPolicy::Allowed(dedup_map) => { + // `trait_def_id` only returns `None` for errors during resolution. + let Some(trait_def_id) = trait_ref.trait_def_id() else { return }; + let tcx = self.tcx; + let err = |s| { + let name = tcx.item_name(trait_def_id); + tcx.dcx() + .struct_span_err( + vec![span, s], + format!("duplicate relaxed `{name}` bounds"), + ) + .with_code(E0203) + .emit(); + }; + dedup_map.entry(trait_def_id).and_modify(|&mut s| err(s)).or_insert(span); + return; + } RelaxedBoundPolicy::Forbidden(reason) => { let gate = |context, subject| { let extended = self.tcx.features().more_maybe_bounds(); @@ -2307,7 +2341,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_param_bounds( &mut self, bounds: &[GenericBound], - rbp: RelaxedBoundPolicy, + rbp: RelaxedBoundPolicy<'_>, itctx: ImplTraitContext, ) -> hir::GenericBounds<'hir> { self.arena.alloc_from_iter(self.lower_param_bounds_mut(bounds, rbp, itctx)) @@ -2316,10 +2350,10 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_param_bounds_mut( &mut self, bounds: &[GenericBound], - rbp: RelaxedBoundPolicy, + mut rbp: RelaxedBoundPolicy<'_>, itctx: ImplTraitContext, ) -> impl Iterator> { - bounds.iter().map(move |bound| self.lower_param_bound(bound, rbp, itctx)) + bounds.iter().map(move |bound| self.lower_param_bound(bound, rbp.reborrow(), itctx)) } #[instrument(level = "debug", skip(self), ret)] @@ -2353,7 +2387,7 @@ impl<'hir> LoweringContext<'_, 'hir> { bounds, /* colon_span */ None, span, - RelaxedBoundPolicy::Allowed, + RelaxedBoundPolicy::Allowed(&mut Default::default()), ImplTraitContext::Universal, hir::PredicateOrigin::ImplTrait, ); diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 081de1ac2de6b..919a8e553263b 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -352,7 +352,8 @@ impl<'a> AstValidator<'a> { fn check_fn_decl(&self, fn_decl: &FnDecl, self_semantic: SelfSemantic) { self.check_decl_num_args(fn_decl); - self.check_decl_cvariadic_pos(fn_decl); + let c_variadic_span = self.check_decl_cvariadic_pos(fn_decl); + self.check_decl_splatting(fn_decl, c_variadic_span); self.check_decl_attrs(fn_decl); self.check_decl_self_param(fn_decl, self_semantic); } @@ -370,17 +371,59 @@ impl<'a> AstValidator<'a> { /// Emits an error if a function declaration has a variadic parameter in the /// beginning or middle of parameter list. /// Example: `fn foo(..., x: i32)` will emit an error. - fn check_decl_cvariadic_pos(&self, fn_decl: &FnDecl) { + /// If a C-variadic parameter is found, returns its span. + fn check_decl_cvariadic_pos(&self, fn_decl: &FnDecl) -> Option { + let mut c_variadic_span = None; + match &*fn_decl.inputs { [ps @ .., _] => { for Param { ty, span, .. } in ps { if let TyKind::CVarArgs = ty.kind { + c_variadic_span = Some(*span); self.dcx().emit_err(diagnostics::FnParamCVarArgsNotLast { span: *span }); } } } _ => {} } + + if let Some(Param { ty, span, .. }) = &fn_decl.inputs.last() + && let TyKind::CVarArgs = ty.kind + { + c_variadic_span = Some(*span); + } + + c_variadic_span + } + + /// Emits an error if a function declaration has more than one splatted argument, with a + /// C-variadic parameter, or a splat at an unsupported index (for performance). + /// Example: `fn foo(#[splat] x: (), #[splat] y: ())` will emit an error. + fn check_decl_splatting(&self, fn_decl: &FnDecl, c_variadic_span: Option) { + let (splatted_arg_indexes, mut splatted_spans): (Vec, Vec) = fn_decl + .inputs + .iter() + .enumerate() + .filter_map(|(index, arg)| { + arg.attrs + .iter() + .any(|attr| attr.has_name(sym::splat)) + .then_some((u16::try_from(index).unwrap(), arg.span)) + }) + .unzip(); + + // Multiple splatted arguments are invalid: we can't know which arguments go in each splat. + if splatted_arg_indexes.len() > 1 { + self.dcx() + .emit_err(diagnostics::DuplicateSplattedArgs { spans: splatted_spans.clone() }); + } + + if let Some(c_variadic_span) = c_variadic_span + && !splatted_spans.is_empty() + { + splatted_spans.push(c_variadic_span); + self.dcx().emit_err(diagnostics::CVarArgsAndSplat { spans: splatted_spans }); + } } fn check_decl_attrs(&self, fn_decl: &FnDecl) { @@ -396,6 +439,7 @@ impl<'a> AstValidator<'a> { sym::deny, sym::expect, sym::forbid, + sym::splat, sym::warn, ]; !attr.has_any_name(&arr) && rustc_attr_parsing::is_builtin_attr(*attr) diff --git a/compiler/rustc_ast_passes/src/diagnostics.rs b/compiler/rustc_ast_passes/src/diagnostics.rs index 89284ca3524fd..6877b33a564ff 100644 --- a/compiler/rustc_ast_passes/src/diagnostics.rs +++ b/compiler/rustc_ast_passes/src/diagnostics.rs @@ -123,6 +123,22 @@ pub(crate) struct FnParamCVarArgsNotLast { pub span: Span, } +#[derive(Diagnostic)] +#[diag("multiple `#[splat]`s are not allowed in the same function")] +#[help("remove `#[splat]` from all but one argument")] +pub(crate) struct DuplicateSplattedArgs { + #[primary_span] + pub spans: Vec, +} + +#[derive(Diagnostic)] +#[diag("`...` and `#[splat]` are not allowed in the same function")] +#[help("remove `#[splat]` or remove `...`")] +pub(crate) struct CVarArgsAndSplat { + #[primary_span] + pub spans: Vec, +} + #[derive(Diagnostic)] #[diag("documentation comments cannot be applied to function parameters")] pub(crate) struct FnParamDocComment { @@ -131,6 +147,7 @@ pub(crate) struct FnParamDocComment { pub span: Span, } +// FIXME(splat): add splat to the allowed built-in attributes when it is complete/stabilized #[derive(Diagnostic)] #[diag( "allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters" diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 09672d1e69f11..fbe2251b5e688 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -500,6 +500,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(pin_ergonomics, "pinned reference syntax is experimental"); gate_all!(postfix_match, "postfix match is experimental"); gate_all!(return_type_notation, "return type notation is experimental"); + gate_all!(splat, "`fn(#[splat] (a, ...))` is incomplete", "call as func((a, ...)) instead"); gate_all!(super_let, "`super let` is experimental"); gate_all!(try_blocks_heterogeneous, "`try bikeshed` expression is experimental"); gate_all!(unnamed_enum_variants, "unnamed enum variants are experimental"); diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 325db4f0250b6..7ea4c7fc6fe73 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -69,6 +69,7 @@ pub(crate) mod rustc_allocator; pub(crate) mod rustc_dump; pub(crate) mod rustc_internal; pub(crate) mod semantics; +pub(crate) mod splat; pub(crate) mod stability; pub(crate) mod test_attrs; pub(crate) mod traits; diff --git a/compiler/rustc_attr_parsing/src/attributes/splat.rs b/compiler/rustc_attr_parsing/src/attributes/splat.rs new file mode 100644 index 0000000000000..65fc9fb8123f4 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/splat.rs @@ -0,0 +1,16 @@ +//! Attribute parsing for the `#[splat]` function argument overloading attribute. +//! This attribute modifies typecheck to support overload resolution, then modifies codegen for performance. + +use rustc_feature::AttributeStability; + +use super::prelude::*; + +pub(crate) struct SplatParser; + +impl NoArgsAttributeParser for SplatParser { + const PATH: &[Symbol] = &[sym::splat]; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Param)]); + const STABILITY: AttributeStability = + unstable!(splat, "the `#[splat]` attribute is experimental"); + const CREATE: fn(Span) -> AttributeKind = AttributeKind::Splat; +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 8da5058da7ac8..e3f376fa341ef 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -57,6 +57,7 @@ use crate::attributes::rustc_allocator::*; use crate::attributes::rustc_dump::*; use crate::attributes::rustc_internal::*; use crate::attributes::semantics::{ComptimeParser, *}; +use crate::attributes::splat::*; use crate::attributes::stability::*; use crate::attributes::test_attrs::*; use crate::attributes::traits::*; @@ -323,6 +324,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, // tidy-alphabetical-end diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 6f55becdc9a7a..a0adfd8814b72 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -208,6 +208,12 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ // - https://github.com/rust-lang/rust/issues/130494 sym::pin_v2, + // The `#[splat]` attribute is part of the `splat` experiment + // that improves the ergonomics of function overloading, tracked in: + // + // - https://github.com/rust-lang/rust/issues/153629 + sym::splat, + // ========================================================================== // Internal attributes: Stability, deprecation, and unsafe: // ========================================================================== diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index a8f0b4c38e828..e1f63b7c3dce8 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -721,6 +721,9 @@ declare_features! ( (unstable, sparc_target_feature, "1.84.0", Some(132783)), /// Allows specialization of implementations (RFC 1210). (incomplete, specialization, "1.7.0", Some(31844)), + /// Experimental "splatting" of function call arguments at the call site. + /// e.g. `foo(a, b, c)` calls `#[splat] fn foo((a: A, b: B, c: C))`. + (incomplete, splat, "CURRENT_RUSTC_VERSION", Some(153629)), /// Allows using `#[rustc_align_static(...)]` on static items. (unstable, static_align, "1.91.0", Some(146177)), /// Allows attributes on expressions and non-item statements. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index f2b4041498c02..bdb181087122d 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1589,6 +1589,9 @@ pub enum AttributeKind { reason: Option, }, + /// Represents `#[splat]` + Splat(Span), + /// Represents `#[stable]`, `#[unstable]` and `#[rustc_allowed_through_unstable_modules]`. Stability { stability: Stability, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 80eeb3400ec88..da03f039b8617 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -192,6 +192,7 @@ impl AttributeKind { RustcUnsafeSpecializationMarker => No, Sanitize { .. } => No, ShouldPanic { .. } => No, + Splat(..) => Yes, Stability { .. } => Yes, TargetFeature { .. } => No, TestRunner(..) => Yes, diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index 770ccc7dff51f..4dba70cbd56b0 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -14,7 +14,6 @@ use rustc_middle::ty::{ }; use rustc_span::{ErrorGuaranteed, Ident, Span, kw}; use rustc_trait_selection::traits; -use smallvec::SmallVec; use tracing::{debug, instrument}; use crate::errors; @@ -85,19 +84,6 @@ fn search_bounds_for<'tcx>( } } -fn collect_relaxed_bounds<'tcx>( - hir_bounds: &'tcx [hir::GenericBound<'tcx>], - context: ImpliedBoundsContext<'tcx>, -) -> SmallVec<[&'tcx PolyTraitRef<'tcx>; 1]> { - let mut relaxed_bounds: SmallVec<[_; 1]> = SmallVec::new(); - search_bounds_for(hir_bounds, context, |ptr| { - if matches!(ptr.modifiers.polarity, hir::BoundPolarity::Maybe(_)) { - relaxed_bounds.push(ptr); - } - }); - relaxed_bounds -} - fn collect_bounds<'a, 'tcx>( hir_bounds: &'a [hir::GenericBound<'tcx>], context: ImpliedBoundsContext<'tcx>, @@ -192,18 +178,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } } ImpliedBoundsContext::TyParam(..) | ImpliedBoundsContext::AssociatedTypeOrImplTrait => { - // Report invalid relaxed bounds. - // FIXME: Since we only call this validation function here in this function, we only - // fully validate relaxed bounds in contexts where we perform - // "sized elaboration". In most cases that doesn't matter because we *usually* - // reject such relaxed bounds outright during AST lowering. - // However, this can easily get out of sync! Ideally, we would perform this step - // where we are guaranteed to catch *all* bounds like in - // `Self::lower_poly_trait_ref`. List of concrete issues: - // FIXME(more_maybe_bounds): We don't call this for trait object tys, supertrait - // bounds, trait alias bounds, assoc type bounds (ATB)! - let bounds = collect_relaxed_bounds(hir_bounds, context); - self.reject_duplicate_relaxed_bounds(bounds); } } @@ -287,28 +261,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { !find_attr!(self.tcx(), crate, RustcNoImplicitBounds) && !collected.any() } - fn reject_duplicate_relaxed_bounds(&self, relaxed_bounds: SmallVec<[&PolyTraitRef<'_>; 1]>) { - let tcx = self.tcx(); - - let mut grouped_bounds = FxIndexMap::<_, Vec<_>>::default(); - - for bound in &relaxed_bounds { - if let Res::Def(DefKind::Trait, trait_def_id) = bound.trait_ref.path.res { - grouped_bounds.entry(trait_def_id).or_default().push(bound.span); - } - } - - for (trait_def_id, spans) in grouped_bounds { - if spans.len() > 1 { - let name = tcx.item_name(trait_def_id); - self.dcx() - .struct_span_err(spans, format!("duplicate relaxed `{name}` bounds")) - .with_code(E0203) - .emit(); - } - } - } - pub(crate) fn require_bound_to_relax_default_trait( &self, trait_ref: hir::TraitRef<'_>, diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index a1020962faedf..b086d7e1fedb9 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -147,7 +147,16 @@ fn maybe_normalize_erasing_regions<'tcx>( cx: &LateContext<'tcx>, value: Unnormalized<'tcx, Ty<'tcx>>, ) -> Ty<'tcx> { - cx.tcx.try_normalize_erasing_regions(cx.typing_env(), value).unwrap_or(value.skip_norm_wip()) + // Use `TypingMode::Borrowck` so the new solver doesn't reveal opaque types since we're now + // past hir typeck. If we were to attempt to reveal more opaque types, dropping the + // `InferCtxt` would ICE (see #156352). + let typing_env = if let Some(body_id) = cx.enclosing_body { + let body_def_id = cx.tcx.hir_enclosing_body_owner(body_id.hir_id); + ty::TypingEnv::new(cx.param_env, ty::TypingMode::borrowck(cx.tcx, body_def_id)) + } else { + cx.typing_env() + }; + cx.tcx.try_normalize_erasing_regions(typing_env, value).unwrap_or(value.skip_norm_wip()) } /// Check a variant of a non-exhaustive enum for improper ctypes @@ -468,12 +477,15 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { base_ty: Unnormalized<'tcx, Ty<'tcx>>, base_fn_mode: CItemKind, ) -> Self { - ImproperCTypesVisitor { - cx, - base_ty: maybe_normalize_erasing_regions(cx, base_ty), - base_fn_mode, - cache: FxHashSet::default(), - } + // Skip normalization for opaques: even in `TypingMode::Borrowck` the body's own + // defining opaques still get revealed, leaving entries in `OpaqueTypeStorage` that + // ICE on `InferCtxt` drop (issue #156352). + let base_ty = if base_ty.skip_norm_wip().has_opaque_types() { + base_ty.skip_norm_wip() + } else { + maybe_normalize_erasing_regions(cx, base_ty) + }; + ImproperCTypesVisitor { cx, base_ty, base_fn_mode, cache: FxHashSet::default() } } /// Checks if the given indirection (box,ref,pointer) is "ffi-safe". @@ -894,6 +906,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { help: None, }, + // Safety net for when normalization reveals a body's own defining opaque + // (e.g. `async extern fn`'s `impl Future` → `Coroutine`); the nicer + // "opaque types have no C equivalent" message comes from `visit_for_opaque_ty` + // in `check_type` before normalization (issue #156352). + ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Coroutine(..) + | ty::CoroutineWitness(..) => FfiUnsafe { + ty, + reason: msg!("closures and coroutines are not FFI-safe"), + help: None, + }, + ty::Param(..) | ty::Alias(ty::AliasTy { kind: ty::Projection { .. } | ty::Inherent { .. } | ty::Free { .. }, @@ -902,10 +927,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { | ty::Infer(..) | ty::Bound(..) | ty::Error(_) - | ty::Closure(..) - | ty::CoroutineClosure(..) - | ty::Coroutine(..) - | ty::CoroutineWitness(..) | ty::Placeholder(..) | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty), } @@ -941,6 +962,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { state: VisitorState, ty: Unnormalized<'tcx, Ty<'tcx>>, ) -> FfiResult<'tcx> { + // Catch opaques before normalization so the new solver doesn't reveal them + // (e.g. `async extern fn` return → `Coroutine`) and we get the nicer + // "opaque types have no C equivalent" message. + if let Some(res) = self.visit_for_opaque_ty(ty.skip_norm_wip()) { + return res; + } let ty = maybe_normalize_erasing_regions(self.cx, ty); if let Some(res) = self.visit_for_opaque_ty(ty) { return res; diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 5d12e762fdc0a..7df8c11ebb996 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -402,6 +402,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::RustcUnsafeSpecializationMarker => (), AttributeKind::Sanitize { .. } => {} AttributeKind::ShouldPanic { .. } => (), + AttributeKind::Splat(..) => (), AttributeKind::Stability { .. } => (), AttributeKind::TestRunner(..) => (), AttributeKind::ThreadLocal => (), diff --git a/compiler/rustc_public/src/compiler_interface.rs b/compiler/rustc_public/src/compiler_interface.rs index 7953af37c1833..5512371b68b3a 100644 --- a/compiler/rustc_public/src/compiler_interface.rs +++ b/compiler/rustc_public/src/compiler_interface.rs @@ -136,6 +136,13 @@ impl<'tcx> CompilerInterface<'tcx> { }) } + pub(crate) fn crate_adts(&self, crate_num: CrateNum) -> Vec { + self.with_cx(|tables, cx| { + let krate = crate_num.internal(tables, cx.tcx); + cx.crate_adts(krate).iter().map(|did| tables.adt_def(*did)).collect() + }) + } + /// Retrieve all static items defined in this crate. pub(crate) fn crate_statics(&self, crate_num: CrateNum) -> Vec { self.with_cx(|tables, cx| { diff --git a/compiler/rustc_public/src/crate_def.rs b/compiler/rustc_public/src/crate_def.rs index 7773abdcb4d28..04ab2a1908e53 100644 --- a/compiler/rustc_public/src/crate_def.rs +++ b/compiler/rustc_public/src/crate_def.rs @@ -163,6 +163,32 @@ macro_rules! crate_def_with_ty { } } + impl CrateDefType for $name {} + }; + ( $(#[$attr:meta])* + $vis:vis $name:ident { + $( + $(#[$f_attr:meta])* + $f_vis:vis $f_name:ident: $f_ty:ty, + )* + } + ) => { + $(#[$attr])* + #[derive(Clone, PartialEq, Eq, Debug)] + $vis struct $name { + pub(crate) def: DefId, + $( + $(#[$f_attr])* + $f_vis $f_name: $f_ty, + )* + } + + impl CrateDef for $name { + fn def_id(&self) -> DefId { + self.def + } + } + impl CrateDefType for $name {} }; } diff --git a/compiler/rustc_public/src/lib.rs b/compiler/rustc_public/src/lib.rs index e1e02e282bf2e..be4886b8cceeb 100644 --- a/compiler/rustc_public/src/lib.rs +++ b/compiler/rustc_public/src/lib.rs @@ -35,7 +35,7 @@ pub use crate::error::*; use crate::mir::mono::StaticDef; use crate::mir::{Body, Mutability}; use crate::ty::{ - AssocItem, FnDef, ForeignModuleDef, ImplDef, ProvenanceMap, Span, TraitDef, Ty, + AdtDef, AssocItem, FnDef, ForeignModuleDef, ImplDef, ProvenanceMap, Span, TraitDef, Ty, serialize_index_impl, }; use crate::unstable::Stable; @@ -114,6 +114,11 @@ impl Crate { pub fn statics(&self) -> Vec { with(|cx| cx.crate_statics(self.id)) } + + /// Return a list of ADTs defined in this crate independent on their visibility. + pub fn adts(&self) -> Vec { + with(|cx| cx.crate_adts(self.id)) + } } #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize)] diff --git a/compiler/rustc_public/src/ty.rs b/compiler/rustc_public/src/ty.rs index 642e616dc1adb..0b8e84213b5f7 100644 --- a/compiler/rustc_public/src/ty.rs +++ b/compiler/rustc_public/src/ty.rs @@ -897,26 +897,11 @@ impl VariantDef { } } -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct FieldDef { - /// The field definition. - pub(crate) def: DefId, - - /// The field name. - pub name: Symbol, -} - -impl FieldDef { - /// Retrieve the type of this field instantiating and normalizing it with the given arguments. - /// - /// This will assume the type can be instantiated with these arguments. - pub fn ty_with_args(&self, args: &GenericArgs) -> Ty { - with(|cx| cx.def_ty_with_args(self.def, args)) - } - - /// Retrieve the type of this field. - pub fn ty(&self) -> Ty { - with(|cx| cx.def_ty(self.def)) +crate_def_with_ty! { + #[derive(Serialize)] + pub FieldDef { + /// The field name. + pub name: Symbol, } } diff --git a/compiler/rustc_public_bridge/src/context/impls.rs b/compiler/rustc_public_bridge/src/context/impls.rs index 490dcbef8d79c..c309df59e9eca 100644 --- a/compiler/rustc_public_bridge/src/context/impls.rs +++ b/compiler/rustc_public_bridge/src/context/impls.rs @@ -110,6 +110,11 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { matches!(self.tcx.def_kind(def_id), DefKind::Static { .. }).then(|| def_id) } + fn filter_adt_def(&self, def_id: DefId) -> Option { + matches!(self.tcx.def_kind(def_id), DefKind::Struct | DefKind::Enum | DefKind::Union) + .then(|| def_id) + } + pub fn target_endian(&self) -> Endian { self.tcx.data_layout.endian } @@ -152,6 +157,11 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { filter_def_ids(self.tcx, crate_num, |def_id| self.filter_static_def(def_id)) } + /// Retrieve all ADTs defined in this crate. + pub fn crate_adts(&self, crate_num: CrateNum) -> Vec { + filter_def_ids(self.tcx, crate_num, |def_id| self.filter_adt_def(def_id)) + } + pub fn foreign_module(&self, mod_def: DefId) -> &ForeignModule { self.tcx.foreign_modules(mod_def.krate).get(&mod_def).unwrap() } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 75e2666db46cd..b846f2be1845f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1976,6 +1976,7 @@ symbols! { specialization, speed, spirv, + splat, spotlight, sqrtf16, sqrtf32, diff --git a/compiler/rustc_ty_utils/src/needs_drop.rs b/compiler/rustc_ty_utils/src/needs_drop.rs index 8299284b58004..e2529e7a77226 100644 --- a/compiler/rustc_ty_utils/src/needs_drop.rs +++ b/compiler/rustc_ty_utils/src/needs_drop.rs @@ -23,10 +23,16 @@ fn needs_drop_raw<'tcx>( // needs drop. let adt_has_dtor = |adt_def: ty::AdtDef<'tcx>| adt_def.destructor(tcx).map(|_| DtorType::Significant); - let res = drop_tys_helper(tcx, query.value, query.typing_env, adt_has_dtor, false, false) - .filter(filter_array_elements(tcx, query.typing_env)) - .next() - .is_some(); + let res = drop_tys_helper( + tcx, + query.value, + query.typing_env, + adt_has_dtor, + DropTysOptions::default(), + ) + .filter(filter_array_elements(tcx, query.typing_env)) + .next() + .is_some(); debug!("needs_drop_raw({:?}) = {:?}", query, res); res @@ -41,10 +47,16 @@ fn needs_async_drop_raw<'tcx>( // it needs async drop. let adt_has_async_dtor = |adt_def: ty::AdtDef<'tcx>| adt_def.async_destructor(tcx).map(|_| DtorType::Significant); - let res = drop_tys_helper(tcx, query.value, query.typing_env, adt_has_async_dtor, false, false) - .filter(filter_array_elements_async(tcx, query.typing_env)) - .next() - .is_some(); + let res = drop_tys_helper( + tcx, + query.value, + query.typing_env, + adt_has_async_dtor, + DropTysOptions::default().recurse_into_box_for_async_drop(), + ) + .filter(filter_array_elements_async(tcx, query.typing_env)) + .next() + .is_some(); debug!("needs_async_drop_raw({:?}) = {:?}", query, res); res @@ -88,8 +100,7 @@ fn has_significant_drop_raw<'tcx>( query.value, query.typing_env, adt_consider_insignificant_dtor(tcx), - true, - false, + DropTysOptions::default().only_significant(), ) .filter(filter_array_elements(tcx, query.typing_env)) .next() @@ -320,6 +331,30 @@ enum DtorType { Significant, } +#[derive(Copy, Clone, Default)] +struct DropTysOptions { + only_significant: bool, + exhaustive: bool, + async_drop_recurses_into_box: bool, +} + +impl DropTysOptions { + fn only_significant(mut self) -> Self { + self.only_significant = true; + self + } + + fn exhaustive(mut self) -> Self { + self.exhaustive = true; + self + } + + fn recurse_into_box_for_async_drop(mut self) -> Self { + self.async_drop_recurses_into_box = true; + self + } +} + // This is a helper function for `adt_drop_tys` and `adt_significant_drop_tys`. // Depending on the implantation of `adt_has_dtor`, it is used to check if the // ADT has a destructor or if the ADT only has a significant destructor. For @@ -329,8 +364,7 @@ fn drop_tys_helper<'tcx>( ty: Ty<'tcx>, typing_env: ty::TypingEnv<'tcx>, adt_has_dtor: impl Fn(ty::AdtDef<'tcx>) -> Option, - only_significant: bool, - exhaustive: bool, + options: DropTysOptions, ) -> impl Iterator>> { fn with_query_cache<'tcx>( tcx: TyCtxt<'tcx>, @@ -353,6 +387,24 @@ fn drop_tys_helper<'tcx>( if adt_def.is_manually_drop() { debug!("drop_tys_helper: `{:?}` is manually drop", adt_def); Ok(Vec::new()) + } else if options.async_drop_recurses_into_box && adt_def.is_box() { + let box_components = match args.as_slice() { + [boxed_ty, allocator_ty] => { + let boxed_ty = boxed_ty.expect_ty(); + let allocator_ty = allocator_ty.expect_ty(); + match boxed_ty.kind() { + // FIXME(async_drop): boxed dyn pointees are deliberately skipped here + // because async drop glue does not yet dispatch through dyn metadata. + // Once that is supported, this should include the boxed pointee too. + ty::Dynamic(..) | ty::Error(_) => vec![allocator_ty], + _ => vec![boxed_ty, allocator_ty], + } + } + _ => { + bug!("drop_tys_helper: `Box` has unexpected generic args: {args:?}"); + } + }; + Ok(box_components) } else if let Some(dtor_info) = adt_has_dtor(adt_def) { match dtor_info { DtorType::Significant => { @@ -380,7 +432,7 @@ fn drop_tys_helper<'tcx>( ); r }); - if only_significant { + if options.only_significant { // We can't recurse through the query system here because we might induce a cycle Ok(field_tys.collect()) } else { @@ -394,7 +446,7 @@ fn drop_tys_helper<'tcx>( .map(|v| v.into_iter()) }; - NeedsDropTypes::new(tcx, typing_env, ty, exhaustive, adt_components) + NeedsDropTypes::new(tcx, typing_env, ty, options.exhaustive, adt_components) } fn adt_consider_insignificant_dtor<'tcx>( @@ -433,8 +485,7 @@ fn adt_drop_tys<'tcx>( tcx.type_of(def_id).instantiate_identity().skip_norm_wip(), ty::TypingEnv::non_body_analysis(tcx, def_id), adt_has_dtor, - false, - false, + DropTysOptions::default(), ) .collect::, _>>() .map(|components| tcx.mk_type_list(&components)) @@ -453,8 +504,7 @@ fn adt_async_drop_tys<'tcx>( tcx.type_of(def_id).instantiate_identity().skip_norm_wip(), ty::TypingEnv::non_body_analysis(tcx, def_id), adt_has_dtor, - false, - false, + DropTysOptions::default().recurse_into_box_for_async_drop(), ) .collect::, _>>() .map(|components| tcx.mk_type_list(&components)) @@ -472,8 +522,7 @@ fn adt_significant_drop_tys( tcx.type_of(def_id).instantiate_identity().skip_norm_wip(), // identical to `tcx.make_adt(def, identity_args)` ty::TypingEnv::non_body_analysis(tcx, def_id), adt_consider_insignificant_dtor(tcx), - true, - false, + DropTysOptions::default().only_significant(), ) .collect::, _>>() .map(|components| tcx.mk_type_list(&components)) @@ -490,8 +539,7 @@ fn list_significant_drop_tys<'tcx>( key.value, key.typing_env, adt_consider_insignificant_dtor(tcx), - true, - true, + DropTysOptions::default().only_significant().exhaustive(), ) .filter_map(|res| res.ok()) .collect::>(), diff --git a/src/bootstrap/bootstrap.py b/src/bootstrap/bootstrap.py index 563792868f5d2..fcec091fed1c7 100644 --- a/src/bootstrap/bootstrap.py +++ b/src/bootstrap/bootstrap.py @@ -926,6 +926,21 @@ def get_toml(self, key, section=None): >>> rb.get_toml('key', 'c') is None True + A dotted key names a table relative to its enclosing section, so the + full table path must match for the key to be found: + + >>> rb.config_toml = 'build.cargo = "/path/to/cargo"' + >>> rb.get_toml('cargo', 'build') + '/path/to/cargo' + >>> rb.get_toml('cargo', 'other') is None + True + + A dotted key inside a section composes with that section's name: + + >>> rb.config_toml = '[target]\\nx86_64-unknown-linux-gnu.cc = "gcc"' + >>> rb.get_toml('cc', 'target.x86_64-unknown-linux-gnu') + 'gcc' + >>> rb.config_toml = 'key1 = true' >>> rb.get_toml("key1") 'true' @@ -940,10 +955,24 @@ def get_toml_static(config_toml, key, section=None): if section_match is not None: cur_section = section_match.group(1) - match = re.match(r"^{}\s*=(.*)$".format(key), line) + # Match the key, optionally preceded by a dotted-table prefix (the + # `build.` in `build.cargo`), which names a table relative to the + # current `[section]` and is appended to `cur_section`. This is a + # subset parser, not full TOML: quoted names (e.g. the `'a.b'` that + # configure.py emits for dotted targets) are not matched here. + match = re.match( + r"^\s*(?:([\w.-]+)\.)?{}\s*=(.*)$".format(re.escape(key)), line + ) if match is not None: - value = match.group(1) - if section is None or section == cur_section: + prefix = match.group(1) + if prefix is None: + line_section = cur_section + elif cur_section is None: + line_section = prefix + else: + line_section = "{}.{}".format(cur_section, prefix) + value = match.group(2) + if section is None or section == line_section: return RustBuild.get_string(value) or value.strip() return None @@ -959,7 +988,10 @@ def program_config(self, program): """Return config path for the given program at the given stage >>> rb = RustBuild() - >>> rb.config_toml = 'rustc = "rustc"\\n' + >>> rb.config_toml = 'build.rustc = "rustc"\\n' + >>> rb.program_config('rustc') + 'rustc' + >>> rb.config_toml = '[build]\\nrustc = "rustc"\\n' >>> rb.program_config('rustc') 'rustc' >>> rb.config_toml = '' @@ -968,7 +1000,7 @@ def program_config(self, program): ... "bin", "cargo") True """ - config = self.get_toml(program) + config = self.get_toml(program, "build") if config: return os.path.expanduser(config) return os.path.join(self.bin_root(), "bin", "{}{}".format(program, EXE_SUFFIX)) diff --git a/src/bootstrap/bootstrap_test.py b/src/bootstrap/bootstrap_test.py index b9cd0d7aa34a7..163191aa01f32 100644 --- a/src/bootstrap/bootstrap_test.py +++ b/src/bootstrap/bootstrap_test.py @@ -190,6 +190,73 @@ def test_set_codegen_backends(self): self.assertNotEqual(build.config_toml.find("codegen-backends = ['llvm']"), -1) +class GetTomlDottedKeys(unittest.TestCase): + """Test that get_toml understands the dotted-table key syntax that a + hand-written bootstrap.toml may use (e.g. `build.cargo = "..."`), and that + it checks the table a key belongs to. See issue #156948.""" + + def parse(self, config_toml): + return bootstrap.RustBuild(config_toml=config_toml, args=bootstrap.FakeArgs()) + + def test_dotted_key(self): + build = self.parse('build.cargo = "/path/to/cargo"') + self.assertEqual(build.get_toml("cargo", "build"), "/path/to/cargo") + + def test_dotted_key_matches_section_form(self): + dotted = self.parse('build.cargo = "/path/to/cargo"') + section = self.parse('[build]\ncargo = "/path/to/cargo"') + self.assertEqual( + dotted.get_toml("cargo", "build"), section.get_toml("cargo", "build") + ) + + def test_dotted_key_wrong_section(self): + # A `cargo` key in some other table must not be picked up for `build`. + build = self.parse('[foo]\ncargo = "false"') + self.assertIsNone(build.get_toml("cargo", "build")) + + def test_program_config_requires_build_table(self): + # A cargo/rustc key outside [build] must be ignored; program_config + # falls back to the stage0 default path rather than the misplaced value. + wrong = self.parse('[foo]\ncargo = "/wrong/cargo"') + cargo_default = os.path.join( + wrong.bin_root(), "bin", "cargo" + bootstrap.EXE_SUFFIX + ) + self.assertEqual(wrong.cargo(), cargo_default) + + wrong_rustc = self.parse('[foo]\nrustc = "/wrong/rustc"') + rustc_default = os.path.join( + wrong_rustc.bin_root(), "bin", "rustc" + bootstrap.EXE_SUFFIX + ) + self.assertEqual(wrong_rustc.rustc(), rustc_default) + + # A dotted key in the build table is honored. + right = self.parse('build.cargo = "/path/to/cargo"') + self.assertEqual(right.cargo(), "/path/to/cargo") + + def test_dotted_target_key(self): + build = self.parse('target.x86_64-unknown-linux-gnu.cc = "gcc"') + self.assertEqual(build.get_toml("cc", "target.x86_64-unknown-linux-gnu"), "gcc") + + def test_dotted_key_inside_section(self): + # A dotted key nested under a `[section]` header composes with that + # section's name; it is equivalent to the fully top-level dotted form. + nested = self.parse('[target]\nx86_64-unknown-linux-gnu.cc = "gcc"') + self.assertEqual( + nested.get_toml("cc", "target.x86_64-unknown-linux-gnu"), "gcc" + ) + top_level = self.parse('target.x86_64-unknown-linux-gnu.cc = "gcc"') + self.assertEqual( + nested.get_toml("cc", "target.x86_64-unknown-linux-gnu"), + top_level.get_toml("cc", "target.x86_64-unknown-linux-gnu"), + ) + + def test_dotted_prefix_does_not_alias_other_section(self): + # `build.cargo` inside `[foo]` is `foo.build.cargo`, not the top-level + # `[build]` table, so it must not be returned for section "build". + build = self.parse('[foo]\nbuild.cargo = "false"') + self.assertIsNone(build.get_toml("cargo", "build")) + + class BuildBootstrap(unittest.TestCase): """Test that we generate the appropriate arguments when building bootstrap""" diff --git a/src/doc/rustdoc/src/write-documentation/documentation-tests.md b/src/doc/rustdoc/src/write-documentation/documentation-tests.md index 4084c1d962a22..84d98277bee2d 100644 --- a/src/doc/rustdoc/src/write-documentation/documentation-tests.md +++ b/src/doc/rustdoc/src/write-documentation/documentation-tests.md @@ -1,7 +1,7 @@ # Documentation tests -`rustdoc` supports executing your documentation examples as tests. This makes sure -that examples within your documentation are up to date and working. +`rustdoc` supports executing your documentation examples as tests. +This makes sure that examples within your documentation are up to date and working. The basic idea is this: @@ -14,11 +14,13 @@ The basic idea is this: # fn f() {} ``` -The triple backticks start and end code blocks. If this were in a file named `foo.rs`, -running `rustdoc --test foo.rs` will extract this example, and then run it as a test. +Here, the triple backticks start and end the code block. +If this were in a file named `foo.rs`, running `rustdoc --test foo.rs` will extract this example, +and then run it as a test. -Please note that by default, if no language is set for the block code, rustdoc -assumes it is Rust code. So the following: +Please note that by default, +if no language is set for the block code, rustdoc assumes it is Rust code. +So the following: ``````markdown ```rust @@ -321,21 +323,43 @@ we can add the `#[macro_use]` attribute. Second, we’ll need to add our own ## Attributes -Code blocks can be annotated with attributes that help `rustdoc` do the right -thing when testing your code: +Code blocks can be annotated with attributes that tell `rustdoc` how to build and interpret the test. +They follow the [code fence] in the opening line. +As such, they share the place with language strings like `rust` or `text`. +Multiple attributes can be provided by separating them with commas, spaces or tabs. +You can also write comments which are enclosed in parentheses `(…)`. -The `ignore` attribute tells Rust to ignore your code. This is almost never -what you want as it's the most generic. Instead, consider annotating it -with `text` if it's not code or using `#`s to get a working example that -only shows the part you care about. +As alluded to in the introduction at the very top, +unless you specify `rust` or something that isn't an attribute (except for `custom`), +the code block is assumed to be Rust source code (and is syntax highlighted as such). + +You can of course add `rust` explicitly (like `rust,ignore`) if the Markdown is also consumed by +other tools (e.g., if it's contained inside of a `README.md` that's included via `include_str`). + +### `ignore` + +The `ignore` attribute tells `rustdoc` to ignore your code. This is useful if you would like to +have Rust syntax highlighting but the snippet is incomplete or pseudocode. +It is customary to add the reason why it should be ignored in a `(…)` comment. ```rust /// ```ignore /// fn foo() { /// ``` +/// +/// ```ignore (needs extra dependency) +/// use dependency::functionality; +/// functionality(); +/// ``` # fn foo() {} ``` +Do note that this is almost never what you want as it's the most generic. +Instead, consider annotating it with `text` if it's not code or +using `#`s to get a working example that only shows the part you care about. + +### `should_panic` + `should_panic` tells `rustdoc` that the code should compile correctly but panic during execution. If the code doesn't panic, the test will fail. @@ -346,6 +370,8 @@ panic during execution. If the code doesn't panic, the test will fail. # fn foo() {} ``` +### `no_run` + The `no_run` attribute will compile your code but not run it. This is important for examples such as "Here's how to retrieve a web page," which you would want to ensure compiles, but might be run in a test @@ -361,10 +387,10 @@ used to demonstrate code snippets that can cause Undefined Behavior. # fn foo() {} ``` +### `compile_fail` + `compile_fail` tells `rustdoc` that the compilation should fail. If it -compiles, then the test will fail. However, please note that code failing -with the current Rust release may work in a future release, as new features -are added. +compiles, then the test will fail. ```rust /// ```compile_fail @@ -374,6 +400,13 @@ are added. # fn foo() {} ``` +
+However, please note that code failing with the current Rust release may work in a future release, +as new features are added! +
+ +### `edition…` + `edition2015`, `edition2018`, `edition2021`, and `edition2024` tell `rustdoc` that the code sample should be compiled using the respective edition of Rust. @@ -390,6 +423,8 @@ that the code sample should be compiled using the respective edition of Rust. # fn foo() {} ``` +### `standalone_crate` + Starting in the 2024 edition[^edition-note], compatible doctests are merged as one before being run. We combine doctests for performance reasons: the slowest part of doctests is to compile them. Merging all of them into one file and compiling this new file, then running the doctests is much @@ -441,7 +476,7 @@ should not be merged with the others. So the previous code should use it: In this case, it means that the line information will not change if you add/remove other doctests. -### Ignoring targets +### `ignore-…`: Ignoring targets Attributes starting with `ignore-` can be used to ignore doctests for specific targets. For example, `ignore-x86_64` will avoid building doctests when the @@ -478,7 +513,7 @@ struct Foo; In older versions, this will be ignored on all targets, but starting with version 1.88.0, `ignore-x86_64` will override `ignore`. -### Custom CSS classes for code blocks +### `{…}` & `custom`: Custom CSS classes for code blocks ```rust /// ```custom,{class=language-c} @@ -504,8 +539,9 @@ To be noted that you can replace `class=` with `.` to achieve the same result: pub struct Bar; ``` -To be noted, `rust` and `.rust`/`class=rust` have different effects: `rust` indicates that this is -a Rust code block whereas the two others add a "rust" CSS class on the code block. +To be noted, `rust` and `{.rust}` / `{class=rust}` have different effects: +`rust` indicates that this is a Rust code block whereas +the two others add a "rust" CSS class on the code block in the generated HTML. You can also use double quotes: @@ -516,24 +552,42 @@ You can also use double quotes: pub struct Bar; ``` +### `test_harness` + +With `test_harness` applied, `rustdoc` will run any contained *test functions* +instead of the (potentially implicit) `main` function. + +```rust +//! ```test_harness +//! #[test] +//! #[should_panic] +//! fn abc() { assert!(false); } +//! +//! #[test] +//! fn xyz() { assert!(true); } +//! ``` +``` + +You can read more about *test functions* in [the Book][testing-book] or in [the Rust Reference][testing-ref]. + +[testing-book]: ../../book/ch11-01-writing-tests.html +[testing-ref]: ../../reference/attributes/testing.html + ## Syntax reference -The *exact* syntax for code blocks, including the edge cases, can be found -in the [Fenced Code Blocks](https://spec.commonmark.org/0.29/#fenced-code-blocks) -section of the CommonMark specification. +The *exact* syntax for code blocks, including the edge cases, +can be found in the [Fenced Code Blocks] section of the CommonMark specification. -Rustdoc also accepts *indented* code blocks as an alternative to fenced -code blocks: instead of surrounding your code with three backticks, you -can indent each line by four or more spaces. +Rustdoc also accepts *indented* code blocks as an alternative to fenced code blocks: +Instead of surrounding your code with a [code fence] (e.g., three backticks), +you can indent each line by four or more spaces. ``````markdown let foo = "foo"; assert_eq!(foo, "foo"); `````` -These, too, are documented in the CommonMark specification, in the -[Indented Code Blocks](https://spec.commonmark.org/0.29/#indented-code-blocks) -section. +These, too, are documented in the CommonMark specification, in the [Indented Code Blocks] section. However, it's preferable to use fenced code blocks over indented code blocks. Not only are fenced code blocks considered more idiomatic for Rust code, @@ -595,3 +649,8 @@ operations within documentation test examples, such as `std::fs::read_to_string` The `--test-run-directory` flag allows controlling the run directory separately from the compilation directory. This is particularly useful in workspaces, where compiler invocations and thus diagnostics should be relative to the workspace directory, but documentation test examples should run relative to the crate directory. + + +[code fence]: https://spec.commonmark.org/0.29/#code-fence +[Fenced Code Blocks]: https://spec.commonmark.org/0.29/#fenced-code-blocks +[Indented Code Blocks]: https://spec.commonmark.org/0.29/#indented-code-blocks diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 6679d162520df..b05a831d119cb 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -846,11 +846,12 @@ pub(crate) enum Ignore { Some(Vec), } -/// This is the parser for fenced codeblocks attributes. It implements the following eBNF: +/// This is the parser for fenced codeblocks attributes. /// -/// ```eBNF -/// lang-string = *(token-list / delimited-attribute-list / comment) +/// It implements the following grammar as expressed in ABNF: /// +/// ```ABNF +/// lang-string = *(token-list / delimited-attribute-list / comment) /// bareword = LEADINGCHAR *(CHAR) /// bareword-without-leading-char = CHAR *(CHAR) /// quoted-string = QUOTE *(NONQUOTE) QUOTE @@ -861,7 +862,7 @@ pub(crate) enum Ignore { /// attribute-list = [sep] attribute *(sep attribute) [sep] /// delimited-attribute-list = OPEN-CURLY-BRACKET attribute-list CLOSE-CURLY-BRACKET /// token-list = [sep] token *(sep token) [sep] -/// comment = OPEN_PAREN *(all characters) CLOSE_PAREN +/// comment = OPEN_PAREN * CLOSE_PAREN /// /// OPEN_PAREN = "(" /// CLOSE_PARENT = ")" diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 7469b08b4b424..7ade72429cb4d 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -783,9 +783,6 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt: } })?; - // Trait documentation - write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?; - if let rustc_middle::ty::trait_def::ImplRestrictionKind::Restricted(def_id, _) = impl_restriction { @@ -793,7 +790,7 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt: let v2; write!( w, - "
This trait cannot be implemented outside {}.
", + "
This trait cannot be implemented outside {}.
", if cx.cache().document_private { v1 = rustc_middle::ty::print::with_resolve_crate_name!(tcx.def_path_str(def_id)); @@ -805,6 +802,9 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt: )?; } + // Trait documentation + write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?; + fn trait_item(cx: &Context<'_>, m: &clean::Item, t: &clean::Item) -> impl fmt::Display { fmt::from_fn(|w| { let name = m.name.unwrap(); diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index da34deeefb197..1090d4e2feb01 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1179,6 +1179,10 @@ div.where { margin-left: calc(var(--docblock-indent) + var(--impl-items-indent)); } +.impl-restriction { + margin-bottom: 0.75em; +} + #synthetic-implementors-list:not(.loaded), #implementors-list:not(.loaded) { /* To prevent layout shift when loading the page with extra implementors being loaded from JS, we hide the list until it's complete. */ diff --git a/tests/debuginfo/pretty-std.rs b/tests/debuginfo/pretty-std.rs index bf3102993d896..3ecaf02ab7d26 100644 --- a/tests/debuginfo/pretty-std.rs +++ b/tests/debuginfo/pretty-std.rs @@ -109,12 +109,15 @@ //@ cdb-check: [10] : 103 'g' [Type: char] //@ cdb-check: [11] : 33 '!' [Type: char] -//@ cdb-command: dx os_string -// NOTE: OSString is WTF-8 encoded which Windows debuggers don't understand. Verify the UTF-8 -// portion displays correctly. -//@ cdb-check:os_string : "IAMA OS string [...]" [Type: std::ffi::os_str::OsString] -//@ cdb-check: [] [Type: std::ffi::os_str::OsString] -//@ cdb-check: [chars] : "IAMA OS string [...]" +// FIXME(#88840, #148743, #88796): since approx. Windows SDK 20348, the corresponding cdb (and/or +// its underlying WinDbg engine) changed or regressed the `OsStr`/`OsString` visualization, and no +// longer renders the emoji. Since approx. 26100, the output formatting of the string containing +// UTF-8 (i.e. the multi-byte emoji grapheme) seems to have further regressed (e.g. the end +// quotation mark is no longer shown and command output becomes garbled). +//(DISABLED) @ cdb-command: dx os_string +//(DISABLED) @ cdb-check:os_string : "IAMA OS string 😃" [Type: std::ffi::os_str::OsString] +//(DISABLED) @ cdb-check: [] [Type: std::ffi::os_str::OsString] +//(DISABLED) @ cdb-check: [chars] : "IAMA OS string 😃" //@ cdb-command: dx some //@ cdb-check:some : Some [Type: enum2$ >] diff --git a/tests/run-make/doctests-test_harness/doctests.rs b/tests/run-make/doctests-test_harness/doctests.rs new file mode 100644 index 0000000000000..9e2a6e429855f --- /dev/null +++ b/tests/run-make/doctests-test_harness/doctests.rs @@ -0,0 +1,26 @@ +// Test that we can successfully run two separate test suites. +// Check that we run all four tests even though `ill` and `bad` both fail. + +//! ```test_harness +//! #[test] +//! fn well() { +//! assert!(true); +//! } +//! +//! #[test] +//! fn ill() { +//! assert!(false); +//! } +//! ``` + +//! ```test_harness +//! #[test] +//! fn bad() { +//! assert!(false); +//! } +//! +//! #[test] +//! fn good() { +//! assert!(true); +//! } +//! ``` diff --git a/tests/run-make/doctests-test_harness/doctests.stdout b/tests/run-make/doctests-test_harness/doctests.stdout new file mode 100644 index 0000000000000..082f9be7c16ba --- /dev/null +++ b/tests/run-make/doctests-test_harness/doctests.stdout @@ -0,0 +1,64 @@ + +running 2 tests +test doctests.rs - (line 15) ... FAILED +test doctests.rs - (line 4) ... FAILED + +failures: + +---- doctests.rs - (line 15) stdout ---- +Test executable failed ($STATUS). + +stdout: + +running 2 tests +test bad ... FAILED +test good ... ok + +failures: + +---- bad stdout ---- + +thread 'bad' ($TID) panicked at doctests.rs:4:5: +assertion failed: false +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + bad + +test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + + +---- doctests.rs - (line 4) stdout ---- +Test executable failed ($STATUS). + +stdout: + +running 2 tests +test ill ... FAILED +test well ... ok + +failures: + +---- ill stdout ---- + +thread 'ill' ($TID) panicked at doctests.rs:9:6: +assertion failed: false +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + ill + +test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + + + +failures: + doctests.rs - (line 15) + doctests.rs - (line 4) + +test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/run-make/doctests-test_harness/rmake.rs b/tests/run-make/doctests-test_harness/rmake.rs new file mode 100644 index 0000000000000..608adebbd54f2 --- /dev/null +++ b/tests/run-make/doctests-test_harness/rmake.rs @@ -0,0 +1,41 @@ +//@ ignore-cross-compile (needs to run host tool binary) + +// Test the behavior of doctests that are marked `test_harness` and that contain multiple `#[test]` +// functions. Sadly this needs to be a run-make test instead of rustdoc-UI one because at the time +// of writing we can only pass `--test-threads=1` to the inner test suite using a runtool (needed to +// guarantee deterministic test output) which we'd like to be written in Rust to be cross-platform. +// +// See also #157511 and `tests/rustdoc-ui/doctest/test_harness.rs`. + +use std::path::Path; + +use run_make_support::{diff, rustc, rustdoc}; + +fn main() { + let doctests_path = Path::new("doctests.rs"); + let runtool_path = Path::new("runtool.rs"); + + rustc().input(doctests_path).crate_type("lib").run(); + rustc().input(runtool_path).run(); + + let output = rustdoc() + .input(doctests_path) + .arg("--test") + // for the outer test suite + .arg("--test-args=--test-threads=1") + .arg("--test-runtool") + .arg(Path::new(".").join(runtool_path).with_extension(std::env::consts::EXE_EXTENSION)) + .arg("-L.") + .env("RUST_BACKTRACE", "0") + .run_fail(); + output.assert_exit_code(101); + output.assert_stderr_equals(""); + + diff() + .expected_file(doctests_path.with_extension("stdout")) + .actual_text("stdout", output.stdout_utf8()) + .normalize(r#"finished in \d+\.\d+s"#, "finished in $$TIME") + .normalize(r"thread '(?P.*?)' \(\d+\) panicked", "thread '$name' ($$TID) panicked") + .normalize(r"Test executable failed \(.+?\)", "Test executable failed ($$STATUS)") + .run(); +} diff --git a/tests/run-make/doctests-test_harness/runtool.rs b/tests/run-make/doctests-test_harness/runtool.rs new file mode 100644 index 0000000000000..e656b58269ff7 --- /dev/null +++ b/tests/run-make/doctests-test_harness/runtool.rs @@ -0,0 +1,11 @@ +// The whole purpose of this runtool is to pass `--test-threads=1` +// to the inner testsuite to guarantee deterministic output. +// See also #157511. + +fn main() { + let status = std::process::Command::new(std::env::args().nth(1).unwrap()) + .arg("--test-threads=1") + .status() + .unwrap(); + std::process::exit(status.code().unwrap()) +} diff --git a/tests/rustdoc-html/impl/impl-restriction-document-private.rs b/tests/rustdoc-html/impl/impl-restriction-document-private.rs index 6ea55eff54cf6..97975d4e0b6e1 100644 --- a/tests/rustdoc-html/impl/impl-restriction-document-private.rs +++ b/tests/rustdoc-html/impl/impl-restriction-document-private.rs @@ -2,14 +2,14 @@ #![crate_name = "c"] #![feature(impl_restriction)] -//@ matches c/trait.Foo.html '//*[@class="stab impl_restriction"]' \ +//@ matches c/trait.Foo.html '//*[@class="impl-restriction"]' \ // 'This trait cannot be implemented outside c.$' -//@ has c/trait.Foo.html '//*[@class="stab impl_restriction"]//code' 'c' +//@ has c/trait.Foo.html '//*[@class="impl-restriction"]//code' 'c' pub impl(crate) trait Foo {} pub mod inner { - //@ matches c/inner/trait.Bar.html '//*[@class="stab impl_restriction"]' \ + //@ matches c/inner/trait.Bar.html '//*[@class="impl-restriction"]' \ // 'This trait cannot be implemented outside c::inner.$' - //@ has c/inner/trait.Bar.html '//*[@class="stab impl_restriction"]//code' 'c::inner' + //@ has c/inner/trait.Bar.html '//*[@class="impl-restriction"]//code' 'c::inner' pub impl(self) trait Bar {} } diff --git a/tests/rustdoc-html/impl/impl-restriction.rs b/tests/rustdoc-html/impl/impl-restriction.rs index 11df67425f9b9..deb355959e7d2 100644 --- a/tests/rustdoc-html/impl/impl-restriction.rs +++ b/tests/rustdoc-html/impl/impl-restriction.rs @@ -1,14 +1,14 @@ #![crate_name = "c"] #![feature(impl_restriction)] -//@ matches c/trait.Foo.html '//*[@class="stab impl_restriction"]' \ +//@ matches c/trait.Foo.html '//*[@class="impl-restriction"]' \ // 'This trait cannot be implemented outside c.$' -//@ has c/trait.Foo.html '//*[@class="stab impl_restriction"]//code' 'c' +//@ has c/trait.Foo.html '//*[@class="impl-restriction"]//code' 'c' pub impl(crate) trait Foo {} pub mod inner { - //@ matches c/inner/trait.Bar.html '//*[@class="stab impl_restriction"]' \ + //@ matches c/inner/trait.Bar.html '//*[@class="impl-restriction"]' \ // 'This trait cannot be implemented outside c.$' - //@ has c/inner/trait.Bar.html '//*[@class="stab impl_restriction"]//code' 'c' + //@ has c/inner/trait.Bar.html '//*[@class="impl-restriction"]//code' 'c' pub impl(self) trait Bar {} } diff --git a/tests/rustdoc-ui/doctest/test_harness.rs b/tests/rustdoc-ui/doctest/test_harness.rs new file mode 100644 index 0000000000000..f32309b9104ec --- /dev/null +++ b/tests/rustdoc-ui/doctest/test_harness.rs @@ -0,0 +1,42 @@ +// Ensure that the code block attr `test_harness` works as expected. +//@ compile-flags: --test --test-args --test-threads=1 +//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ normalize-stdout: "Test executable failed \(.+?\)" -> "Test executable failed ($$STATUS)" +//@ rustc-env: RUST_BACKTRACE=0 +//@ failure-status: 101 + +// NOTE(#157511): This test file only contains `test_harness` doctests that each only contain at +// most a single `#[test]` function. That's because at the time of writing, arguments passed via +// `--test-args` only get propagated to the "main" libtest runner. However, we would *have* to pass +// `--test-threads=1` to the inner libtest runner to ensure deterministic test output. We can only +// do that by utilizing a runtool currently. +// +// While we could call a runtool "inline" (via something like `sh -c` on Linux) here in this UI test +// we would need to branch on the host platform and do some crimes to get it working. Instead, we +// have chosen to use a run-make test for this with a Rust runtool which is cross-platform. + +// The `main` fn won't be run under `test_harness`, so this test should pass. +//! ```test_harness +//! fn main() { +//! assert!(false); +//! } +//! ``` + +// Contrary to normal doctests, cfg `test` is active under `test_harness`. +//! ```test_harness +//! #[cfg(test)] +//! mod group { +//! #[test] +//! fn element() { +//! assert!(false); +//! } +//! } +//! ``` + +// `test_harness` doctests aren't implicitly wrapped in a `main` fn even if they contain stmts. +//! ```test_harness +//! assert!(true); +//! +//! #[test] fn extra() {} +//! ``` diff --git a/tests/rustdoc-ui/doctest/test_harness.stdout b/tests/rustdoc-ui/doctest/test_harness.stdout new file mode 100644 index 0000000000000..6e71e5497b224 --- /dev/null +++ b/tests/rustdoc-ui/doctest/test_harness.stdout @@ -0,0 +1,49 @@ + +running 3 tests +test $DIR/test_harness.rs - (line 20) ... ok +test $DIR/test_harness.rs - (line 25) ... FAILED +test $DIR/test_harness.rs - (line 34) ... FAILED + +failures: + +---- $DIR/test_harness.rs - (line 25) stdout ---- +Test executable failed ($STATUS). + +stdout: + +running 1 test +test group::element ... FAILED + +failures: + +---- group::element stdout ---- + +thread 'group::element' ($TID) panicked at $DIR/test_harness.rs:6:9: +assertion failed: false +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + group::element + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + + +---- $DIR/test_harness.rs - (line 34) stdout ---- +error: non-item macro in item position: assert + --> $DIR/test_harness.rs:35:1 + | +LL | assert!(true); + | ^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +Couldn't compile the test. + +failures: + $DIR/test_harness.rs - (line 25) + $DIR/test_harness.rs - (line 34) + +test result: FAILED. 1 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/ui-fulldeps/rustc_public/check_attribute.rs b/tests/ui-fulldeps/rustc_public/check_attribute.rs index 393ff4c63c5ad..32ae1945ee3db 100644 --- a/tests/ui-fulldeps/rustc_public/check_attribute.rs +++ b/tests/ui-fulldeps/rustc_public/check_attribute.rs @@ -15,6 +15,7 @@ extern crate rustc_interface; #[macro_use] extern crate rustc_public; +use rustc_public::ty::AdtDef; use rustc_public::{CrateDef, CrateItems}; use std::io::Write; use std::ops::ControlFlow; @@ -25,14 +26,15 @@ const CRATE_NAME: &str = "input"; fn test_stable_mir() -> ControlFlow<()> { // Find items in the local crate. let items = rustc_public::all_local_items(); + let adts = rustc_public::local_crate().adts(); - test_tool(&items); + test_tool(&items, &adts); ControlFlow::Continue(()) } // Test tool attributes. -fn test_tool(items: &CrateItems) { +fn test_tool(items: &CrateItems, adts: &[AdtDef]) { let rustfmt_fn = *get_item(&items, "do_not_format").unwrap(); let rustfmt_attrs = rustfmt_fn.tool_attrs(&["rustfmt".to_string(), "skip".to_string()]); assert_eq!(rustfmt_attrs[0].as_str(), "#[rustfmt::skip]\n"); @@ -41,12 +43,24 @@ fn test_tool(items: &CrateItems) { let clippy_attrs = clippy_fn.tool_attrs(&["clippy".to_string(), "cyclomatic_complexity".to_string()]); assert_eq!(clippy_attrs[0].as_str(), "#[clippy::cyclomatic_complexity = \"100\"]\n"); + + let cake_struct = *get_adt(adts, "Cake").unwrap(); + let cake_variant = cake_struct.variants()[0]; + let fields = cake_variant.fields(); + let sugar_field = fields.iter().find(|f| f.name == "cake_sugar").unwrap(); + let cake_attrs = + sugar_field.tool_attrs(&["clippy".to_string(), "struct_field_names".to_string()]); + assert_eq!(cake_attrs[0].as_str(), "#[clippy::struct_field_names]\n"); } fn get_item<'a>(items: &'a CrateItems, name: &str) -> Option<&'a rustc_public::CrateItem> { items.iter().find(|crate_item| crate_item.trimmed_name() == name) } +fn get_adt<'a>(adts: &'a [AdtDef], name: &str) -> Option<&'a AdtDef> { + adts.iter().find(|adt| adt.trimmed_name() == name) +} + /// This test will generate and analyze a dummy crate using the stable mir. /// For that, it will first write the dummy crate into a file. /// Then it will create a `RustcPublic` using custom arguments and then @@ -87,6 +101,15 @@ fn generate_input(path: &str) -> std::io::Result<()> { #[derive(Debug, Clone, Copy)] struct Foo(u32); + // Field annotations. + pub struct Cake {{ + #[clippy::struct_field_names] + cake_sugar: u8, + #[clippy::struct_field_names] + cake_flour: u8, + cake_eggs: u8 + }} + // A rustfmt tool attribute. #[rustfmt::skip] fn do_not_format() {{}} diff --git a/tests/ui-fulldeps/rustc_public/check_ty_fold.rs b/tests/ui-fulldeps/rustc_public/check_ty_fold.rs index 9ac7f14570e57..5e3069123d5e9 100644 --- a/tests/ui-fulldeps/rustc_public/check_ty_fold.rs +++ b/tests/ui-fulldeps/rustc_public/check_ty_fold.rs @@ -16,6 +16,7 @@ extern crate rustc_interface; #[macro_use] extern crate rustc_public; +use rustc_public::CrateDefType; use rustc_public::mir::{ Body, FieldIdx, MirVisitor, Place, ProjectionElem, visit::{Location, PlaceContext}, diff --git a/tests/ui/README.md b/tests/ui/README.md index a1ec43ea3ba4f..6ffef1a1cd699 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -1291,6 +1291,12 @@ An assorted collection of tests that involves specific diagnostic spans. See [Tracking issue for specialization (RFC 1210) #31844](https://github.com/rust-lang/rust/issues/31844). +## `tests/ui/splat` + +Tests for the `#![feature(splat)]` attribute. + +See [Tracking Issue for argument splatting #153629](https://github.com/rust-lang/rust/issues/153629). + ## `tests/ui/stability-attribute/` Stability attributes used internally by the standard library: `#[stable()]` and `#[unstable()]`. diff --git a/tests/ui/async-await/async-drop/async-drop-box.rs b/tests/ui/async-await/async-drop/async-drop-box.rs index 0a6ed412863a2..f44a3e8749e04 100644 --- a/tests/ui/async-await/async-drop/async-drop-box.rs +++ b/tests/ui/async-await/async-drop/async-drop-box.rs @@ -1,12 +1,9 @@ //@ run-pass //@ check-run-results // struct `Foo` has both sync and async drop. -// `Foo` is always inside `Box` +// `Foo` is always inside `Box`. // Sync version is called in sync context, async version is called in async function. -//@ known-bug: #143658 -// async version is never actually called - #![feature(async_drop)] #![allow(incomplete_features)] @@ -30,6 +27,10 @@ struct Foo { my_resource_handle: usize, } +trait DynFoo {} + +impl DynFoo for Foo {} + impl Foo { fn new(my_resource_handle: usize) -> Self { let out = Foo { @@ -58,6 +59,8 @@ fn main() { } println!("Middle"); block_on(bar(10)); + println!("Dyn"); + block_on(bar_dyn(20)); println!("Done") } @@ -65,6 +68,12 @@ async fn bar(ident_base: usize) { let _first = Box::new(Foo::new(ident_base)); } +async fn bar_dyn(ident_base: usize) { + // FIXME(async_drop): boxed dyn pointees should eventually use async drop + // glue in async contexts. For now this documents the sync-drop fallback. + let _first: Box = Box::new(Foo::new(ident_base)); +} + fn block_on(fut_unpin: F) -> F::Output where F: Future, diff --git a/tests/ui/async-await/async-drop/async-drop-box.run.stdout b/tests/ui/async-await/async-drop/async-drop-box.run.stdout index a2dab2ba992b5..da0ff3c65384b 100644 --- a/tests/ui/async-await/async-drop/async-drop-box.run.stdout +++ b/tests/ui/async-await/async-drop/async-drop-box.run.stdout @@ -2,5 +2,8 @@ Foo::new() : 7 Foo::drop() : 7 Middle Foo::new() : 10 -Foo::drop() : 10 +Foo::async drop() : 10 +Dyn +Foo::new() : 20 +Foo::drop() : 20 Done diff --git a/tests/ui/feature-gates/feature-gate-splat.rs b/tests/ui/feature-gates/feature-gate-splat.rs new file mode 100644 index 0000000000000..ebcfc0e5a1d9f --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-splat.rs @@ -0,0 +1,8 @@ +#[rustfmt::skip] +fn tuple_args( + #[splat] //~ ERROR the `#[splat]` attribute is an experimental feature + (a, b, c): (u32, i8, char), +) { +} + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-splat.stderr b/tests/ui/feature-gates/feature-gate-splat.stderr new file mode 100644 index 0000000000000..11ddc2a3b82e7 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-splat.stderr @@ -0,0 +1,14 @@ +error[E0658]: the `#[splat]` attribute is an experimental feature + --> $DIR/feature-gate-splat.rs:3:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = note: see issue #153629 for more information + = help: add `#![feature(splat)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = note: the `#[splat]` attribute is experimental + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/json/multibyte-rendered-ansi-issue-157148.rs b/tests/ui/json/multibyte-rendered-ansi-issue-157148.rs new file mode 100644 index 0000000000000..72e39856db9c2 --- /dev/null +++ b/tests/ui/json/multibyte-rendered-ansi-issue-157148.rs @@ -0,0 +1,16 @@ +// Regression test for . +// JSON rendered diagnostics should not ICE when spans involve non-ASCII source text. + +//@ edition: 2021 +//@ check-pass +//@ compile-flags: --error-format=json --json=diagnostic-rendered-ansi +//@ normalize-stderr: "(\\u001b\[[0-9;]+m)+" -> "[ANSI]" + +#![warn(unused_mut)] + +fn main() { + let mut x = 0; // 这是一段中文注释,用于在被下划线标注的行中加入多字节字符 + //~^ WARNING variable does not need to be mutable + //~| HELP remove this `mut` + let _ = x; +} diff --git a/tests/ui/json/multibyte-rendered-ansi-issue-157148.stderr b/tests/ui/json/multibyte-rendered-ansi-issue-157148.stderr new file mode 100644 index 0000000000000..268702281d88d --- /dev/null +++ b/tests/ui/json/multibyte-rendered-ansi-issue-157148.stderr @@ -0,0 +1,18 @@ +{"$message_type":"diagnostic","message":"variable does not need to be mutable","code":{"code":"unused_mut","explanation":null},"level":"warning","spans":[{"file_name":"$DIR/multibyte-rendered-ansi-issue-157148.rs","byte_start":365,"byte_end":370,"line_start":12,"line_end":12,"column_start":9,"column_end":14,"is_primary":true,"text":[{"text":" let mut x = 0; // 这是一段中文注释,用于在被下划线标注的行中加入多字节字符","highlight_start":9,"highlight_end":14}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"the lint level is defined here","code":null,"level":"note","spans":[{"file_name":"$DIR/multibyte-rendered-ansi-issue-157148.rs","byte_start":331,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":19,"is_primary":true,"text":[{"text":"#![warn(unused_mut)]","highlight_start":9,"highlight_end":19}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":null},{"message":"remove this `mut`","code":null,"level":"help","spans":[{"file_name":"$DIR/multibyte-rendered-ansi-issue-157148.rs","byte_start":365,"byte_end":369,"line_start":12,"line_end":12,"column_start":9,"column_end":13,"is_primary":true,"text":[{"text":" let mut x = 0; // 这是一段中文注释,用于在被下划线标注的行中加入多字节字符","highlight_start":9,"highlight_end":13}],"label":null,"suggested_replacement":"","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"[ANSI]warning[ANSI]: variable does not need to be mutable[ANSI] + [ANSI]--> [ANSI]$DIR/multibyte-rendered-ansi-issue-157148.rs:12:9 + [ANSI]|[ANSI] +[ANSI]LL[ANSI] [ANSI]|[ANSI] let mut x = 0; // 这是一段中文注释,用于在被下划线标注的行中加入多字节字符 + [ANSI]|[ANSI] [ANSI]----[ANSI]^[ANSI] + [ANSI]|[ANSI] [ANSI]|[ANSI] + [ANSI]|[ANSI] [ANSI]help: remove this `mut`[ANSI] + [ANSI]|[ANSI] +[ANSI]note[ANSI]: the lint level is defined here + [ANSI]--> [ANSI]$DIR/multibyte-rendered-ansi-issue-157148.rs:9:9 + [ANSI]|[ANSI] +[ANSI]LL[ANSI] [ANSI]|[ANSI] #![warn(unused_mut)] + [ANSI]|[ANSI] [ANSI]^^^^^^^^^^[ANSI] + +"} +{"$message_type":"diagnostic","message":"1 warning emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"[ANSI]warning[ANSI]: 1 warning emitted[ANSI] + +"} diff --git a/tests/ui/lint/improper-ctypes/async-extern-fn-next-solver-issue-156352.rs b/tests/ui/lint/improper-ctypes/async-extern-fn-next-solver-issue-156352.rs new file mode 100644 index 0000000000000..4624e0d0a81ea --- /dev/null +++ b/tests/ui/lint/improper-ctypes/async-extern-fn-next-solver-issue-156352.rs @@ -0,0 +1,15 @@ +//@ compile-flags: -Znext-solver=globally +//@ edition: 2021 +//@ check-pass + +// Regression test for . +// `async extern "system" fn` was ICE-ing with the new solver because +// `improper_ctypes_definitions` normalized the opaque return type all the way +// to a `Coroutine`, which hit an unexpected `bug!`. After the fix the lint +// fires with "opaque types have no C equivalent" instead of crashing. + +#![allow(improper_ctypes_definitions)] + +async extern "system" fn check_valid_subset(h: usize) {} + +fn main() {} diff --git a/tests/ui/splat/splat-invalid.rs b/tests/ui/splat/splat-invalid.rs new file mode 100644 index 0000000000000..314003459ef72 --- /dev/null +++ b/tests/ui/splat/splat-invalid.rs @@ -0,0 +1,33 @@ +//! Test using `#[splat]` incorrectly, in ways not covered by other tests. + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(c_variadic)] + +fn multisplat_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} +//~^ ERROR multiple `#[splat]`s are not allowed in the same function + +unsafe extern "C" fn splat_variadic(#[splat] (_a, _b): (u32, i8), varargs: ...) {} +//~^ ERROR `...` and `#[splat]` are not allowed in the same function + +unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} +//~^ ERROR `...` and `#[splat]` are not allowed in the same function +//~| ERROR `...` must be the last argument of a C-variadic function + +extern "C" { + fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} + //~^ ERROR incorrect function inside `extern` block + //~| ERROR `...` and `#[splat]` are not allowed in the same function + + fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + //~^ ERROR incorrect function inside `extern` block + //~| ERROR `...` and `#[splat]` are not allowed in the same function + //~| ERROR `...` must be the last argument of a C-variadic function + + // FIXME(splat): tuple layouts are unspecified. Should this error in addition to + // the existing `improper_ctypes` lint? + #[expect(improper_ctypes)] + fn bar_2(#[splat] _: (u32, i8)); +} + +fn main() {} diff --git a/tests/ui/splat/splat-invalid.stderr b/tests/ui/splat/splat-invalid.stderr new file mode 100644 index 0000000000000..d88cbf4ff0b6d --- /dev/null +++ b/tests/ui/splat/splat-invalid.stderr @@ -0,0 +1,77 @@ +error: multiple `#[splat]`s are not allowed in the same function + --> $DIR/splat-invalid.rs:7:19 + | +LL | fn multisplat_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` from all but one argument + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:10:37 + | +LL | unsafe extern "C" fn splat_variadic(#[splat] (_a, _b): (u32, i8), varargs: ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^ + | + = help: remove `#[splat]` or remove `...` + +error: `...` must be the last argument of a C-variadic function + --> $DIR/splat-invalid.rs:13:38 + | +LL | unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^^^^^^^^^^ + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:13:38 + | +LL | unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` or remove `...` + +error: incorrect function inside `extern` block + --> $DIR/splat-invalid.rs:18:8 + | +LL | extern "C" { + | ---------- `extern` blocks define existing foreign functions and functions inside of them cannot have a body +LL | fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} + | ^^^^^^^^^^^^^^^ cannot have a body -- help: remove the invalid body: `;` + | + = help: you might have meant to write a function accessible through FFI, which can be done by writing `extern fn` outside of the `extern` block + = note: for more information, visit https://doc.rust-lang.org/std/keyword.extern.html + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:18:24 + | +LL | fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ + | + = help: remove `#[splat]` or remove `...` + +error: incorrect function inside `extern` block + --> $DIR/splat-invalid.rs:22:8 + | +LL | extern "C" { + | ---------- `extern` blocks define existing foreign functions and functions inside of them cannot have a body +... +LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^^^^^^^^^^^^^ cannot have a body -- help: remove the invalid body: `;` + | + = help: you might have meant to write a function accessible through FFI, which can be done by writing `extern fn` outside of the `extern` block + = note: for more information, visit https://doc.rust-lang.org/std/keyword.extern.html + +error: `...` must be the last argument of a C-variadic function + --> $DIR/splat-invalid.rs:22:24 + | +LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^ + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:22:24 + | +LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` or remove `...` + +error: aborting due to 9 previous errors + diff --git a/tests/ui/splat/splat-non-fn-arg.rs b/tests/ui/splat/splat-non-fn-arg.rs new file mode 100644 index 0000000000000..6f2815e2b6579 --- /dev/null +++ b/tests/ui/splat/splat-non-fn-arg.rs @@ -0,0 +1,59 @@ +//! Test that using `#[splat]` on non-function-arguments is an error. + +#![allow(incomplete_features)] +#![feature(splat)] + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on functions +fn tuple_args_bad((a, b): (u32, i8)) {} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on traits +trait FooTraitBad { + fn tuple_1(_: (u32,)); + + fn tuple_4(self, _: (u32, i8, (), f32)); +} + +struct Foo; + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on inherent impl blocks +impl Foo { + fn tuple_1_bad((a,): (u32,)) {} +} + +impl Foo { + #[splat] //~ ERROR `#[splat]` attribute cannot be used on inherent methods + fn tuple_3_bad((a, b, c): (u32, i32, i8)) {} + + #[splat] //~ ERROR `#[splat]` attribute cannot be used on inherent methods + fn tuple_2_bad(self, (a, b): (u32, i8)) -> u32 { + a + } +} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on trait impl blocks +impl FooTraitBad for Foo { + fn tuple_1(_: (u32,)) {} + + fn tuple_4(self, _: (u32, i8, (), f32)) {} +} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on foreign modules +extern "C" { + fn foo_2(_: (u32, i8)); +} + +extern "C" { + #[splat] //~ ERROR `#[splat]` attribute cannot be used on foreign functions + fn bar_2_bad(_: (u32, i8)); +} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on modules +mod foo_mod {} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on use statements +use std::mem; + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on structs +struct FooStruct; + +fn main() {} diff --git a/tests/ui/splat/splat-non-fn-arg.stderr b/tests/ui/splat/splat-non-fn-arg.stderr new file mode 100644 index 0000000000000..089636f3d4dff --- /dev/null +++ b/tests/ui/splat/splat-non-fn-arg.stderr @@ -0,0 +1,90 @@ +error: `#[splat]` attribute cannot be used on functions + --> $DIR/splat-non-fn-arg.rs:6:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on traits + --> $DIR/splat-non-fn-arg.rs:9:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on inherent impl blocks + --> $DIR/splat-non-fn-arg.rs:18:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on inherent methods + --> $DIR/splat-non-fn-arg.rs:24:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on inherent methods + --> $DIR/splat-non-fn-arg.rs:27:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on trait impl blocks + --> $DIR/splat-non-fn-arg.rs:33:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on foreign modules + --> $DIR/splat-non-fn-arg.rs:40:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on foreign functions + --> $DIR/splat-non-fn-arg.rs:46:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on modules + --> $DIR/splat-non-fn-arg.rs:50:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on use statements + --> $DIR/splat-non-fn-arg.rs:53:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on structs + --> $DIR/splat-non-fn-arg.rs:56:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: aborting due to 11 previous errors + diff --git a/tests/ui/trait-bounds/duplicate-relaxed-bounds.rs b/tests/ui/trait-bounds/duplicate-relaxed-bounds.rs index a1681ddec77b0..69f2ded988c8d 100644 --- a/tests/ui/trait-bounds/duplicate-relaxed-bounds.rs +++ b/tests/ui/trait-bounds/duplicate-relaxed-bounds.rs @@ -9,7 +9,13 @@ trait Trait { // even on *associated types* like here. Test that we no longer do that. type Type: ?Sized + ?Sized; //~^ ERROR duplicate relaxed `Sized` bounds - //~| ERROR duplicate relaxed `Sized` bounds + + // Make sure, should we ever allow such where bounds, + // we still detect duplicate relaxed bounds. + type Type2: ?Sized + where + Self::Type: ?Sized; + //~^ ERROR this relaxed bound is not permitted here } // We used to emit an additional error about "multiple relaxed default bounds". @@ -19,4 +25,12 @@ trait Trait { fn not_dupes() {} //~^ ERROR bound modifier `?` can only be applied to `Sized` +// Demonstrate that we do detect dupes from where bounds +fn where_dupes() +//~^ ERROR duplicate relaxed `Sized` bounds +where + T: ?Sized, +{ +} + fn main() {} diff --git a/tests/ui/trait-bounds/duplicate-relaxed-bounds.stderr b/tests/ui/trait-bounds/duplicate-relaxed-bounds.stderr index ccc723fc7a653..130f5e3640090 100644 --- a/tests/ui/trait-bounds/duplicate-relaxed-bounds.stderr +++ b/tests/ui/trait-bounds/duplicate-relaxed-bounds.stderr @@ -10,6 +10,29 @@ error[E0203]: duplicate relaxed `Iterator` bounds LL | fn dupes() {} | ^^^^^^^^^ ^^^^^^^^^ +error[E0203]: duplicate relaxed `Sized` bounds + --> $DIR/duplicate-relaxed-bounds.rs:10:16 + | +LL | type Type: ?Sized + ?Sized; + | ^^^^^^ ^^^^^^ + +error: this relaxed bound is not permitted here + --> $DIR/duplicate-relaxed-bounds.rs:17:21 + | +LL | Self::Type: ?Sized; + | ^^^^^^ + | + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item + +error[E0203]: duplicate relaxed `Sized` bounds + --> $DIR/duplicate-relaxed-bounds.rs:29:19 + | +LL | fn where_dupes() + | ^^^^^^ +... +LL | T: ?Sized, + | ^^^^^^ + error: bound modifier `?` can only be applied to `Sized` --> $DIR/duplicate-relaxed-bounds.rs:1:31 | @@ -23,25 +46,11 @@ LL | fn dupes() {} | ^^^^^^^^^ error: bound modifier `?` can only be applied to `Sized` - --> $DIR/duplicate-relaxed-bounds.rs:19:26 + --> $DIR/duplicate-relaxed-bounds.rs:25:26 | LL | fn not_dupes() {} | ^^^^^^^^^ -error[E0203]: duplicate relaxed `Sized` bounds - --> $DIR/duplicate-relaxed-bounds.rs:10:16 - | -LL | type Type: ?Sized + ?Sized; - | ^^^^^^ ^^^^^^ - -error[E0203]: duplicate relaxed `Sized` bounds - --> $DIR/duplicate-relaxed-bounds.rs:10:16 - | -LL | type Type: ?Sized + ?Sized; - | ^^^^^^ ^^^^^^ - | - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -error: aborting due to 7 previous errors +error: aborting due to 8 previous errors For more information about this error, try `rustc --explain E0203`.