Skip to content

Commit

Permalink
Auto merge of #97476 - Dylan-DPC:rollup-t53nxoe, r=Dylan-DPC
Browse files Browse the repository at this point in the history
Rollup of 5 pull requests

Successful merges:

 - #94640 (Partially stabilize `(const_)slice_ptr_len` feature by stabilizing `NonNull::len`)
 - #97034 (Implement `Hash` for `core::alloc::Layout`)
 - #97327 (macros: introduce `fluent_messages` macro )
 - #97448 (docs: Don't imply that OsStr on Unix is always UTF-8)
 - #97466 ([bootstrap] Move `sanitize_sh` from `dist` to `install`)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
  • Loading branch information
bors committed May 28, 2022
2 parents b97bfc3 + 5badc29 commit 19abca1
Show file tree
Hide file tree
Showing 20 changed files with 494 additions and 49 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4010,10 +4010,14 @@ dependencies = [
name = "rustc_macros"
version = "0.1.0"
dependencies = [
"annotate-snippets",
"fluent-bundle",
"fluent-syntax",
"proc-macro2",
"quote",
"syn",
"synstructure",
"unic-langid",
]

[[package]]
Expand Down
11 changes: 8 additions & 3 deletions compiler/rustc_error_messages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<FluentResource, IntlLangMemoizer>;

Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
254 changes: 254 additions & 0 deletions compiler/rustc_macros/src/diagnostics/fluent.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
Ok(Resource {
ident: input.parse()?,
fat_arrow_token: input.parse()?,
resource: input.parse()?,
})
}
}

struct Resources(Punctuated<Resource, token::Comma>);

impl Parse for Resources {
fn parse(input: ParseStream<'_>) -> Result<Self> {
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<usize> = 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()
}
10 changes: 6 additions & 4 deletions compiler/rustc_macros/src/diagnostics/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -43,7 +45,7 @@ use synstructure::Structure;
///
/// Then, later, to emit the error:
///
/// ```ignore (pseudo-rust)
/// ```ignore (rust)
/// sess.emit_err(MoveOutOfBorrowError {
/// expected,
/// actual,
Expand All @@ -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")]
Expand Down Expand Up @@ -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 });
Expand Down
Loading

0 comments on commit 19abca1

Please sign in to comment.