about summary refs log tree commit diff
path: root/compiler/rustc_session/src
diff options
context:
space:
mode:
authorMatthias Krüger <476013+matthiaskrgr@users.noreply.github.com>2025-04-16 13:45:27 +0200
committerGitHub <noreply@github.com>2025-04-16 13:45:27 +0200
commit723ef24e278480495d9f9f786714feaa199c07b6 (patch)
tree83cb17f1fb24317a3ad2db96a3c119aba2d9d8aa /compiler/rustc_session/src
parentafa859f8121bf2985362a2c8414dc71a825ccf2d (diff)
parentf35c85f72fc1249f0553d97482ca29fe3fa0a165 (diff)
downloadrust-723ef24e278480495d9f9f786714feaa199c07b6.tar.gz
rust-723ef24e278480495d9f9f786714feaa199c07b6.zip
Rollup merge of #139647 - eholk:package-namespace, r=fmease
Add unstable parsing of `--extern foo::bar=libbar.rlib` command line options

This is a tiny step towards implementing the rustc side of support for implementing packages as optional namespaces (#122349). We add support for parsing command line options like `--extern foo::bar=libbar.rlib` when the `-Z namespaced-crates` option is present.

We don't do anything further with them. The next step is to plumb this down to the name resolver.

This PR also generally refactors the extern argument parsing code and adds some unit tests to make it clear what forms should be accepted with and without the flag.

cc ```@epage``` ```@ehuss```
Diffstat (limited to 'compiler/rustc_session/src')
-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 ea3a1f8bd8c..1b0794f79d3 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -2334,6 +2334,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],