Squashed 'third_party/rules_rust/' content from commit bf59038ca

git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/util/import/import_internal.rs b/util/import/import_internal.rs
new file mode 100644
index 0000000..1d44fe6
--- /dev/null
+++ b/util/import/import_internal.rs
@@ -0,0 +1,554 @@
+use std::iter;
+use std::vec;
+
+use aho_corasick::AhoCorasick;
+use lazy_static::lazy_static;
+use proc_macro2::{Span, TokenStream};
+use quote::quote_spanned;
+use syn::parse::{Parse, ParseStream};
+use syn::{Error, Ident, Lit, LitStr, Result, Token};
+
+/// The possible renaming modes for this macro.
+pub enum Mode {
+    /// No renaming will be done; the expansion will replace each label with
+    /// just the target.
+    NoRenaming,
+    /// First-party crates will be renamed, and third-party crates will not be.
+    /// The expansion will replace first-party labels with an encoded version,
+    /// and third-party labels with just their target.
+    RenameFirstPartyCrates { third_party_dir: String },
+}
+
+/// A special case of label::Label, which must be absolute and must not specify
+/// a repository.
+#[derive(Debug, PartialEq)]
+struct AbsoluteLabel<'s> {
+    package_name: &'s str,
+    name: &'s str,
+}
+
+impl<'s> AbsoluteLabel<'s> {
+    /// Parses a string as an absolute Bazel label. Labels must be for the
+    /// current repository.
+    fn parse(label: &'s str, span: &'s Span) -> Result<Self> {
+        if let Ok(label::Label {
+            repository_name: None,
+            package_name: Some(package_name),
+            name,
+        }) = label::analyze(label)
+        {
+            Ok(AbsoluteLabel { package_name, name })
+        } else {
+            Err(Error::new(
+                *span,
+                "Bazel labels must be of the form '//package[:target]'",
+            ))
+        }
+    }
+
+    /// Returns true iff this label should be renamed.
+    fn should_rename(&self, mode: &Mode) -> bool {
+        match mode {
+            Mode::NoRenaming => false,
+            Mode::RenameFirstPartyCrates { third_party_dir } => {
+                !self.package_name.starts_with(third_party_dir)
+            }
+        }
+    }
+
+    /// Returns the appropriate (encoded) alias to use, if this label is being
+    /// renamed; otherwise, returns None.
+    fn target_as_alias(&self, mode: &Mode) -> Option<String> {
+        self.should_rename(mode).then(|| encode(self.name))
+    }
+
+    /// Returns the full crate name, encoded if necessary.
+    fn crate_name(&self, mode: &Mode) -> String {
+        if self.should_rename(mode) {
+            encode(&format!("{}:{}", self.package_name, self.name))
+        } else {
+            self.name.to_string()
+        }
+    }
+}
+
+lazy_static! {
+    // Transformations are stored as "(unencoded, encoded)" tuples.
+    // Target names can include:
+    // !%-@^_` "#$&'()*-+,;<=>?[]{|}~/.
+    //
+    // Package names are alphanumeric, plus [_/-].
+    //
+    // Packages and targets are separated by colons.
+    static ref SUBSTITUTIONS: (Vec<String>, Vec<String>) =
+        iter::once(("_quote".to_string(), "_quotequote_".to_string()))
+        .chain(
+            vec![
+                    (":", "colon"),
+                    ("!", "bang"),
+                    ("%", "percent"),
+                    ("@", "at"),
+                    ("^", "caret"),
+                    ("`", "backtick"),
+                    (" ", "space"),
+                    ("\"", "quote"),
+                    ("#", "hash"),
+                    ("$", "dollar"),
+                    ("&", "ampersand"),
+                    ("'", "backslash"),
+                    ("(", "lparen"),
+                    (")", "rparen"),
+                    ("*", "star"),
+                    ("-", "dash"),
+                    ("+", "plus"),
+                    (",", "comma"),
+                    (";", "semicolon"),
+                    ("<", "langle"),
+                    ("=", "equal"),
+                    (">", "rangle"),
+                    ("?", "question"),
+                    ("[", "lbracket"),
+                    ("]", "rbracket"),
+                    ("{", "lbrace"),
+                    ("|", "pipe"),
+                    ("}", "rbrace"),
+                    ("~", "tilde"),
+                    ("/", "slash"),
+                    (".", "dot"),
+            ].into_iter()
+            .flat_map(|pair| {
+                vec![
+                    (format!("_{}_", &pair.1), format!("_quote{}_", &pair.1)),
+                    (pair.0.to_string(), format!("_{}_", &pair.1)),
+                ].into_iter()
+            })
+        )
+        .unzip();
+
+    static ref ENCODER: AhoCorasick = AhoCorasick::new(&SUBSTITUTIONS.0);
+    static ref DECODER: AhoCorasick = AhoCorasick::new(&SUBSTITUTIONS.1);
+}
+
+/// Encodes a string using the above encoding scheme.
+fn encode(s: &str) -> String {
+    ENCODER.replace_all(s, &SUBSTITUTIONS.1)
+}
+
+struct Import {
+    label: LitStr,
+    alias: Option<Ident>,
+}
+
+impl Import {
+    fn try_into_statement(self, mode: &Mode) -> Result<proc_macro2::TokenStream> {
+        let label_literal = &self.label.value();
+        let span = self.label.span();
+        let label = AbsoluteLabel::parse(label_literal, &span)?;
+        let crate_name = &label.crate_name(mode);
+
+        let crate_ident = Ident::new(crate_name, span);
+        let alias = self
+            .alias
+            .or_else(|| {
+                label
+                    .target_as_alias(mode)
+                    .map(|alias| Ident::new(&alias, span))
+            })
+            .filter(|alias| alias != crate_name);
+
+        Ok(if let Some(alias) = alias {
+            quote_spanned! {span=> extern crate #crate_ident as #alias; }
+        } else {
+            quote_spanned! {span=> extern crate #crate_ident;}
+        })
+    }
+}
+
+pub struct ImportMacroInput {
+    imports: Vec<Import>,
+}
+
+impl Parse for ImportMacroInput {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let mut imports: Vec<Import> = Vec::new();
+
+        while !input.is_empty() {
+            let label = match Lit::parse(input)
+                .map_err(|_| input.error("expected Bazel label as a string literal"))?
+            {
+                Lit::Str(label) => label,
+                lit => {
+                    return Err(input.error(format!(
+                        "expected Bazel label as string literal, found '{}' literal",
+                        quote::quote! {#lit}
+                    )));
+                }
+            };
+            let alias = if input.peek(Token![as]) {
+                <Token![as]>::parse(input)?;
+                Some(
+                    Ident::parse(input)
+                        .map_err(|_| input.error("alias must be a valid Rust identifier"))?,
+                )
+            } else {
+                None
+            };
+            imports.push(Import { label, alias });
+            <syn::Token![;]>::parse(input)?;
+        }
+
+        Ok(Self { imports })
+    }
+}
+
+pub fn expand_imports(
+    input: ImportMacroInput,
+    mode: &Mode,
+) -> std::result::Result<TokenStream, Vec<syn::Error>> {
+    let (statements, errs): (Vec<_>, Vec<_>) = input
+        .imports
+        .into_iter()
+        .map(|i| i.try_into_statement(mode))
+        .partition(Result::is_ok);
+
+    if !errs.is_empty() {
+        Err(errs.into_iter().map(Result::unwrap_err).collect())
+    } else {
+        Ok(statements.into_iter().map(Result::unwrap).collect())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::*;
+    use quickcheck::quickcheck;
+    use syn::parse_quote;
+
+    /// Decodes a string that was encoded using `encode`.
+    fn decode(s: &str) -> String {
+        DECODER.replace_all(s, &SUBSTITUTIONS.0)
+    }
+
+    #[test]
+    fn test_expand_imports_without_renaming() -> std::result::Result<(), Vec<syn::Error>> {
+        let mode = Mode::NoRenaming;
+
+        // Nothing to do.
+        let expanded = expand_imports(parse_quote! {}, &mode)?;
+        assert_eq!(expanded.to_string(), "");
+
+        // Package and a target.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils"; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate utils ;");
+
+        // Package and a target, with a no-op alias.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils"; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate utils ;");
+
+        // Package and a target, with an alias.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils" as my_utils; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate utils as my_utils ;");
+
+        // Package and an implicit target.
+        let expanded = expand_imports(parse_quote! { "//some_project/utils"; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate utils ;");
+
+        // Package and an implicit target, with a no-op alias.
+        let expanded = expand_imports(parse_quote! { "//some_project/utils" as utils; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate utils ;");
+
+        // Package and an implicit target, with an alias.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils" as my_utils; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate utils as my_utils ;");
+
+        // A third-party target.
+        let expanded =
+            expand_imports(parse_quote! { "//third_party/rust/serde/v1:serde"; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate serde ;");
+
+        // A third-party target with a no-op alias.
+        let expanded = expand_imports(
+            parse_quote! { "//third_party/rust/serde/v1:serde" as serde; },
+            &mode,
+        )?;
+        assert_eq!(expanded.to_string(), "extern crate serde ;");
+
+        // A third-party target with an alias.
+        let expanded = expand_imports(
+            parse_quote! { "//third_party/rust/serde/v1:serde" as my_serde; },
+            &mode,
+        )?;
+        assert_eq!(expanded.to_string(), "extern crate serde as my_serde ;");
+
+        // Multiple targets.
+        let expanded = expand_imports(
+            parse_quote! { "//some_project:utils"; "//third_party/rust/serde/v1:serde"; },
+            &mode,
+        )?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate utils ; extern crate serde ;"
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_expand_imports_with_renaming() -> std::result::Result<(), Vec<syn::Error>> {
+        let mode = Mode::RenameFirstPartyCrates {
+            third_party_dir: "third_party/rust".to_string(),
+        };
+
+        // Nothing to do.
+        let expanded = expand_imports(parse_quote! {}, &mode)?;
+        assert_eq!(expanded.to_string(), "");
+
+        // Package and a target.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils"; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_colon_utils as utils ;"
+        );
+
+        // Package and a target, with a no-op alias.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils" as utils; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_colon_utils as utils ;"
+        );
+
+        // Package and a target, with an alias.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils" as my_utils; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_colon_utils as my_utils ;"
+        );
+
+        // Package and an implicit target.
+        let expanded = expand_imports(parse_quote! { "//some_project/utils"; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_slash_utils_colon_utils as utils ;"
+        );
+
+        // Package and an implicit target, with a no-op alias.
+        let expanded = expand_imports(parse_quote! { "//some_project/utils" as utils; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_slash_utils_colon_utils as utils ;"
+        );
+
+        // Package and an implicit target, with an alias.
+        let expanded = expand_imports(parse_quote! { "//some_project/utils" as my_utils; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_slash_utils_colon_utils as my_utils ;"
+        );
+
+        // A third-party target.
+        let expanded =
+            expand_imports(parse_quote! { "//third_party/rust/serde/v1:serde"; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate serde ;");
+
+        // A third-party target with a no-op alias.
+        let expanded = expand_imports(
+            parse_quote! { "//third_party/rust/serde/v1:serde" as serde; },
+            &mode,
+        )?;
+        assert_eq!(expanded.to_string(), "extern crate serde ;");
+
+        // A third-party target with an alias.
+        let expanded = expand_imports(
+            parse_quote! { "//third_party/rust/serde/v1:serde" as my_serde; },
+            &mode,
+        )?;
+        assert_eq!(expanded.to_string(), "extern crate serde as my_serde ;");
+
+        // Multiple targets.
+        let expanded = expand_imports(
+            parse_quote! { "//some_project:utils"; "//third_party/rust/serde/v1:serde"; },
+            &mode,
+        )?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_colon_utils as utils ; extern crate serde ;"
+        );
+
+        // Problematic target name.
+        let expanded = expand_imports(parse_quote! { "//some_project:thing-types"; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_colon_thing_dash_types as thing_dash_types ;"
+        );
+
+        // Problematic target name with alias.
+        let expanded = expand_imports(
+            parse_quote! { "//some_project:thing-types" as types; },
+            &mode,
+        )?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_colon_thing_dash_types as types ;"
+        );
+
+        // Problematic package name.
+        let expanded = expand_imports(parse_quote! { "//some_project-prototype:utils"; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_dash_prototype_colon_utils as utils ;"
+        );
+
+        // Problematic package and target names.
+        let expanded = expand_imports(
+            parse_quote! { "//some_project-prototype:thing-types"; },
+            &mode,
+        )?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_dash_prototype_colon_thing_dash_types as thing_dash_types ;"
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_expansion_failures() -> Result<()> {
+        let mode = Mode::NoRenaming;
+
+        // Missing leading "//", not a valid label.
+        let errs = expand_imports(parse_quote! { "some_project:utils"; }, &mode).unwrap_err();
+        assert_eq!(
+            errs.into_iter()
+                .map(|e| e.to_string())
+                .collect::<Vec<String>>(),
+            vec!["Bazel labels must be of the form '//package[:target]'"]
+        );
+
+        // Valid label, but relative.
+        let errs = expand_imports(parse_quote! { ":utils"; }, &mode).unwrap_err();
+        assert_eq!(
+            errs.into_iter()
+                .map(|e| e.to_string())
+                .collect::<Vec<String>>(),
+            vec!["Bazel labels must be of the form '//package[:target]'"]
+        );
+
+        // Valid label, but a wildcard.
+        let errs = expand_imports(parse_quote! { "some_project/..."; }, &mode).unwrap_err();
+        assert_eq!(
+            errs.into_iter()
+                .map(|e| e.to_string())
+                .collect::<Vec<String>>(),
+            vec!["Bazel labels must be of the form '//package[:target]'"]
+        );
+
+        // Valid label, but only in Bazel (not in Bazel).
+        let errs =
+            expand_imports(parse_quote! { "@repository//some_project:utils"; }, &mode).unwrap_err();
+        assert_eq!(
+            errs.into_iter()
+                .map(|e| e.to_string())
+                .collect::<Vec<String>>(),
+            vec!["Bazel labels must be of the form '//package[:target]'"]
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_macro_input_parsing_errors() -> Result<()> {
+        // Label is not a string literal.
+        assert_eq!(
+            syn::parse_str::<ImportMacroInput>("some_project:utils;")
+                .err()
+                .unwrap()
+                .to_string(),
+            "expected Bazel label as a string literal"
+        );
+
+        // Label is the wrong kind of literal.
+        assert_eq!(
+            syn::parse_str::<ImportMacroInput>("true;")
+                .err()
+                .unwrap()
+                .to_string(),
+            "expected Bazel label as string literal, found 'true' literal"
+        );
+        assert_eq!(
+            syn::parse_str::<ImportMacroInput>("123;")
+                .err()
+                .unwrap()
+                .to_string(),
+            "expected Bazel label as string literal, found '123' literal"
+        );
+
+        // Alias is not a valid identifier.
+        assert_eq!(
+            syn::parse_str::<ImportMacroInput>(r#""some_project:utils" as "!@#$%";"#)
+                .err()
+                .unwrap()
+                .to_string(),
+            "alias must be a valid Rust identifier"
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_label_parsing() -> Result<()> {
+        assert_eq!(
+            AbsoluteLabel::parse("//some_project:utils", &Span::call_site())?,
+            AbsoluteLabel {
+                package_name: "some_project",
+                name: "utils"
+            },
+        );
+        assert_eq!(
+            AbsoluteLabel::parse("//some_project/utils", &Span::call_site())?,
+            AbsoluteLabel {
+                package_name: "some_project/utils",
+                name: "utils"
+            },
+        );
+        assert_eq!(
+            AbsoluteLabel::parse("//some_project", &Span::call_site())?,
+            AbsoluteLabel {
+                package_name: "some_project",
+                name: "some_project"
+            },
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_encode() -> Result<()> {
+        assert_eq!(encode("some_project:utils"), "some_project_colon_utils");
+        assert_eq!(&encode("_quotedot_"), "_quotequote_dot_");
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_decode() -> Result<()> {
+        assert_eq!(decode("some_project_colon_utils"), "some_project:utils");
+        assert_eq!(decode("_quotequote_dot_"), "_quotedot_");
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_substitutions_compose() -> Result<()> {
+        for s in SUBSTITUTIONS.0.iter().chain(SUBSTITUTIONS.1.iter()) {
+            assert_eq!(&decode(&encode(s)), s);
+        }
+
+        Ok(())
+    }
+
+    quickcheck! {
+        fn composition_is_identity(s: String) -> bool {
+            s == decode(&encode(&s))
+        }
+    }
+}