Skip to content

Add unstable parsing of --extern foo::bar=libbar.rlib command line options #139647

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 5 additions & 34 deletions compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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());

Expand Down
79 changes: 79 additions & 0 deletions compiler/rustc_session/src/config/externs.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
92 changes: 92 additions & 0 deletions compiler/rustc_session/src/config/externs/tests.rs
Original file line number Diff line number Diff line change
@@ -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());
}
2 changes: 2 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Loading