Skip to content

Commit 4fcec5c

Browse files
committed
Add unstable parse for foo::bar extern command line arguments
Issue #122349
1 parent a87adb0 commit 4fcec5c

File tree

4 files changed

+174
-1
lines changed

4 files changed

+174
-1
lines changed

compiler/rustc_session/src/config.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2194,7 +2194,7 @@ pub fn parse_externs(
21942194
let mut externs: BTreeMap<String, ExternEntry> = BTreeMap::new();
21952195
for arg in matches.opt_strs("extern") {
21962196
let ExternOpt { crate_name: name, path, options } =
2197-
split_extern_opt(early_dcx, &arg).unwrap_or_else(|e| e.emit());
2197+
split_extern_opt(early_dcx, unstable_opts, &arg).unwrap_or_else(|e| e.emit());
21982198

21992199
let path = path.map(|p| CanonicalizedPath::new(p.as_path()));
22002200

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//! This module contains code to help parse and manipulate `--extern` arguments.
2+
3+
use std::path::PathBuf;
4+
5+
use rustc_errors::{Diag, FatalAbort};
6+
7+
use super::UnstableOptions;
8+
use crate::EarlyDiagCtxt;
9+
10+
#[cfg(test)]
11+
mod tests;
12+
13+
/// Represents the pieces of an `--extern` argument.
14+
pub(crate) struct ExternOpt {
15+
pub(crate) crate_name: String,
16+
pub(crate) path: Option<PathBuf>,
17+
pub(crate) options: Option<String>,
18+
}
19+
20+
/// Breaks out the major components of an `--extern` argument.
21+
///
22+
/// The options field will be a string containing comma-separated options that will need further
23+
/// parsing and processing.
24+
pub(crate) fn split_extern_opt<'a>(
25+
early_dcx: &'a EarlyDiagCtxt,
26+
unstable_opts: &UnstableOptions,
27+
extern_opt: &str,
28+
) -> Result<ExternOpt, Diag<'a, FatalAbort>> {
29+
let (name, path) = match extern_opt.split_once('=') {
30+
None => (extern_opt.to_string(), None),
31+
Some((name, path)) => (name.to_string(), Some(PathBuf::from(path))),
32+
};
33+
let (options, crate_name) = match name.split_once(':') {
34+
None => (None, name),
35+
Some((opts, crate_name)) => {
36+
if unstable_opts.namespaced_crates && crate_name.starts_with(':') {
37+
// If the name starts with `:`, we know this was actually something like `foo::bar` and
38+
// not a set of options. We can just use the original name as the crate name.
39+
(None, name)
40+
} else {
41+
(Some(opts.to_string()), crate_name.to_string())
42+
}
43+
}
44+
};
45+
46+
if !valid_crate_name(&crate_name, unstable_opts) {
47+
let mut error = early_dcx.early_struct_fatal(format!(
48+
"crate name `{crate_name}` passed to `--extern` is not a valid ASCII identifier"
49+
));
50+
let adjusted_name = crate_name.replace('-', "_");
51+
if is_ascii_ident(&adjusted_name) {
52+
#[allow(rustc::diagnostic_outside_of_impl)] // FIXME
53+
error
54+
.help(format!("consider replacing the dashes with underscores: `{adjusted_name}`"));
55+
}
56+
return Err(error);
57+
}
58+
59+
Ok(ExternOpt { crate_name, path, options })
60+
}
61+
62+
fn valid_crate_name(name: &str, unstable_opts: &UnstableOptions) -> bool {
63+
match name.split_once("::") {
64+
Some((a, b)) if unstable_opts.namespaced_crates => is_ascii_ident(a) && is_ascii_ident(b),
65+
Some(_) => false,
66+
None => is_ascii_ident(name),
67+
}
68+
}
69+
70+
fn is_ascii_ident(string: &str) -> bool {
71+
let mut chars = string.chars();
72+
if let Some(start) = chars.next()
73+
&& (start.is_ascii_alphabetic() || start == '_')
74+
{
75+
chars.all(|char| char.is_ascii_alphanumeric() || char == '_')
76+
} else {
77+
false
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use std::path::PathBuf;
2+
3+
use super::split_extern_opt;
4+
use crate::EarlyDiagCtxt;
5+
use crate::config::UnstableOptions;
6+
7+
/// Verifies split_extern_opt handles the supported cases.
8+
#[test]
9+
fn test_split_extern_opt() {
10+
let early_dcx = EarlyDiagCtxt::new(<_>::default());
11+
let unstable_opts = &UnstableOptions::default();
12+
13+
let extern_opt =
14+
split_extern_opt(&early_dcx, unstable_opts, "priv,noprelude:foo=libbar.rlib").unwrap();
15+
assert_eq!(extern_opt.crate_name, "foo");
16+
assert_eq!(extern_opt.path, Some(PathBuf::from("libbar.rlib")));
17+
assert_eq!(extern_opt.options, Some("priv,noprelude".to_string()));
18+
19+
let extern_opt = split_extern_opt(&early_dcx, unstable_opts, "priv,noprelude:foo").unwrap();
20+
assert_eq!(extern_opt.crate_name, "foo");
21+
assert_eq!(extern_opt.path, None);
22+
assert_eq!(extern_opt.options, Some("priv,noprelude".to_string()));
23+
24+
let extern_opt = split_extern_opt(&early_dcx, unstable_opts, "foo=libbar.rlib").unwrap();
25+
assert_eq!(extern_opt.crate_name, "foo");
26+
assert_eq!(extern_opt.path, Some(PathBuf::from("libbar.rlib")));
27+
assert_eq!(extern_opt.options, None);
28+
29+
let extern_opt = split_extern_opt(&early_dcx, unstable_opts, "foo").unwrap();
30+
assert_eq!(extern_opt.crate_name, "foo");
31+
assert_eq!(extern_opt.path, None);
32+
assert_eq!(extern_opt.options, None);
33+
}
34+
35+
/// Tests some invalid cases for split_extern_opt.
36+
#[test]
37+
fn test_split_extern_opt_invalid() {
38+
let early_dcx = EarlyDiagCtxt::new(<_>::default());
39+
let unstable_opts = &UnstableOptions::default();
40+
41+
// too many `:`s
42+
let result = split_extern_opt(&early_dcx, unstable_opts, "priv:noprelude:foo=libbar.rlib");
43+
assert!(result.is_err());
44+
let _ = result.map_err(|e| e.cancel());
45+
46+
// can't nest externs without the unstable flag
47+
let result = split_extern_opt(&early_dcx, unstable_opts, "noprelude:foo::bar=libbar.rlib");
48+
assert!(result.is_err());
49+
let _ = result.map_err(|e| e.cancel());
50+
}
51+
52+
/// Tests some cases for split_extern_opt with nested crates like `foo::bar`.
53+
#[test]
54+
fn test_split_extern_opt_nested() {
55+
let early_dcx = EarlyDiagCtxt::new(<_>::default());
56+
let unstable_opts = &UnstableOptions { namespaced_crates: true, ..Default::default() };
57+
58+
let extern_opt =
59+
split_extern_opt(&early_dcx, unstable_opts, "priv,noprelude:foo::bar=libbar.rlib").unwrap();
60+
assert_eq!(extern_opt.crate_name, "foo::bar");
61+
assert_eq!(extern_opt.path, Some(PathBuf::from("libbar.rlib")));
62+
assert_eq!(extern_opt.options, Some("priv,noprelude".to_string()));
63+
64+
let extern_opt =
65+
split_extern_opt(&early_dcx, unstable_opts, "priv,noprelude:foo::bar").unwrap();
66+
assert_eq!(extern_opt.crate_name, "foo::bar");
67+
assert_eq!(extern_opt.path, None);
68+
assert_eq!(extern_opt.options, Some("priv,noprelude".to_string()));
69+
70+
let extern_opt = split_extern_opt(&early_dcx, unstable_opts, "foo::bar=libbar.rlib").unwrap();
71+
assert_eq!(extern_opt.crate_name, "foo::bar");
72+
assert_eq!(extern_opt.path, Some(PathBuf::from("libbar.rlib")));
73+
assert_eq!(extern_opt.options, None);
74+
75+
let extern_opt = split_extern_opt(&early_dcx, unstable_opts, "foo::bar").unwrap();
76+
assert_eq!(extern_opt.crate_name, "foo::bar");
77+
assert_eq!(extern_opt.path, None);
78+
assert_eq!(extern_opt.options, None);
79+
}
80+
81+
/// Tests some invalid cases for split_extern_opt with nested crates like `foo::bar`.
82+
#[test]
83+
fn test_split_extern_opt_nested_invalid() {
84+
let early_dcx = EarlyDiagCtxt::new(<_>::default());
85+
let unstable_opts = &UnstableOptions { namespaced_crates: true, ..Default::default() };
86+
87+
// crates can only be nested one deep.
88+
let result =
89+
split_extern_opt(&early_dcx, unstable_opts, "priv,noprelude:foo::bar::baz=libbar.rlib");
90+
assert!(result.is_err());
91+
let _ = result.map_err(|e| e.cancel());
92+
}

compiler/rustc_session/src/options.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2331,6 +2331,8 @@ options! {
23312331
"the size at which the `large_assignments` lint starts to be emitted"),
23322332
mutable_noalias: bool = (true, parse_bool, [TRACKED],
23332333
"emit noalias metadata for mutable references (default: yes)"),
2334+
namespaced_crates: bool = (false, parse_bool, [TRACKED],
2335+
"allow crates to be nested in other crates (default: no)"),
23342336
next_solver: NextSolverConfig = (NextSolverConfig::default(), parse_next_solver_config, [TRACKED],
23352337
"enable and configure the next generation trait solver used by rustc"),
23362338
nll_facts: bool = (false, parse_bool, [UNTRACKED],

0 commit comments

Comments
 (0)