# Split out Fluent from Diagnostic Struct ## Problems There are several issues for our diagnostic structs and fluent translation implementation: - Separating the Fluent message from the structs makes a bunch of static checks much harder - we have runtime checks for unused variables, tidy lints for a bunch of things, and some compile-time checks - we should have everything being checked at compile-time like we try to do with everything else. - This separation also makes the next steps more difficult, we want to use Pontoon to do the translations, and it doesn't handle versioning of messages. We have to manually add -1, -2 prefixes to the slugs and that's dumb. - We need some way to rely on `TyCtxt` or some other context in our structs, so you can have something vaguely like a `#[def_path_str] expr: DefId` field in a type rather than having to do that and pass a String to the types. - Disruptive dev experience in the compiler development - we need to think proper `slug` names - when searching the code where generated specific errors, we need to find from `messages.ftl`, then find the Diagnostics with the `slug`. ## Solution We can change it with something like this: ```rust #[derive(Diagnostic)] #[diag("expected a path on the left-hand side of `+`, not `{$ty}`", code = "E0178")] pub(crate) struct BadTypePlus { pub ty: String, #[primary_span] pub span: Span, #[subdiagnostic] pub sub: BadTypePlusSub, } #[derive(Subdiagnostic)] pub(crate) enum BadTypePlusSub { #[suggestion( label = "try adding parentheses", code = "{sum_with_parens}", applicability = "machine-applicable" )] AddParen { sum_with_parens: String, #[primary_span] span: Span, }, #[label("perhaps you forgot parentheses?")] ForgotParen { #[primary_span] span: Span, }, #[label("expected a path")] ExpectPath { #[primary_span] span: Span, }, } ``` instead of referring to slugs, we'd generate those from the type name and a hash of the message contents, i.e. `signature-redeclaration-8af429de` or something. Because of the message being present here, there are no unused Fluent messages or else there would be unused types; and we can check the fields match the interpolations at compile-time. Same thing would apply for all the subdiagnostic kinds, and the macros would largely work the same as they do otherwise. We'd get rid of needing to grep into ftl files and then search for the slug too. We wouldn't be able to have messages include other slugs, but we barely do that anyway. Our macro should create a public static with the original Fluent message, ideally in a specific section of the binary. We can strip it out in the build system after we've dumped the contents of every static in that section to disk and that's the equivalent of our ftl files now that we'd give to the translation tool. We still need a solution for the fluent::message_slug_here usages, and I'd probably suggest a macro like `_!("fluent {$message}")` that we can translate into an anonymous struct with the derive on it, if that's possible. I've not thought much about accessing TyCtxt or whatever from these yet, that can happen after. The mainly difference with previous usage is: - when we use `#[diag(slug-path)]`, we change it to `#[diag(text = "this is the content")]` - when we use something like `#[suggestion(slug-path, ...)]`, we change it to `#[suggestion(label = "this is the raw content", ...)]` ## Current Plan - Make sure the new format works as before - Write scripts to migrate crates to new format - Cleanup `slug` related code in `compiler/rustc_macros/` ## Further work - How to emit fluent resources from new format which contains raw message in structs - Using `#[link_section]` with that to put them all in a specific section, and then just grabbing that from the final output binary and stripping it out after that - Maybe we can use [scemars](https://github.com/GREsau/schemars) - How to distribute locale files with, maybe a `locales` directory in the sysroot, which will be populated by rustup when it downloads translation packs, these will contain ftl files for different languages, and the compiler will load those when it needs to emit a diagnostic in that language. - How to emit a diagnostic from `locales`