Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rollup of 5 pull requests #97476

Merged
merged 13 commits into from
May 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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