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_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/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 73ad415d6c355..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; @@ -12,7 +14,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 +45,7 @@ use synstructure::Structure; /// /// Then, later, to emit the error: /// -/// ```ignore (pseudo-rust) +/// ```ignore (rust) /// sess.emit_err(MoveOutOfBorrowError { /// expected, /// actual, @@ -67,7 +69,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 +106,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 }); 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/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/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 } } 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 631cc313fa092..688ab63bf1366 100644 --- a/library/core/src/primitive_docs.rs +++ b/library/core/src/primitive_docs.rs @@ -1351,6 +1351,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: diff --git a/library/std/src/primitive_docs.rs b/library/std/src/primitive_docs.rs index 631cc313fa092..688ab63bf1366 100644 --- a/library/std/src/primitive_docs.rs +++ b/library/std/src/primitive_docs.rs @@ -1351,6 +1351,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: diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index d820e43cb6123..a82abe6692655 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 4ddfae4daccf8..4068531f0108b 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(), @@ -1788,14 +1796,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 => { @@ -1813,8 +1821,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)), @@ -1823,14 +1831,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 = @@ -1858,14 +1866,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 = @@ -1878,9 +1886,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; @@ -1961,8 +1969,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); @@ -1971,7 +1979,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)); @@ -2009,11 +2021,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); @@ -2059,12 +2071,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 @@ -2172,9 +2184,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(); @@ -2205,8 +2217,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), @@ -2214,8 +2226,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 47597e0413ae1..a0014f721f46c 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 { let mut ret_val = @@ -99,12 +99,12 @@ pub(crate) fn substs_to_args( ret_val } -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); 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! 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 +