about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEric Holk <ericholk@microsoft.com>2025-04-08 16:11:28 -0700
committerEric Holk <ericholk@microsoft.com>2025-04-15 13:04:50 -0700
commitf35c85f72fc1249f0553d97482ca29fe3fa0a165 (patch)
treed9658ab24494953654ab41df5956cef1b3d212c5
parent414da5b63d699c1c648088114ad2796deb0ce737 (diff)
downloadrust-f35c85f72fc1249f0553d97482ca29fe3fa0a165.tar.gz
rust-f35c85f72fc1249f0553d97482ca29fe3fa0a165.zip
Add unstable foo::bar extern command line arguments
Also refactors some of the crate name parsing code and adds unit tests

Issue #122349

Co-authored-by: León Orell Valerian Liehr <me@fmease.dev>
-rw-r--r--compiler/rustc_session/src/config.rs39
-rw-r--r--compiler/rustc_session/src/config/externs.rs79
-rw-r--r--compiler/rustc_session/src/config/externs/tests.rs92
-rw-r--r--compiler/rustc_session/src/options.rs2
4 files changed, 178 insertions, 34 deletions
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index fc05470d941..202378560ee 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -14,6 +14,7 @@ use std::str::{self, FromStr};
 use std::sync::LazyLock;
 use std::{cmp, fmt, fs, iter};
 
+use externs::{ExternOpt, split_extern_opt};
 use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
 use rustc_data_structures::stable_hasher::{StableOrd, ToStableHashKey};
 use rustc_errors::emitter::HumanReadableErrorType;
@@ -39,6 +40,7 @@ use crate::utils::CanonicalizedPath;
 use crate::{EarlyDiagCtxt, HashStableContext, Session, filesearch, lint};
 
 mod cfg;
+mod externs;
 mod native_libs;
 pub mod sigpipe;
 
@@ -2205,44 +2207,13 @@ pub fn parse_externs(
     matches: &getopts::Matches,
     unstable_opts: &UnstableOptions,
 ) -> Externs {
-    fn is_ascii_ident(string: &str) -> bool {
-        let mut chars = string.chars();
-        if let Some(start) = chars.next()
-            && (start.is_ascii_alphabetic() || start == '_')
-        {
-            chars.all(|char| char.is_ascii_alphanumeric() || char == '_')
-        } else {
-            false
-        }
-    }
-
     let is_unstable_enabled = unstable_opts.unstable_options;
     let mut externs: BTreeMap<String, ExternEntry> = BTreeMap::new();
     for arg in matches.opt_strs("extern") {
-        let (name, path) = match arg.split_once('=') {
-            None => (arg, None),
-            Some((name, path)) => (name.to_string(), Some(Path::new(path))),
-        };
-        let (options, name) = match name.split_once(':') {
-            None => (None, name),
-            Some((opts, name)) => (Some(opts), name.to_string()),
-        };
-
-        if !is_ascii_ident(&name) {
-            let mut error = early_dcx.early_struct_fatal(format!(
-                "crate name `{name}` passed to `--extern` is not a valid ASCII identifier"
-            ));
-            let adjusted_name = name.replace('-', "_");
-            if is_ascii_ident(&adjusted_name) {
-                #[allow(rustc::diagnostic_outside_of_impl)] // FIXME
-                error.help(format!(
-                    "consider replacing the dashes with underscores: `{adjusted_name}`"
-                ));
-            }
-            error.emit();
-        }
+        let ExternOpt { crate_name: name, path, options } =
+            split_extern_opt(early_dcx, unstable_opts, &arg).unwrap_or_else(|e| e.emit());
 
-        let path = path.map(|p| CanonicalizedPath::new(p));
+        let path = path.map(|p| CanonicalizedPath::new(p.as_path()));
 
         let entry = externs.entry(name.to_owned());
 
diff --git a/compiler/rustc_session/src/config/externs.rs b/compiler/rustc_session/src/config/externs.rs
new file mode 100644
index 00000000000..1420ee38bf2
--- /dev/null
+++ b/compiler/rustc_session/src/config/externs.rs
@@ -0,0 +1,79 @@
+//! This module contains code to help parse and manipulate `--extern` arguments.
+
+use std::path::PathBuf;
+
+use rustc_errors::{Diag, FatalAbort};
+
+use super::UnstableOptions;
+use crate::EarlyDiagCtxt;
+
+#[cfg(test)]
+mod tests;
+
+/// Represents the pieces of an `--extern` argument.
+pub(crate) struct ExternOpt {
+    pub(crate) crate_name: String,
+    pub(crate) path: Option<PathBuf>,
+    pub(crate) options: Option<String>,
+}
+
+/// Breaks out the major components of an `--extern` argument.
+///
+/// The options field will be a string containing comma-separated options that will need further
+/// parsing and processing.
+pub(crate) fn split_extern_opt<'a>(
+    early_dcx: &'a EarlyDiagCtxt,
+    unstable_opts: &UnstableOptions,
+    extern_opt: &str,
+) -> Result<ExternOpt, Diag<'a, FatalAbort>> {
+    let (name, path) = match extern_opt.split_once('=') {
+        None => (extern_opt.to_string(), None),
+        Some((name, path)) => (name.to_string(), Some(PathBuf::from(path))),
+    };
+    let (options, crate_name) = match name.split_once(':') {
+        None => (None, name),
+        Some((opts, crate_name)) => {
+            if unstable_opts.namespaced_crates && crate_name.starts_with(':') {
+                // If the name starts with `:`, we know this was actually something like `foo::bar` and
+                // not a set of options. We can just use the original name as the crate name.
+                (None, name)
+            } else {
+                (Some(opts.to_string()), crate_name.to_string())
+            }
+        }
+    };
+
+    if !valid_crate_name(&crate_name, unstable_opts) {
+        let mut error = early_dcx.early_struct_fatal(format!(
+            "crate name `{crate_name}` passed to `--extern` is not a valid ASCII identifier"
+        ));
+        let adjusted_name = crate_name.replace('-', "_");
+        if is_ascii_ident(&adjusted_name) {
+            #[allow(rustc::diagnostic_outside_of_impl)] // FIXME
+            error
+                .help(format!("consider replacing the dashes with underscores: `{adjusted_name}`"));
+        }
+        return Err(error);
+    }
+
+    Ok(ExternOpt { crate_name, path, options })
+}
+
+fn valid_crate_name(name: &str, unstable_opts: &UnstableOptions) -> bool {
+    match name.split_once("::") {
+        Some((a, b)) if unstable_opts.namespaced_crates => is_ascii_ident(a) && is_ascii_ident(b),
+        Some(_) => false,
+        None => is_ascii_ident(name),
+    }
+}
+
+fn is_ascii_ident(string: &str) -> bool {
+    let mut chars = string.chars();
+    if let Some(start) = chars.next()
+        && (start.is_ascii_alphabetic() || start == '_')
+    {
+        chars.all(|char| char.is_ascii_alphanumeric() || char == '_')
+    } else {
+        false
+    }
+}
diff --git a/compiler/rustc_session/src/config/externs/tests.rs b/compiler/rustc_session/src/config/externs/tests.rs
new file mode 100644
index 00000000000..65448869515
--- /dev/null
+++ b/compiler/rustc_session/src/config/externs/tests.rs
@@ -0,0 +1,92 @@
+use std::path::PathBuf;
+
+use super::split_extern_opt;
+use crate::EarlyDiagCtxt;
+use crate::config::UnstableOptions;
+
+/// Verifies split_extern_opt handles the supported cases.
+#[test]
+fn test_split_extern_opt() {
+    let early_dcx = EarlyDiagCtxt::new(<_>::default());
+    let unstable_opts = &UnstableOptions::default();
+
+    let extern_opt =
+        split_extern_opt(&early_dcx, unstable_opts, "priv,noprelude:foo=libbar.rlib").unwrap();
+    assert_eq!(extern_opt.crate_name, "foo");
+    assert_eq!(extern_opt.path, Some(PathBuf::from("libbar.rlib")));
+    assert_eq!(extern_opt.options, Some("priv,noprelude".to_string()));
+
+    let extern_opt = split_extern_opt(&early_dcx, unstable_opts, "priv,noprelude:foo").unwrap();
+    assert_eq!(extern_opt.crate_name, "foo");
+    assert_eq!(extern_opt.path, None);
+    assert_eq!(extern_opt.options, Some("priv,noprelude".to_string()));
+
+    let extern_opt = split_extern_opt(&early_dcx, unstable_opts, "foo=libbar.rlib").unwrap();
+    assert_eq!(extern_opt.crate_name, "foo");
+    assert_eq!(extern_opt.path, Some(PathBuf::from("libbar.rlib")));
+    assert_eq!(extern_opt.options, None);
+
+    let extern_opt = split_extern_opt(&early_dcx, unstable_opts, "foo").unwrap();
+    assert_eq!(extern_opt.crate_name, "foo");
+    assert_eq!(extern_opt.path, None);
+    assert_eq!(extern_opt.options, None);
+}
+
+/// Tests some invalid cases for split_extern_opt.
+#[test]
+fn test_split_extern_opt_invalid() {
+    let early_dcx = EarlyDiagCtxt::new(<_>::default());
+    let unstable_opts = &UnstableOptions::default();
+
+    // too many `:`s
+    let result = split_extern_opt(&early_dcx, unstable_opts, "priv:noprelude:foo=libbar.rlib");
+    assert!(result.is_err());
+    let _ = result.map_err(|e| e.cancel());
+
+    // can't nest externs without the unstable flag
+    let result = split_extern_opt(&early_dcx, unstable_opts, "noprelude:foo::bar=libbar.rlib");
+    assert!(result.is_err());
+    let _ = result.map_err(|e| e.cancel());
+}
+
+/// Tests some cases for split_extern_opt with nested crates like `foo::bar`.
+#[test]
+fn test_split_extern_opt_nested() {
+    let early_dcx = EarlyDiagCtxt::new(<_>::default());
+    let unstable_opts = &UnstableOptions { namespaced_crates: true, ..Default::default() };
+
+    let extern_opt =
+        split_extern_opt(&early_dcx, unstable_opts, "priv,noprelude:foo::bar=libbar.rlib").unwrap();
+    assert_eq!(extern_opt.crate_name, "foo::bar");
+    assert_eq!(extern_opt.path, Some(PathBuf::from("libbar.rlib")));
+    assert_eq!(extern_opt.options, Some("priv,noprelude".to_string()));
+
+    let extern_opt =
+        split_extern_opt(&early_dcx, unstable_opts, "priv,noprelude:foo::bar").unwrap();
+    assert_eq!(extern_opt.crate_name, "foo::bar");
+    assert_eq!(extern_opt.path, None);
+    assert_eq!(extern_opt.options, Some("priv,noprelude".to_string()));
+
+    let extern_opt = split_extern_opt(&early_dcx, unstable_opts, "foo::bar=libbar.rlib").unwrap();
+    assert_eq!(extern_opt.crate_name, "foo::bar");
+    assert_eq!(extern_opt.path, Some(PathBuf::from("libbar.rlib")));
+    assert_eq!(extern_opt.options, None);
+
+    let extern_opt = split_extern_opt(&early_dcx, unstable_opts, "foo::bar").unwrap();
+    assert_eq!(extern_opt.crate_name, "foo::bar");
+    assert_eq!(extern_opt.path, None);
+    assert_eq!(extern_opt.options, None);
+}
+
+/// Tests some invalid cases for split_extern_opt with nested crates like `foo::bar`.
+#[test]
+fn test_split_extern_opt_nested_invalid() {
+    let early_dcx = EarlyDiagCtxt::new(<_>::default());
+    let unstable_opts = &UnstableOptions { namespaced_crates: true, ..Default::default() };
+
+    // crates can only be nested one deep.
+    let result =
+        split_extern_opt(&early_dcx, unstable_opts, "priv,noprelude:foo::bar::baz=libbar.rlib");
+    assert!(result.is_err());
+    let _ = result.map_err(|e| e.cancel());
+}
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index c70f1500d39..bd66f835f12 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -2331,6 +2331,8 @@ options! {
         "the size at which the `large_assignments` lint starts to be emitted"),
     mutable_noalias: bool = (true, parse_bool, [TRACKED],
         "emit noalias metadata for mutable references (default: yes)"),
+    namespaced_crates: bool = (false, parse_bool, [TRACKED],
+        "allow crates to be namespaced by other crates (default: no)"),
     next_solver: NextSolverConfig = (NextSolverConfig::default(), parse_next_solver_config, [TRACKED],
         "enable and configure the next generation trait solver used by rustc"),
     nll_facts: bool = (false, parse_bool, [UNTRACKED],