From b2a95cb582cec1088b060e79e1ef6ce5a9393ed0 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sun, 22 May 2022 12:40:26 -0700 Subject: [PATCH 1/7] Lifetime variance fixes for rustdoc --- compiler/rustc_hir/src/hir.rs | 8 +- src/librustdoc/clean/inline.rs | 2 +- src/librustdoc/clean/mod.rs | 288 +++++++++++++++++---------------- src/librustdoc/clean/utils.rs | 22 +-- 4 files changed, 166 insertions(+), 154 deletions(-) diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index cebf68769361b..bda7affe52983 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -343,12 +343,12 @@ pub struct GenericArgs<'hir> { pub span_ext: Span, } -impl GenericArgs<'_> { +impl<'hir> GenericArgs<'hir> { pub const fn none() -> Self { Self { args: &[], bindings: &[], parenthesized: false, span_ext: DUMMY_SP } } - pub fn inputs(&self) -> &[Ty<'_>] { + pub fn inputs(&self) -> &[Ty<'hir>] { if self.parenthesized { for arg in self.args { match arg { @@ -549,7 +549,7 @@ impl<'hir> Generics<'hir> { &NOPE } - pub fn get_named(&self, name: Symbol) -> Option<&GenericParam<'_>> { + pub fn get_named(&self, name: Symbol) -> Option<&GenericParam<'hir>> { for param in self.params { if name == param.name.ident().name { return Some(param); @@ -608,7 +608,7 @@ impl<'hir> Generics<'hir> { pub fn bounds_for_param( &self, param_def_id: LocalDefId, - ) -> impl Iterator> { + ) -> impl Iterator> { self.predicates.iter().filter_map(move |pred| match pred { WherePredicate::BoundPredicate(bp) if bp.is_param_bound(param_def_id.to_def_id()) => { Some(bp) diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index e7b966758d6e0..46c94344a8711 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -218,7 +218,7 @@ pub(crate) fn build_external_trait(cx: &mut DocContext<'_>, did: DefId) -> clean } } -fn build_external_function(cx: &mut DocContext<'_>, did: DefId) -> clean::Function { +fn build_external_function<'tcx>(cx: &mut DocContext<'tcx>, did: DefId) -> clean::Function { let sig = cx.tcx.fn_sig(did); let predicates = cx.tcx.predicates_of(did); diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 04d97e0dabfa0..461037dd3736a 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -44,12 +44,12 @@ use utils::*; pub(crate) use self::types::*; pub(crate) use self::utils::{get_auto_trait_and_blanket_impls, krate, register_res}; -pub(crate) trait Clean { - fn clean(&self, cx: &mut DocContext<'_>) -> T; +pub(crate) trait Clean<'tcx, T> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> T; } -impl Clean for DocModule<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Item { +impl<'tcx> Clean<'tcx, Item> for DocModule<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Item { let mut items: Vec = vec![]; items.extend( self.foreigns @@ -89,14 +89,14 @@ impl Clean for DocModule<'_> { } } -impl Clean for [ast::Attribute] { +impl<'tcx> Clean<'tcx, Attributes> for [ast::Attribute] { fn clean(&self, _cx: &mut DocContext<'_>) -> Attributes { Attributes::from_ast(self, None) } } -impl Clean> for hir::GenericBound<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Option { +impl<'tcx> Clean<'tcx, Option> for hir::GenericBound<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Option { Some(match *self { hir::GenericBound::Outlives(lt) => GenericBound::Outlives(lt.clean(cx)), hir::GenericBound::LangItemTrait(lang_item, span, _, generic_args) => { @@ -131,9 +131,9 @@ impl Clean> for hir::GenericBound<'_> { } } -fn clean_trait_ref_with_bindings( - cx: &mut DocContext<'_>, - trait_ref: ty::TraitRef<'_>, +fn clean_trait_ref_with_bindings<'tcx>( + cx: &mut DocContext<'tcx>, + trait_ref: ty::TraitRef<'tcx>, bindings: &[TypeBinding], ) -> Path { let kind = cx.tcx.def_kind(trait_ref.def_id).into(); @@ -148,15 +148,15 @@ fn clean_trait_ref_with_bindings( path } -impl Clean for ty::TraitRef<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Path { +impl<'tcx> Clean<'tcx, Path> for ty::TraitRef<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Path { clean_trait_ref_with_bindings(cx, *self, &[]) } } -fn clean_poly_trait_ref_with_bindings( - cx: &mut DocContext<'_>, - poly_trait_ref: ty::PolyTraitRef<'_>, +fn clean_poly_trait_ref_with_bindings<'tcx>( + cx: &mut DocContext<'tcx>, + poly_trait_ref: ty::PolyTraitRef<'tcx>, bindings: &[TypeBinding], ) -> GenericBound { let poly_trait_ref = poly_trait_ref.lift_to_tcx(cx.tcx).unwrap(); @@ -182,14 +182,14 @@ fn clean_poly_trait_ref_with_bindings( ) } -impl<'tcx> Clean for ty::PolyTraitRef<'tcx> { - fn clean(&self, cx: &mut DocContext<'_>) -> GenericBound { +impl<'tcx> Clean<'tcx, GenericBound> for ty::PolyTraitRef<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> GenericBound { clean_poly_trait_ref_with_bindings(cx, *self, &[]) } } -impl Clean for hir::Lifetime { - fn clean(&self, cx: &mut DocContext<'_>) -> Lifetime { +impl<'tcx> Clean<'tcx, Lifetime> for hir::Lifetime { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Lifetime { let def = cx.tcx.named_region(self.hir_id); if let Some( rl::Region::EarlyBound(_, node_id) @@ -205,8 +205,8 @@ impl Clean for hir::Lifetime { } } -impl Clean for hir::ConstArg { - fn clean(&self, cx: &mut DocContext<'_>) -> Constant { +impl<'tcx> Clean<'tcx, Constant> for hir::ConstArg { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Constant { Constant { type_: cx .tcx @@ -217,7 +217,7 @@ impl Clean for hir::ConstArg { } } -impl Clean> for ty::Region<'_> { +impl<'tcx> Clean<'tcx, Option> for ty::Region<'tcx> { fn clean(&self, _cx: &mut DocContext<'_>) -> Option { match **self { ty::ReStatic => Some(Lifetime::statik()), @@ -239,8 +239,8 @@ impl Clean> for ty::Region<'_> { } } -impl Clean> for hir::WherePredicate<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Option { +impl<'tcx> Clean<'tcx, Option> for hir::WherePredicate<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Option { if !self.in_where_clause() { return None; } @@ -279,8 +279,8 @@ impl Clean> for hir::WherePredicate<'_> { } } -impl<'a> Clean> for ty::Predicate<'a> { - fn clean(&self, cx: &mut DocContext<'_>) -> Option { +impl<'tcx> Clean<'tcx, Option> for ty::Predicate<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Option { let bound_predicate = self.kind(); match bound_predicate.skip_binder() { ty::PredicateKind::Trait(pred) => bound_predicate.rebind(pred).clean(cx), @@ -300,8 +300,8 @@ impl<'a> Clean> for ty::Predicate<'a> { } } -impl<'a> Clean> for ty::PolyTraitPredicate<'a> { - fn clean(&self, cx: &mut DocContext<'_>) -> Option { +impl<'tcx> Clean<'tcx, Option> for ty::PolyTraitPredicate<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Option { // `T: ~const Destruct` is hidden because `T: Destruct` is a no-op. if self.skip_binder().constness == ty::BoundConstness::ConstIfConst && Some(self.skip_binder().def_id()) == cx.tcx.lang_items().destruct_trait() @@ -318,10 +318,10 @@ impl<'a> Clean> for ty::PolyTraitPredicate<'a> { } } -impl<'tcx> Clean> +impl<'tcx> Clean<'tcx, Option> for ty::OutlivesPredicate, ty::Region<'tcx>> { - fn clean(&self, cx: &mut DocContext<'_>) -> Option { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Option { let ty::OutlivesPredicate(a, b) = self; if a.is_empty() && b.is_empty() { @@ -335,8 +335,10 @@ impl<'tcx> Clean> } } -impl<'tcx> Clean> for ty::OutlivesPredicate, ty::Region<'tcx>> { - fn clean(&self, cx: &mut DocContext<'_>) -> Option { +impl<'tcx> Clean<'tcx, Option> + for ty::OutlivesPredicate, ty::Region<'tcx>> +{ + fn clean(&self, cx: &mut DocContext<'tcx>) -> Option { let ty::OutlivesPredicate(ty, lt) = self; if lt.is_empty() { @@ -351,8 +353,8 @@ impl<'tcx> Clean> for ty::OutlivesPredicate, ty: } } -impl<'tcx> Clean for ty::Term<'tcx> { - fn clean(&self, cx: &mut DocContext<'_>) -> Term { +impl<'tcx> Clean<'tcx, Term> for ty::Term<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Term { match self { ty::Term::Ty(ty) => Term::Type(ty.clean(cx)), ty::Term::Const(c) => Term::Constant(c.clean(cx)), @@ -360,8 +362,8 @@ impl<'tcx> Clean for ty::Term<'tcx> { } } -impl<'tcx> Clean for hir::Term<'tcx> { - fn clean(&self, cx: &mut DocContext<'_>) -> Term { +impl<'tcx> Clean<'tcx, Term> for hir::Term<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Term { match self { hir::Term::Ty(ty) => Term::Type(ty.clean(cx)), hir::Term::Const(c) => { @@ -372,8 +374,8 @@ impl<'tcx> Clean for hir::Term<'tcx> { } } -impl<'tcx> Clean for ty::ProjectionPredicate<'tcx> { - fn clean(&self, cx: &mut DocContext<'_>) -> WherePredicate { +impl<'tcx> Clean<'tcx, WherePredicate> for ty::ProjectionPredicate<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> WherePredicate { let ty::ProjectionPredicate { projection_ty, term } = self; WherePredicate::EqPredicate { lhs: projection_ty.clean(cx), rhs: term.clean(cx) } } @@ -381,7 +383,7 @@ impl<'tcx> Clean for ty::ProjectionPredicate<'tcx> { fn clean_projection<'tcx>( ty: ty::ProjectionTy<'tcx>, - cx: &mut DocContext<'_>, + cx: &mut DocContext<'tcx>, def_id: Option, ) -> Type { let lifted = ty.lift_to_tcx(cx.tcx).unwrap(); @@ -401,8 +403,8 @@ fn clean_projection<'tcx>( } } -impl<'tcx> Clean for ty::ProjectionTy<'tcx> { - fn clean(&self, cx: &mut DocContext<'_>) -> Type { +impl<'tcx> Clean<'tcx, Type> for ty::ProjectionTy<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Type { clean_projection(*self, cx, None) } } @@ -414,7 +416,10 @@ fn compute_should_show_cast(self_def_id: Option, trait_: &Path, self_type .map_or(!self_type.is_self_type(), |(id, trait_)| id != trait_) } -fn projection_to_path_segment(ty: ty::ProjectionTy<'_>, cx: &mut DocContext<'_>) -> PathSegment { +fn projection_to_path_segment<'tcx>( + ty: ty::ProjectionTy<'tcx>, + cx: &mut DocContext<'tcx>, +) -> PathSegment { let item = cx.tcx.associated_item(ty.item_def_id); let generics = cx.tcx.generics_of(ty.item_def_id); PathSegment { @@ -426,8 +431,8 @@ fn projection_to_path_segment(ty: ty::ProjectionTy<'_>, cx: &mut DocContext<'_>) } } -impl Clean for ty::GenericParamDef { - fn clean(&self, cx: &mut DocContext<'_>) -> GenericParamDef { +impl<'tcx> Clean<'tcx, GenericParamDef> for ty::GenericParamDef { + fn clean(&self, cx: &mut DocContext<'tcx>) -> GenericParamDef { let (name, kind) = match self.kind { ty::GenericParamDefKind::Lifetime => { (self.name, GenericParamDefKind::Lifetime { outlives: vec![] }) @@ -465,10 +470,10 @@ impl Clean for ty::GenericParamDef { } } -fn clean_generic_param( - cx: &mut DocContext<'_>, - generics: Option<&hir::Generics<'_>>, - param: &hir::GenericParam<'_>, +fn clean_generic_param<'tcx>( + cx: &mut DocContext<'tcx>, + generics: Option<&hir::Generics<'tcx>>, + param: &hir::GenericParam<'tcx>, ) -> GenericParamDef { let (name, kind) = match param.kind { hir::GenericParamKind::Lifetime { .. } => { @@ -536,8 +541,8 @@ fn clean_generic_param( GenericParamDef { name, kind } } -impl Clean for hir::Generics<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Generics { +impl<'tcx> Clean<'tcx, Generics> for hir::Generics<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Generics { // Synthetic type-parameters are inserted after normal ones. // In order for normal parameters to be able to refer to synthetic ones, // scans them first. @@ -618,10 +623,10 @@ impl Clean for hir::Generics<'_> { } } -fn clean_ty_generics( - cx: &mut DocContext<'_>, +fn clean_ty_generics<'tcx>( + cx: &mut DocContext<'tcx>, gens: &ty::Generics, - preds: ty::GenericPredicates<'_>, + preds: ty::GenericPredicates<'tcx>, ) -> Generics { // Don't populate `cx.impl_trait_bounds` before `clean`ning `where` clauses, // since `Clean for ty::Predicate` would consume them. @@ -784,13 +789,13 @@ fn clean_ty_generics( } } -fn clean_fn_or_proc_macro( - item: &hir::Item<'_>, - sig: &hir::FnSig<'_>, - generics: &hir::Generics<'_>, +fn clean_fn_or_proc_macro<'tcx>( + item: &hir::Item<'tcx>, + sig: &hir::FnSig<'tcx>, + generics: &hir::Generics<'tcx>, body_id: hir::BodyId, name: &mut Symbol, - cx: &mut DocContext<'_>, + cx: &mut DocContext<'tcx>, ) -> ItemKind { let attrs = cx.tcx.hir().attrs(item.hir_id()); let macro_kind = attrs.iter().find_map(|a| { @@ -868,10 +873,10 @@ fn clean_fn_decl_legacy_const_generics(func: &mut Function, attrs: &[ast::Attrib } } -fn clean_function( - cx: &mut DocContext<'_>, - sig: &hir::FnSig<'_>, - generics: &hir::Generics<'_>, +fn clean_function<'tcx>( + cx: &mut DocContext<'tcx>, + sig: &hir::FnSig<'tcx>, + generics: &hir::Generics<'tcx>, body_id: hir::BodyId, ) -> Function { let (generics, decl) = enter_impl_trait(cx, |cx| { @@ -884,9 +889,9 @@ fn clean_function( Function { decl, generics } } -fn clean_args_from_types_and_names( - cx: &mut DocContext<'_>, - types: &[hir::Ty<'_>], +fn clean_args_from_types_and_names<'tcx>( + cx: &mut DocContext<'tcx>, + types: &[hir::Ty<'tcx>], names: &[Ident], ) -> Arguments { Arguments { @@ -904,9 +909,9 @@ fn clean_args_from_types_and_names( } } -fn clean_args_from_types_and_body_id( - cx: &mut DocContext<'_>, - types: &[hir::Ty<'_>], +fn clean_args_from_types_and_body_id<'tcx>( + cx: &mut DocContext<'tcx>, + types: &[hir::Ty<'tcx>], body_id: hir::BodyId, ) -> Arguments { let body = cx.tcx.hir().body(body_id); @@ -924,18 +929,18 @@ fn clean_args_from_types_and_body_id( } } -fn clean_fn_decl_with_args( - cx: &mut DocContext<'_>, - decl: &hir::FnDecl<'_>, +fn clean_fn_decl_with_args<'tcx>( + cx: &mut DocContext<'tcx>, + decl: &hir::FnDecl<'tcx>, args: Arguments, ) -> FnDecl { FnDecl { inputs: args, output: decl.output.clean(cx), c_variadic: decl.c_variadic } } -fn clean_fn_decl_from_did_and_sig( - cx: &mut DocContext<'_>, +fn clean_fn_decl_from_did_and_sig<'tcx>( + cx: &mut DocContext<'tcx>, did: Option, - sig: ty::PolyFnSig<'_>, + sig: ty::PolyFnSig<'tcx>, ) -> FnDecl { let mut names = did.map_or(&[] as &[_], |did| cx.tcx.fn_arg_names(did)).iter(); @@ -964,8 +969,8 @@ fn clean_fn_decl_from_did_and_sig( } } -impl Clean for hir::FnRetTy<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> FnRetTy { +impl<'tcx> Clean<'tcx, FnRetTy> for hir::FnRetTy<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> FnRetTy { match *self { Self::Return(ref typ) => Return(typ.clean(cx)), Self::DefaultReturn(..) => DefaultReturn, @@ -973,8 +978,8 @@ impl Clean for hir::FnRetTy<'_> { } } -impl Clean for hir::IsAuto { - fn clean(&self, _: &mut DocContext<'_>) -> bool { +impl<'tcx> Clean<'tcx, bool> for hir::IsAuto { + fn clean(&self, _: &mut DocContext<'tcx>) -> bool { match *self { hir::IsAuto::Yes => true, hir::IsAuto::No => false, @@ -982,16 +987,16 @@ impl Clean for hir::IsAuto { } } -impl Clean for hir::TraitRef<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Path { +impl<'tcx> Clean<'tcx, Path> for hir::TraitRef<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Path { let path = self.path.clean(cx); register_res(cx, path.res); path } } -impl Clean for hir::PolyTraitRef<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> PolyTrait { +impl<'tcx> Clean<'tcx, PolyTrait> for hir::PolyTraitRef<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> PolyTrait { PolyTrait { trait_: self.trait_ref.clean(cx), generic_params: self @@ -1003,8 +1008,8 @@ impl Clean for hir::PolyTraitRef<'_> { } } -impl Clean for hir::TraitItem<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Item { +impl<'tcx> Clean<'tcx, Item> for hir::TraitItem<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Item { let local_did = self.def_id.to_def_id(); cx.with_param_env(local_did, |cx| { let inner = match self.kind { @@ -1050,8 +1055,8 @@ impl Clean for hir::TraitItem<'_> { } } -impl Clean for hir::ImplItem<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Item { +impl<'tcx> Clean<'tcx, Item> for hir::ImplItem<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Item { let local_did = self.def_id.to_def_id(); cx.with_param_env(local_did, |cx| { let inner = match self.kind { @@ -1091,8 +1096,8 @@ impl Clean for hir::ImplItem<'_> { } } -impl Clean for ty::AssocItem { - fn clean(&self, cx: &mut DocContext<'_>) -> Item { +impl<'tcx> Clean<'tcx, Item> for ty::AssocItem { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Item { let tcx = cx.tcx; let kind = match self.kind { ty::AssocKind::Const => { @@ -1282,7 +1287,7 @@ impl Clean for ty::AssocItem { } } -fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &mut DocContext<'_>) -> Type { +fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type { let hir::Ty { hir_id: _, span, ref kind } = *hir_ty; let hir::TyKind::Path(qpath) = kind else { unreachable!() }; @@ -1352,7 +1357,10 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &mut DocContext<'_>) -> Type { } } -fn maybe_expand_private_type_alias(cx: &mut DocContext<'_>, path: &hir::Path<'_>) -> Option { +fn maybe_expand_private_type_alias<'tcx>( + cx: &mut DocContext<'tcx>, + path: &hir::Path<'tcx>, +) -> Option { let Res::Def(DefKind::TyAlias, def_id) = path.res else { return None }; // Substitute private type aliases let def_id = def_id.as_local()?; @@ -1435,8 +1443,8 @@ fn maybe_expand_private_type_alias(cx: &mut DocContext<'_>, path: &hir::Path<'_> Some(cx.enter_alias(substs, |cx| ty.clean(cx))) } -impl Clean for hir::Ty<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Type { +impl<'tcx> Clean<'tcx, Type> for hir::Ty<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Type { use rustc_hir::*; match self.kind { @@ -1530,7 +1538,7 @@ fn normalize<'tcx>(cx: &mut DocContext<'tcx>, ty: Ty<'_>) -> Option> { } } -fn clean_ty<'tcx>(this: Ty<'tcx>, cx: &mut DocContext<'_>, def_id: Option) -> Type { +fn clean_ty<'tcx>(this: Ty<'tcx>, cx: &mut DocContext<'tcx>, def_id: Option) -> Type { trace!("cleaning type: {:?}", this); let ty = normalize(cx, this).unwrap_or(this); match *ty.kind() { @@ -1715,14 +1723,14 @@ fn clean_ty<'tcx>(this: Ty<'tcx>, cx: &mut DocContext<'_>, def_id: Option } } -impl<'tcx> Clean for Ty<'tcx> { - fn clean(&self, cx: &mut DocContext<'_>) -> Type { +impl<'tcx> Clean<'tcx, Type> for Ty<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Type { clean_ty(*self, cx, None) } } -impl<'tcx> Clean for ty::Const<'tcx> { - fn clean(&self, cx: &mut DocContext<'_>) -> Constant { +impl<'tcx> Clean<'tcx, Constant> for ty::Const<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Constant { // FIXME: instead of storing the stringified expression, store `self` directly instead. Constant { type_: self.ty().clean(cx), @@ -1731,15 +1739,15 @@ impl<'tcx> Clean for ty::Const<'tcx> { } } -impl Clean for hir::FieldDef<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Item { +impl<'tcx> Clean<'tcx, Item> for hir::FieldDef<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Item { let def_id = cx.tcx.hir().local_def_id(self.hir_id).to_def_id(); clean_field(def_id, self.ident.name, self.ty.clean(cx), cx) } } -impl Clean for ty::FieldDef { - fn clean(&self, cx: &mut DocContext<'_>) -> Item { +impl<'tcx> Clean<'tcx, Item> for ty::FieldDef { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Item { clean_field(self.did, self.name, cx.tcx.type_of(self.did).clean(cx), cx) } } @@ -1765,7 +1773,7 @@ fn is_field_vis_inherited(tcx: TyCtxt<'_>, def_id: DefId) -> bool { } } -impl Clean for ty::Visibility { +impl<'tcx> Clean<'tcx, Visibility> for ty::Visibility { fn clean(&self, _cx: &mut DocContext<'_>) -> Visibility { match *self { ty::Visibility::Public => Visibility::Public, @@ -1779,8 +1787,8 @@ impl Clean for ty::Visibility { } } -impl Clean for rustc_hir::VariantData<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> VariantStruct { +impl<'tcx> Clean<'tcx, VariantStruct> for rustc_hir::VariantData<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> VariantStruct { VariantStruct { struct_type: CtorKind::from_hir(self), fields: self.fields().iter().map(|x| x.clean(cx)).collect(), @@ -1789,14 +1797,14 @@ impl Clean for rustc_hir::VariantData<'_> { } } -impl Clean> for hir::VariantData<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Vec { +impl<'tcx> Clean<'tcx, Vec> for hir::VariantData<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Vec { self.fields().iter().map(|x| x.clean(cx)).collect() } } -impl Clean for ty::VariantDef { - fn clean(&self, cx: &mut DocContext<'_>) -> Item { +impl<'tcx> Clean<'tcx, Item> for ty::VariantDef { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Item { let kind = match self.ctor_kind { CtorKind::Const => Variant::CLike, CtorKind::Fn => { @@ -1815,8 +1823,8 @@ impl Clean for ty::VariantDef { } } -impl Clean for hir::VariantData<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Variant { +impl<'tcx> Clean<'tcx, Variant> for hir::VariantData<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Variant { match self { hir::VariantData::Struct(..) => Variant::Struct(self.clean(cx)), hir::VariantData::Tuple(..) => Variant::Tuple(self.clean(cx)), @@ -1825,14 +1833,14 @@ impl Clean for hir::VariantData<'_> { } } -impl Clean for hir::Path<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Path { +impl<'tcx> Clean<'tcx, Path> for hir::Path<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Path { Path { res: self.res, segments: self.segments.iter().map(|x| x.clean(cx)).collect() } } } -impl Clean for hir::GenericArgs<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> GenericArgs { +impl<'tcx> Clean<'tcx, GenericArgs> for hir::GenericArgs<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> GenericArgs { if self.parenthesized { let output = self.bindings[0].ty().clean(cx); let output = @@ -1859,14 +1867,14 @@ impl Clean for hir::GenericArgs<'_> { } } -impl Clean for hir::PathSegment<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> PathSegment { +impl<'tcx> Clean<'tcx, PathSegment> for hir::PathSegment<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> PathSegment { PathSegment { name: self.ident.name, args: self.args().clean(cx) } } } -impl Clean for hir::BareFnTy<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> BareFunctionDecl { +impl<'tcx> Clean<'tcx, BareFunctionDecl> for hir::BareFnTy<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> BareFunctionDecl { let (generic_params, decl) = enter_impl_trait(cx, |cx| { // NOTE: generics must be cleaned before args let generic_params = @@ -1879,9 +1887,9 @@ impl Clean for hir::BareFnTy<'_> { } } -fn clean_maybe_renamed_item( - cx: &mut DocContext<'_>, - item: &hir::Item<'_>, +fn clean_maybe_renamed_item<'tcx>( + cx: &mut DocContext<'tcx>, + item: &hir::Item<'tcx>, renamed: Option, ) -> Vec { use hir::ItemKind; @@ -1965,8 +1973,8 @@ fn clean_maybe_renamed_item( }) } -impl Clean for hir::Variant<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> Item { +impl<'tcx> Clean<'tcx, Item> for hir::Variant<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> Item { let kind = VariantItem(self.data.clean(cx)); let what_rustc_thinks = Item::from_hir_id_and_parts(self.id, Some(self.ident.name), kind, cx); @@ -1975,7 +1983,11 @@ impl Clean for hir::Variant<'_> { } } -fn clean_impl(impl_: &hir::Impl<'_>, hir_id: hir::HirId, cx: &mut DocContext<'_>) -> Vec { +fn clean_impl<'tcx>( + impl_: &hir::Impl<'tcx>, + hir_id: hir::HirId, + cx: &mut DocContext<'tcx>, +) -> Vec { let tcx = cx.tcx; let mut ret = Vec::new(); let trait_ = impl_.of_trait.as_ref().map(|t| t.clean(cx)); @@ -2013,11 +2025,11 @@ fn clean_impl(impl_: &hir::Impl<'_>, hir_id: hir::HirId, cx: &mut DocContext<'_> ret } -fn clean_extern_crate( - krate: &hir::Item<'_>, +fn clean_extern_crate<'tcx>( + krate: &hir::Item<'tcx>, name: Symbol, orig_name: Option, - cx: &mut DocContext<'_>, + cx: &mut DocContext<'tcx>, ) -> Vec { // this is the ID of the `extern crate` statement let cnum = cx.tcx.extern_mod_stmt_cnum(krate.def_id).unwrap_or(LOCAL_CRATE); @@ -2063,12 +2075,12 @@ fn clean_extern_crate( }] } -fn clean_use_statement( - import: &hir::Item<'_>, +fn clean_use_statement<'tcx>( + import: &hir::Item<'tcx>, name: Symbol, - path: &hir::Path<'_>, + path: &hir::Path<'tcx>, kind: hir::UseKind, - cx: &mut DocContext<'_>, + cx: &mut DocContext<'tcx>, ) -> Vec { // We need this comparison because some imports (for std types for example) // are "inserted" as well but directly by the compiler and they should not be @@ -2176,9 +2188,9 @@ fn clean_use_statement( vec![Item::from_def_id_and_parts(import.def_id.to_def_id(), None, ImportItem(inner), cx)] } -fn clean_maybe_renamed_foreign_item( - cx: &mut DocContext<'_>, - item: &hir::ForeignItem<'_>, +fn clean_maybe_renamed_foreign_item<'tcx>( + cx: &mut DocContext<'tcx>, + item: &hir::ForeignItem<'tcx>, renamed: Option, ) -> Item { let def_id = item.def_id.to_def_id(); @@ -2209,8 +2221,8 @@ fn clean_maybe_renamed_foreign_item( }) } -impl Clean for hir::TypeBinding<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> TypeBinding { +impl<'tcx> Clean<'tcx, TypeBinding> for hir::TypeBinding<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> TypeBinding { TypeBinding { assoc: PathSegment { name: self.ident.name, args: self.gen_args.clean(cx) }, kind: self.kind.clean(cx), @@ -2218,8 +2230,8 @@ impl Clean for hir::TypeBinding<'_> { } } -impl Clean for hir::TypeBindingKind<'_> { - fn clean(&self, cx: &mut DocContext<'_>) -> TypeBindingKind { +impl<'tcx> Clean<'tcx, TypeBindingKind> for hir::TypeBindingKind<'tcx> { + fn clean(&self, cx: &mut DocContext<'tcx>) -> TypeBindingKind { match *self { hir::TypeBindingKind::Equality { ref term } => { TypeBindingKind::Equality { term: term.clean(cx) } diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 7a12ea0d5c215..285520071c963 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -75,9 +75,9 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { Crate { module, primitives, external_traits: cx.external_traits.clone() } } -pub(crate) fn substs_to_args( - cx: &mut DocContext<'_>, - substs: &[ty::subst::GenericArg<'_>], +pub(crate) fn substs_to_args<'tcx>( + cx: &mut DocContext<'tcx>, + substs: &[ty::subst::GenericArg<'tcx>], mut skip_first: bool, ) -> Vec { substs @@ -99,12 +99,12 @@ pub(crate) fn substs_to_args( .collect() } -fn external_generic_args( - cx: &mut DocContext<'_>, +fn external_generic_args<'tcx>( + cx: &mut DocContext<'tcx>, did: DefId, has_self: bool, bindings: Vec, - substs: SubstsRef<'_>, + substs: SubstsRef<'tcx>, ) -> GenericArgs { let args = substs_to_args(cx, &substs, has_self); @@ -127,12 +127,12 @@ fn external_generic_args( } } -pub(super) fn external_path( - cx: &mut DocContext<'_>, +pub(super) fn external_path<'tcx>( + cx: &mut DocContext<'tcx>, did: DefId, has_self: bool, bindings: Vec, - substs: SubstsRef<'_>, + substs: SubstsRef<'tcx>, ) -> Path { let def_kind = cx.tcx.def_kind(did); let name = cx.tcx.item_name(did); @@ -439,9 +439,9 @@ pub(crate) fn resolve_use_source(cx: &mut DocContext<'_>, path: Path) -> ImportS } } -pub(crate) fn enter_impl_trait(cx: &mut DocContext<'_>, f: F) -> R +pub(crate) fn enter_impl_trait<'tcx, F, R>(cx: &mut DocContext<'tcx>, f: F) -> R where - F: FnOnce(&mut DocContext<'_>) -> R, + F: FnOnce(&mut DocContext<'tcx>) -> R, { let old_bounds = mem::take(&mut cx.impl_trait_bounds); let r = f(cx); From cec6dfcd67b2841e44248a070fd33895330bdfcf Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 23 May 2022 18:47:05 +0200 Subject: [PATCH 2/7] explain how to turn integers into fn ptrs (with an intermediate raw ptr, not a direct transmute) --- library/core/src/intrinsics.rs | 3 +++ library/core/src/primitive_docs.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index 88e4262922dc5..6ba359f6edcd8 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -930,6 +930,9 @@ extern "rust-intrinsic" { /// fn foo() -> i32 { /// 0 /// } + /// // Crucially, we `as`-cast to a raw pointer before `transmute`ing to a function pointer. + /// // This avoids an integer-to-pointer `transmute`, which can be problematic. + /// // Transmuting between raw pointers and function pointers (i.e., two pointer types) is fine. /// let pointer = foo as *const (); /// let function = unsafe { /// std::mem::transmute::<*const (), fn() -> i32>(pointer) diff --git a/library/core/src/primitive_docs.rs b/library/core/src/primitive_docs.rs index ac4e668112b94..147312b9720d4 100644 --- a/library/core/src/primitive_docs.rs +++ b/library/core/src/primitive_docs.rs @@ -1338,6 +1338,32 @@ mod prim_ref {} /// is a reference to the function-specific ZST. `&bar` is basically never what you /// want when `bar` is a function. /// +/// ### Casting to and from integers +/// +/// You cast function pointers directly to integers: +/// +/// ```rust +/// let fnptr: fn(i32) -> i32 = |x| x+2; +/// let fnptr_addr = fnptr as usize; +/// ``` +/// +/// However, a direct cast back is not possible. You need to use `transmute`: +/// +/// ```rust +/// # let fnptr: fn(i32) -> i32 = |x| x+2; +/// # let fnptr_addr = fnptr as usize; +/// let fnptr = fnptr_addr as *const (); +/// let fnptr: fn(i32) -> i32 = unsafe { std::mem::transmute(fnptr) }; +/// assert_eq!(fnptr(40), 42); +/// ``` +/// +/// Crucially, we `as`-cast to a raw pointer before `transmute`ing to a function pointer. +/// This avoids an integer-to-pointer `transmute`, which can be problematic. +/// Transmuting between raw pointers and function pointers (i.e., two pointer types) is fine. +/// +/// Note that all of this is not portable to platforms where function pointers and data pointers +/// have different sizes. +/// /// ### Traits /// /// Function pointers implement the following traits: From 5137d15f91d5778a0d037e5bc4f1f70d9b013aa7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 23 May 2022 19:09:23 +0200 Subject: [PATCH 3/7] sync primitive_docs --- library/std/src/primitive_docs.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/library/std/src/primitive_docs.rs b/library/std/src/primitive_docs.rs index ac4e668112b94..147312b9720d4 100644 --- a/library/std/src/primitive_docs.rs +++ b/library/std/src/primitive_docs.rs @@ -1338,6 +1338,32 @@ mod prim_ref {} /// is a reference to the function-specific ZST. `&bar` is basically never what you /// want when `bar` is a function. /// +/// ### Casting to and from integers +/// +/// You cast function pointers directly to integers: +/// +/// ```rust +/// let fnptr: fn(i32) -> i32 = |x| x+2; +/// let fnptr_addr = fnptr as usize; +/// ``` +/// +/// However, a direct cast back is not possible. You need to use `transmute`: +/// +/// ```rust +/// # let fnptr: fn(i32) -> i32 = |x| x+2; +/// # let fnptr_addr = fnptr as usize; +/// let fnptr = fnptr_addr as *const (); +/// let fnptr: fn(i32) -> i32 = unsafe { std::mem::transmute(fnptr) }; +/// assert_eq!(fnptr(40), 42); +/// ``` +/// +/// Crucially, we `as`-cast to a raw pointer before `transmute`ing to a function pointer. +/// This avoids an integer-to-pointer `transmute`, which can be problematic. +/// Transmuting between raw pointers and function pointers (i.e., two pointer types) is fine. +/// +/// Note that all of this is not portable to platforms where function pointers and data pointers +/// have different sizes. +/// /// ### Traits /// /// Function pointers implement the following traits: From effb56e7665ecaf00cbc1eb2462ae07333d1e5db Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 23 May 2022 18:25:57 +0100 Subject: [PATCH 4/7] macros: change code block language With `ignore (rust)` rather than `ignore (pseudo-Rust)` my editor highlights the code in the block, which is nicer. Signed-off-by: David Wood --- compiler/rustc_macros/src/diagnostics/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs index 73ad415d6c355..ccd3880057b67 100644 --- a/compiler/rustc_macros/src/diagnostics/mod.rs +++ b/compiler/rustc_macros/src/diagnostics/mod.rs @@ -12,7 +12,7 @@ use synstructure::Structure; /// Implements `#[derive(SessionDiagnostic)]`, which allows for errors to be specified as a struct, /// independent from the actual diagnostics emitting code. /// -/// ```ignore (pseudo-rust) +/// ```ignore (rust) /// # extern crate rustc_errors; /// # use rustc_errors::Applicability; /// # extern crate rustc_span; @@ -43,7 +43,7 @@ use synstructure::Structure; /// /// Then, later, to emit the error: /// -/// ```ignore (pseudo-rust) +/// ```ignore (rust) /// sess.emit_err(MoveOutOfBorrowError { /// expected, /// actual, @@ -67,7 +67,7 @@ pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream { /// suggestions to be specified as a structs or enums, independent from the actual diagnostics /// emitting code or diagnostic derives. /// -/// ```ignore (pseudo-rust) +/// ```ignore (rust) /// #[derive(SessionSubdiagnostic)] /// pub enum ExpectedIdentifierLabel<'tcx> { /// #[label(slug = "parser-expected-identifier")] @@ -104,7 +104,7 @@ pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream { /// /// Then, later, to add the subdiagnostic: /// -/// ```ignore (pseudo-rust) +/// ```ignore (rust) /// diag.subdiagnostic(ExpectedIdentifierLabel::WithoutFound { span }); /// /// diag.subdiagnostic(RawIdentifierSuggestion { span, applicability, ident }); From 7e6b6176cb909719c96027a3ad20a71a11485979 Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 23 May 2022 18:24:55 +0100 Subject: [PATCH 5/7] macros: introduce `fluent_messages` macro Adds a new `fluent_messages` macro which performs compile-time validation of the compiler's Fluent resources (i.e. that the resources parse and don't multiply define the same messages) and generates constants that make using those messages in diagnostics more ergonomic. For example, given the following invocation of the macro.. ```ignore (rust) fluent_messages! { typeck => "./typeck.ftl", } ``` ..where `typeck.ftl` has the following contents.. ```fluent typeck-field-multiply-specified-in-initializer = field `{$ident}` specified more than once .label = used more than once .label-previous-use = first use of `{$ident}` ``` ...then the macro parse the Fluent resource, emitting a diagnostic if it fails to do so, and will generate the following code: ```ignore (rust) pub static DEFAULT_LOCALE_RESOURCES: &'static [&'static str] = &[ include_str!("./typeck.ftl"), ]; mod fluent_generated { mod typeck { pub const field_multiply_specified_in_initializer: DiagnosticMessage = DiagnosticMessage::fluent("typeck-field-multiply-specified-in-initializer"); pub const field_multiply_specified_in_initializer_label_previous_use: DiagnosticMessage = DiagnosticMessage::fluent_attr( "typeck-field-multiply-specified-in-initializer", "previous-use-label" ); } } ``` When emitting a diagnostic, the generated constants can be used as follows: ```ignore (rust) let mut err = sess.struct_span_err( span, fluent::typeck::field_multiply_specified_in_initializer ); err.span_default_label(span); err.span_label( previous_use_span, fluent::typeck::field_multiply_specified_in_initializer_label_previous_use ); err.emit(); ``` Signed-off-by: David Wood --- Cargo.lock | 4 + compiler/rustc_error_messages/src/lib.rs | 11 +- compiler/rustc_errors/src/lib.rs | 4 +- compiler/rustc_macros/Cargo.toml | 4 + .../rustc_macros/src/diagnostics/fluent.rs | 254 ++++++++++++++++++ compiler/rustc_macros/src/diagnostics/mod.rs | 2 + compiler/rustc_macros/src/lib.rs | 59 ++++ .../fluent-messages/duplicate-a.ftl | 1 + .../fluent-messages/duplicate-b.ftl | 1 + .../fluent-messages/missing-message.ftl | 1 + src/test/ui-fulldeps/fluent-messages/test.rs | 58 ++++ .../ui-fulldeps/fluent-messages/test.stderr | 45 ++++ .../ui-fulldeps/fluent-messages/valid.ftl | 1 + 13 files changed, 440 insertions(+), 5 deletions(-) create mode 100644 compiler/rustc_macros/src/diagnostics/fluent.rs create mode 100644 src/test/ui-fulldeps/fluent-messages/duplicate-a.ftl create mode 100644 src/test/ui-fulldeps/fluent-messages/duplicate-b.ftl create mode 100644 src/test/ui-fulldeps/fluent-messages/missing-message.ftl create mode 100644 src/test/ui-fulldeps/fluent-messages/test.rs create mode 100644 src/test/ui-fulldeps/fluent-messages/test.stderr create mode 100644 src/test/ui-fulldeps/fluent-messages/valid.ftl diff --git a/Cargo.lock b/Cargo.lock index 9fa6e1d51d2ae..25b43227d6f63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4008,10 +4008,14 @@ dependencies = [ name = "rustc_macros" version = "0.1.0" dependencies = [ + "annotate-snippets", + "fluent-bundle", + "fluent-syntax", "proc-macro2", "quote", "syn", "synstructure", + "unic-langid", ] [[package]] diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs index e1e0ed7222d55..7faf14a247241 100644 --- a/compiler/rustc_error_messages/src/lib.rs +++ b/compiler/rustc_error_messages/src/lib.rs @@ -6,7 +6,7 @@ use fluent_bundle::FluentResource; use fluent_syntax::parser::ParserError; use rustc_data_structures::sync::Lrc; -use rustc_macros::{Decodable, Encodable}; +use rustc_macros::{fluent_messages, Decodable, Encodable}; use rustc_span::Span; use std::borrow::Cow; use std::error::Error; @@ -29,8 +29,13 @@ use intl_memoizer::IntlLangMemoizer; pub use fluent_bundle::{FluentArgs, FluentError, FluentValue}; pub use unic_langid::{langid, LanguageIdentifier}; -pub static DEFAULT_LOCALE_RESOURCES: &'static [&'static str] = - &[include_str!("../locales/en-US/typeck.ftl"), include_str!("../locales/en-US/parser.ftl")]; +// Generates `DEFAULT_LOCALE_RESOURCES` static and `fluent_generated` module. +fluent_messages! { + parser => "../locales/en-US/parser.ftl", + typeck => "../locales/en-US/typeck.ftl", +} + +pub use fluent_generated::{self as fluent, DEFAULT_LOCALE_RESOURCES}; pub type FluentBundle = fluent_bundle::bundle::FluentBundle; diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index d2f50d5df5465..5b9b65da34364 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -31,8 +31,8 @@ use rustc_data_structures::stable_hasher::StableHasher; use rustc_data_structures::sync::{self, Lock, Lrc}; use rustc_data_structures::AtomicRef; pub use rustc_error_messages::{ - fallback_fluent_bundle, fluent_bundle, DiagnosticMessage, FluentBundle, LanguageIdentifier, - LazyFallbackBundle, MultiSpan, SpanLabel, DEFAULT_LOCALE_RESOURCES, + fallback_fluent_bundle, fluent, fluent_bundle, DiagnosticMessage, FluentBundle, + LanguageIdentifier, LazyFallbackBundle, MultiSpan, SpanLabel, DEFAULT_LOCALE_RESOURCES, }; pub use rustc_lint_defs::{pluralize, Applicability}; use rustc_span::source_map::SourceMap; diff --git a/compiler/rustc_macros/Cargo.toml b/compiler/rustc_macros/Cargo.toml index a9192be4d6ef4..25b3aadc1c527 100644 --- a/compiler/rustc_macros/Cargo.toml +++ b/compiler/rustc_macros/Cargo.toml @@ -7,7 +7,11 @@ edition = "2021" proc-macro = true [dependencies] +annotate-snippets = "0.8.0" +fluent-bundle = "0.15.2" +fluent-syntax = "0.11" synstructure = "0.12.1" syn = { version = "1", features = ["full"] } proc-macro2 = "1" quote = "1" +unic-langid = { version = "0.9.0", features = ["macros"] } diff --git a/compiler/rustc_macros/src/diagnostics/fluent.rs b/compiler/rustc_macros/src/diagnostics/fluent.rs new file mode 100644 index 0000000000000..8523d7fa9f988 --- /dev/null +++ b/compiler/rustc_macros/src/diagnostics/fluent.rs @@ -0,0 +1,254 @@ +use annotate_snippets::{ + display_list::DisplayList, + snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, +}; +use fluent_bundle::{FluentBundle, FluentError, FluentResource}; +use fluent_syntax::{ + ast::{Attribute, Entry, Identifier, Message}, + parser::ParserError, +}; +use proc_macro::{Diagnostic, Level, Span}; +use proc_macro2::TokenStream; +use quote::quote; +use std::{ + collections::HashMap, + fs::File, + io::Read, + path::{Path, PathBuf}, +}; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + punctuated::Punctuated, + token, Ident, LitStr, Result, +}; +use unic_langid::langid; + +struct Resource { + ident: Ident, + #[allow(dead_code)] + fat_arrow_token: token::FatArrow, + resource: LitStr, +} + +impl Parse for Resource { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Resource { + ident: input.parse()?, + fat_arrow_token: input.parse()?, + resource: input.parse()?, + }) + } +} + +struct Resources(Punctuated); + +impl Parse for Resources { + fn parse(input: ParseStream<'_>) -> Result { + let mut resources = Punctuated::new(); + loop { + if input.is_empty() || input.peek(token::Brace) { + break; + } + let value = input.parse()?; + resources.push_value(value); + if !input.peek(token::Comma) { + break; + } + let punct = input.parse()?; + resources.push_punct(punct); + } + Ok(Resources(resources)) + } +} + +/// Helper function for returning an absolute path for macro-invocation relative file paths. +/// +/// If the input is already absolute, then the input is returned. If the input is not absolute, +/// then it is appended to the directory containing the source file with this macro invocation. +fn invocation_relative_path_to_absolute(span: Span, path: &str) -> PathBuf { + let path = Path::new(path); + if path.is_absolute() { + path.to_path_buf() + } else { + // `/a/b/c/foo/bar.rs` contains the current macro invocation + let mut source_file_path = span.source_file().path(); + // `/a/b/c/foo/` + source_file_path.pop(); + // `/a/b/c/foo/../locales/en-US/example.ftl` + source_file_path.push(path); + source_file_path + } +} + +/// See [rustc_macros::fluent_messages]. +pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let resources = parse_macro_input!(input as Resources); + + // Cannot iterate over individual messages in a bundle, so do that using the + // `FluentResource` instead. Construct a bundle anyway to find out if there are conflicting + // messages in the resources. + let mut bundle = FluentBundle::new(vec![langid!("en-US")]); + + // Map of Fluent identifiers to the `Span` of the resource that defined them, used for better + // diagnostics. + let mut previous_defns = HashMap::new(); + + let mut includes = TokenStream::new(); + let mut generated = TokenStream::new(); + for res in resources.0 { + let ident_span = res.ident.span().unwrap(); + let path_span = res.resource.span().unwrap(); + + let relative_ftl_path = res.resource.value(); + let absolute_ftl_path = + invocation_relative_path_to_absolute(ident_span, &relative_ftl_path); + // As this macro also outputs an `include_str!` for this file, the macro will always be + // re-executed when the file changes. + let mut resource_file = match File::open(absolute_ftl_path) { + Ok(resource_file) => resource_file, + Err(e) => { + Diagnostic::spanned(path_span, Level::Error, "could not open Fluent resource") + .note(e.to_string()) + .emit(); + continue; + } + }; + let mut resource_contents = String::new(); + if let Err(e) = resource_file.read_to_string(&mut resource_contents) { + Diagnostic::spanned(path_span, Level::Error, "could not read Fluent resource") + .note(e.to_string()) + .emit(); + continue; + } + let resource = match FluentResource::try_new(resource_contents) { + Ok(resource) => resource, + Err((this, errs)) => { + Diagnostic::spanned(path_span, Level::Error, "could not parse Fluent resource") + .help("see additional errors emitted") + .emit(); + for ParserError { pos, slice: _, kind } in errs { + let mut err = kind.to_string(); + // Entirely unnecessary string modification so that the error message starts + // with a lowercase as rustc errors do. + err.replace_range( + 0..1, + &err.chars().next().unwrap().to_lowercase().to_string(), + ); + + let line_starts: Vec = std::iter::once(0) + .chain( + this.source() + .char_indices() + .filter_map(|(i, c)| Some(i + 1).filter(|_| c == '\n')), + ) + .collect(); + let line_start = line_starts + .iter() + .enumerate() + .map(|(line, idx)| (line + 1, idx)) + .filter(|(_, idx)| **idx <= pos.start) + .last() + .unwrap() + .0; + + let snippet = Snippet { + title: Some(Annotation { + label: Some(&err), + id: None, + annotation_type: AnnotationType::Error, + }), + footer: vec![], + slices: vec![Slice { + source: this.source(), + line_start, + origin: Some(&relative_ftl_path), + fold: true, + annotations: vec![SourceAnnotation { + label: "", + annotation_type: AnnotationType::Error, + range: (pos.start, pos.end - 1), + }], + }], + opt: Default::default(), + }; + let dl = DisplayList::from(snippet); + eprintln!("{}\n", dl); + } + continue; + } + }; + + let mut constants = TokenStream::new(); + for entry in resource.entries() { + let span = res.ident.span(); + if let Entry::Message(Message { id: Identifier { name }, attributes, .. }) = entry { + let _ = previous_defns.entry(name.to_string()).or_insert(ident_span); + + // `typeck-foo-bar` => `foo_bar` + let snake_name = Ident::new( + &name.replace(&format!("{}-", res.ident), "").replace("-", "_"), + span, + ); + constants.extend(quote! { + pub const #snake_name: crate::DiagnosticMessage = + crate::DiagnosticMessage::FluentIdentifier( + std::borrow::Cow::Borrowed(#name), + None + ); + }); + + for Attribute { id: Identifier { name: attr_name }, .. } in attributes { + let attr_snake_name = attr_name.replace("-", "_"); + let snake_name = Ident::new(&format!("{snake_name}_{attr_snake_name}"), span); + constants.extend(quote! { + pub const #snake_name: crate::DiagnosticMessage = + crate::DiagnosticMessage::FluentIdentifier( + std::borrow::Cow::Borrowed(#name), + Some(std::borrow::Cow::Borrowed(#attr_name)) + ); + }); + } + } + } + + if let Err(errs) = bundle.add_resource(resource) { + for e in errs { + match e { + FluentError::Overriding { kind, id } => { + Diagnostic::spanned( + ident_span, + Level::Error, + format!("overrides existing {}: `{}`", kind, id), + ) + .span_help(previous_defns[&id], "previously defined in this resource") + .emit(); + } + FluentError::ResolverError(_) | FluentError::ParserError(_) => unreachable!(), + } + } + } + + includes.extend(quote! { include_str!(#relative_ftl_path), }); + + let ident = res.ident; + generated.extend(quote! { + pub mod #ident { + #constants + } + }); + } + + quote! { + #[allow(non_upper_case_globals)] + #[doc(hidden)] + pub mod fluent_generated { + pub static DEFAULT_LOCALE_RESOURCES: &'static [&'static str] = &[ + #includes + ]; + + #generated + } + } + .into() +} diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs index ccd3880057b67..69573d904d4a9 100644 --- a/compiler/rustc_macros/src/diagnostics/mod.rs +++ b/compiler/rustc_macros/src/diagnostics/mod.rs @@ -1,9 +1,11 @@ mod diagnostic; mod error; +mod fluent; mod subdiagnostic; mod utils; use diagnostic::SessionDiagnosticDerive; +pub(crate) use fluent::fluent_messages; use proc_macro2::TokenStream; use quote::format_ident; use subdiagnostic::SessionSubdiagnosticDerive; diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index 0baebdb713062..7c8e3c6d14024 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -2,6 +2,7 @@ #![feature(let_else)] #![feature(never_type)] #![feature(proc_macro_diagnostic)] +#![feature(proc_macro_span)] #![allow(rustc::default_hash_types)] #![recursion_limit = "128"] @@ -49,6 +50,64 @@ pub fn newtype_index(input: TokenStream) -> TokenStream { newtype::newtype(input) } +/// Implements the `fluent_messages` macro, which performs compile-time validation of the +/// compiler's Fluent resources (i.e. that the resources parse and don't multiply define the same +/// messages) and generates constants that make using those messages in diagnostics more ergonomic. +/// +/// For example, given the following invocation of the macro.. +/// +/// ```ignore (rust) +/// fluent_messages! { +/// typeck => "./typeck.ftl", +/// } +/// ``` +/// ..where `typeck.ftl` has the following contents.. +/// +/// ```fluent +/// typeck-field-multiply-specified-in-initializer = +/// field `{$ident}` specified more than once +/// .label = used more than once +/// .label-previous-use = first use of `{$ident}` +/// ``` +/// ...then the macro parse the Fluent resource, emitting a diagnostic if it fails to do so, and +/// will generate the following code: +/// +/// ```ignore (rust) +/// pub static DEFAULT_LOCALE_RESOURCES: &'static [&'static str] = &[ +/// include_str!("./typeck.ftl"), +/// ]; +/// +/// mod fluent_generated { +/// mod typeck { +/// pub const field_multiply_specified_in_initializer: DiagnosticMessage = +/// DiagnosticMessage::fluent("typeck-field-multiply-specified-in-initializer"); +/// pub const field_multiply_specified_in_initializer_label_previous_use: DiagnosticMessage = +/// DiagnosticMessage::fluent_attr( +/// "typeck-field-multiply-specified-in-initializer", +/// "previous-use-label" +/// ); +/// } +/// } +/// ``` +/// When emitting a diagnostic, the generated constants can be used as follows: +/// +/// ```ignore (rust) +/// let mut err = sess.struct_span_err( +/// span, +/// fluent::typeck::field_multiply_specified_in_initializer +/// ); +/// err.span_default_label(span); +/// err.span_label( +/// previous_use_span, +/// fluent::typeck::field_multiply_specified_in_initializer_label_previous_use +/// ); +/// err.emit(); +/// ``` +#[proc_macro] +pub fn fluent_messages(input: TokenStream) -> TokenStream { + diagnostics::fluent_messages(input) +} + decl_derive!([HashStable, attributes(stable_hasher)] => hash_stable::hash_stable_derive); decl_derive!( [HashStable_Generic, attributes(stable_hasher)] => diff --git a/src/test/ui-fulldeps/fluent-messages/duplicate-a.ftl b/src/test/ui-fulldeps/fluent-messages/duplicate-a.ftl new file mode 100644 index 0000000000000..fd9976b5a4146 --- /dev/null +++ b/src/test/ui-fulldeps/fluent-messages/duplicate-a.ftl @@ -0,0 +1 @@ +key = Value diff --git a/src/test/ui-fulldeps/fluent-messages/duplicate-b.ftl b/src/test/ui-fulldeps/fluent-messages/duplicate-b.ftl new file mode 100644 index 0000000000000..fd9976b5a4146 --- /dev/null +++ b/src/test/ui-fulldeps/fluent-messages/duplicate-b.ftl @@ -0,0 +1 @@ +key = Value diff --git a/src/test/ui-fulldeps/fluent-messages/missing-message.ftl b/src/test/ui-fulldeps/fluent-messages/missing-message.ftl new file mode 100644 index 0000000000000..372b1a2e453d2 --- /dev/null +++ b/src/test/ui-fulldeps/fluent-messages/missing-message.ftl @@ -0,0 +1 @@ +missing-message = diff --git a/src/test/ui-fulldeps/fluent-messages/test.rs b/src/test/ui-fulldeps/fluent-messages/test.rs new file mode 100644 index 0000000000000..612995e28af21 --- /dev/null +++ b/src/test/ui-fulldeps/fluent-messages/test.rs @@ -0,0 +1,58 @@ +#![feature(rustc_private)] +#![crate_type = "lib"] + +extern crate rustc_macros; +use rustc_macros::fluent_messages; + +/// Copy of the relevant `DiagnosticMessage` variant constructed by `fluent_messages` as it +/// expects `crate::DiagnosticMessage` to exist. +pub enum DiagnosticMessage { + FluentIdentifier(std::borrow::Cow<'static, str>, Option>), +} + +mod missing_absolute { + use super::fluent_messages; + + fluent_messages! { + missing_absolute => "/definitely_does_not_exist.ftl", +//~^ ERROR could not open Fluent resource + } +} + +mod missing_relative { + use super::fluent_messages; + + fluent_messages! { + missing_relative => "../definitely_does_not_exist.ftl", +//~^ ERROR could not open Fluent resource + } +} + +mod missing_message { + use super::fluent_messages; + + fluent_messages! { + missing_message => "./missing-message.ftl", +//~^ ERROR could not parse Fluent resource + } +} + +mod duplicate { + use super::fluent_messages; + + fluent_messages! { + a => "./duplicate-a.ftl", + b => "./duplicate-b.ftl", +//~^ ERROR overrides existing message: `key` + } +} + +mod valid { + use super::fluent_messages; + + fluent_messages! { + valid => "./valid.ftl", + } + + use self::fluent_generated::{DEFAULT_LOCALE_RESOURCES, valid::valid}; +} diff --git a/src/test/ui-fulldeps/fluent-messages/test.stderr b/src/test/ui-fulldeps/fluent-messages/test.stderr new file mode 100644 index 0000000000000..eddc29984ea69 --- /dev/null +++ b/src/test/ui-fulldeps/fluent-messages/test.stderr @@ -0,0 +1,45 @@ +error: could not open Fluent resource + --> $DIR/test.rs:17:29 + | +LL | missing_absolute => "/definitely_does_not_exist.ftl", + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: No such file or directory (os error 2) + +error: could not open Fluent resource + --> $DIR/test.rs:26:29 + | +LL | missing_relative => "../definitely_does_not_exist.ftl", + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: No such file or directory (os error 2) + +error: could not parse Fluent resource + --> $DIR/test.rs:35:28 + | +LL | missing_message => "./missing-message.ftl", + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: see additional errors emitted + +error: expected a message field for "missing-message" + --> ./missing-message.ftl:1:1 + | +1 | missing-message = + | ^^^^^^^^^^^^^^^^^^ + | + +error: overrides existing message: `key` + --> $DIR/test.rs:45:9 + | +LL | b => "./duplicate-b.ftl", + | ^ + | +help: previously defined in this resource + --> $DIR/test.rs:44:9 + | +LL | a => "./duplicate-a.ftl", + | ^ + +error: aborting due to 4 previous errors + diff --git a/src/test/ui-fulldeps/fluent-messages/valid.ftl b/src/test/ui-fulldeps/fluent-messages/valid.ftl new file mode 100644 index 0000000000000..0eee4a02b96ae --- /dev/null +++ b/src/test/ui-fulldeps/fluent-messages/valid.ftl @@ -0,0 +1 @@ +valid = Valid! From d7d5a0b7ff11f4d2cb859c3113d91569375c8a5d Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 23 May 2022 18:37:27 +0100 Subject: [PATCH 6/7] typeck: use typed fluent identifiers for diags Use new typed Fluent identifiers for the "missing type parameters" diagnostic in the typeck crate which was manually creating `DiagnosticMessage`s previously. Signed-off-by: David Wood --- compiler/rustc_typeck/src/errors.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_typeck/src/errors.rs b/compiler/rustc_typeck/src/errors.rs index cd3813ca4f5b5..d9c9f2920b079 100644 --- a/compiler/rustc_typeck/src/errors.rs +++ b/compiler/rustc_typeck/src/errors.rs @@ -1,7 +1,5 @@ //! Errors emitted by typeck. -use rustc_errors::{ - error_code, Applicability, DiagnosticBuilder, DiagnosticMessage, ErrorGuaranteed, -}; +use rustc_errors::{error_code, Applicability, DiagnosticBuilder, ErrorGuaranteed}; use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic}; use rustc_middle::ty::Ty; use rustc_session::{parse::ParseSess, SessionDiagnostic}; @@ -264,10 +262,9 @@ pub struct MissingTypeParams { // Manual implementation of `SessionDiagnostic` to be able to call `span_to_snippet`. impl<'a> SessionDiagnostic<'a> for MissingTypeParams { fn into_diagnostic(self, sess: &'a ParseSess) -> DiagnosticBuilder<'a, ErrorGuaranteed> { - static SLUG: &'static str = "typeck-missing-type-params"; let mut err = sess.span_diagnostic.struct_span_err_with_code( self.span, - DiagnosticMessage::fluent(SLUG), + rustc_errors::fluent::typeck::missing_type_params, error_code!(E0393), ); err.set_arg("parameterCount", self.missing_type_params.len()); @@ -280,7 +277,7 @@ impl<'a> SessionDiagnostic<'a> for MissingTypeParams { .join(", "), ); - err.span_label(self.def_span, DiagnosticMessage::fluent_attr(SLUG, "label")); + err.span_label(self.def_span, rustc_errors::fluent::typeck::missing_type_params_label); let mut suggested = false; if let (Ok(snippet), true) = ( @@ -298,7 +295,7 @@ impl<'a> SessionDiagnostic<'a> for MissingTypeParams { // least we can clue them to the correct syntax `Iterator`. err.span_suggestion( self.span, - DiagnosticMessage::fluent_attr(SLUG, "suggestion"), + rustc_errors::fluent::typeck::missing_type_params_suggestion, format!("{}<{}>", snippet, self.missing_type_params.join(", ")), Applicability::HasPlaceholders, ); @@ -306,10 +303,13 @@ impl<'a> SessionDiagnostic<'a> for MissingTypeParams { } } if !suggested { - err.span_label(self.span, DiagnosticMessage::fluent_attr(SLUG, "no-suggestion-label")); + err.span_label( + self.span, + rustc_errors::fluent::typeck::missing_type_params_no_suggestion_label, + ); } - err.note(DiagnosticMessage::fluent_attr(SLUG, "note")); + err.note(rustc_errors::fluent::typeck::missing_type_params_note); err } } From 9be37b2d3fc0b3852a7f9b134197dec895030201 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sun, 22 May 2022 16:06:36 -0700 Subject: [PATCH 7/7] Parse expression after `else` as a condition if followed by `{` --- compiler/rustc_parse/src/parser/expr.rs | 62 +++++++++++++++++++++++-- src/test/ui/parser/else-no-if.rs | 32 +++++++++++++ src/test/ui/parser/else-no-if.stderr | 58 +++++++++++++++++++++++ 3 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 src/test/ui/parser/else-no-if.rs create mode 100644 src/test/ui/parser/else-no-if.stderr diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index b5467c659a205..37e3465694131 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -2010,6 +2010,12 @@ impl<'a> Parser<'a> { Ok(self.mk_expr(blk.span, ExprKind::Block(blk, opt_label), attrs)) } + /// Parse a block which takes no attributes and has no label + fn parse_simple_block(&mut self) -> PResult<'a, P> { + let blk = self.parse_block()?; + Ok(self.mk_expr(blk.span, ExprKind::Block(blk, None), AttrVec::new())) + } + /// Recover on an explicitly quantified closure expression, e.g., `for<'a> |x: &'a u8| *x + 1`. fn recover_quantified_closure_expr(&mut self, attrs: AttrVec) -> PResult<'a, P> { let lo = self.token.span; @@ -2157,6 +2163,15 @@ impl<'a> Parser<'a> { let lo = self.prev_token.span; let cond = self.parse_cond_expr()?; + self.parse_if_after_cond(attrs, lo, cond) + } + + fn parse_if_after_cond( + &mut self, + attrs: AttrVec, + lo: Span, + cond: P, + ) -> PResult<'a, P> { let missing_then_block_binop_span = || { match cond.kind { ExprKind::Binary(Spanned { span: binop_span, .. }, _, ref right) @@ -2164,7 +2179,6 @@ impl<'a> Parser<'a> { _ => None } }; - // Verify that the parsed `if` condition makes sense as a condition. If it is a block, then // verify that the last statement is either an implicit return (no `;`) or an explicit // return. This won't catch blocks with an explicit `return`, but that would be caught by @@ -2256,15 +2270,53 @@ impl<'a> Parser<'a> { /// Parses an `else { ... }` expression (`else` token already eaten). fn parse_else_expr(&mut self) -> PResult<'a, P> { - let ctx_span = self.prev_token.span; // `else` + let else_span = self.prev_token.span; // `else` let attrs = self.parse_outer_attributes()?.take_for_recovery(); // For recovery. let expr = if self.eat_keyword(kw::If) { self.parse_if_expr(AttrVec::new())? + } else if self.check(&TokenKind::OpenDelim(Delimiter::Brace)) { + self.parse_simple_block()? } else { - let blk = self.parse_block()?; - self.mk_expr(blk.span, ExprKind::Block(blk, None), AttrVec::new()) + let snapshot = self.create_snapshot_for_diagnostic(); + let first_tok = super::token_descr(&self.token); + let first_tok_span = self.token.span; + match self.parse_expr() { + Ok(cond) + // If it's not a free-standing expression, and is followed by a block, + // then it's very likely the condition to an `else if`. + if self.check(&TokenKind::OpenDelim(Delimiter::Brace)) + && classify::expr_requires_semi_to_be_stmt(&cond) => + { + self.struct_span_err(first_tok_span, format!("expected `{{`, found {first_tok}")) + .span_label(else_span, "expected an `if` or a block after this `else`") + .span_suggestion( + cond.span.shrink_to_lo(), + "add an `if` if this is the condition to an chained `if` statement after the `else`", + "if ".to_string(), + Applicability::MaybeIncorrect, + ).multipart_suggestion( + "... otherwise, place this expression inside of a block if it is not an `if` condition", + vec![ + (cond.span.shrink_to_lo(), "{ ".to_string()), + (cond.span.shrink_to_hi(), " }".to_string()), + ], + Applicability::MaybeIncorrect, + ) + .emit(); + self.parse_if_after_cond(AttrVec::new(), cond.span.shrink_to_lo(), cond)? + } + Err(e) => { + e.cancel(); + self.restore_snapshot(snapshot); + self.parse_simple_block()? + }, + Ok(_) => { + self.restore_snapshot(snapshot); + self.parse_simple_block()? + }, + } }; - self.error_on_if_block_attrs(ctx_span, true, expr.span, &attrs); + self.error_on_if_block_attrs(else_span, true, expr.span, &attrs); Ok(expr) } diff --git a/src/test/ui/parser/else-no-if.rs b/src/test/ui/parser/else-no-if.rs new file mode 100644 index 0000000000000..f0b40ecde6660 --- /dev/null +++ b/src/test/ui/parser/else-no-if.rs @@ -0,0 +1,32 @@ +fn foo() { + if true { + } else false { + //~^ ERROR expected `{`, found keyword `false` + } +} + +fn foo2() { + if true { + } else falsy() { + //~^ ERROR expected `{`, found `falsy` + } +} + +fn foo3() { + if true { + } else falsy(); + //~^ ERROR expected `{`, found `falsy` +} + +fn foo4() { + if true { + } else loop{} + //~^ ERROR expected `{`, found keyword `loop` + {} +} + +fn falsy() -> bool { + false +} + +fn main() {} diff --git a/src/test/ui/parser/else-no-if.stderr b/src/test/ui/parser/else-no-if.stderr new file mode 100644 index 0000000000000..27abbadd7ad24 --- /dev/null +++ b/src/test/ui/parser/else-no-if.stderr @@ -0,0 +1,58 @@ +error: expected `{`, found keyword `false` + --> $DIR/else-no-if.rs:3:12 + | +LL | } else false { + | ---- ^^^^^ + | | + | expected an `if` or a block after this `else` + | +help: add an `if` if this is the condition to an chained `if` statement after the `else` + | +LL | } else if false { + | ++ +help: ... otherwise, place this expression inside of a block if it is not an `if` condition + | +LL | } else { false } { + | + + + +error: expected `{`, found `falsy` + --> $DIR/else-no-if.rs:10:12 + | +LL | } else falsy() { + | ---- ^^^^^ + | | + | expected an `if` or a block after this `else` + | +help: add an `if` if this is the condition to an chained `if` statement after the `else` + | +LL | } else if falsy() { + | ++ +help: ... otherwise, place this expression inside of a block if it is not an `if` condition + | +LL | } else { falsy() } { + | + + + +error: expected `{`, found `falsy` + --> $DIR/else-no-if.rs:17:12 + | +LL | } else falsy(); + | ^^^^^ expected `{` + | +help: try placing this code inside a block + | +LL | } else { falsy() }; + | + + + +error: expected `{`, found keyword `loop` + --> $DIR/else-no-if.rs:23:12 + | +LL | } else loop{} + | ^^^^ expected `{` + | +help: try placing this code inside a block + | +LL | } else { loop{} } + | + + + +error: aborting due to 4 previous errors +