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>
This commit is contained in:
parent
414da5b63d
commit
f35c85f72f
4 changed files with 178 additions and 34 deletions
|
@ -14,6 +14,7 @@ use std::str::{self, FromStr};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use std::{cmp, fmt, fs, iter};
|
use std::{cmp, fmt, fs, iter};
|
||||||
|
|
||||||
|
use externs::{ExternOpt, split_extern_opt};
|
||||||
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
|
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
|
||||||
use rustc_data_structures::stable_hasher::{StableOrd, ToStableHashKey};
|
use rustc_data_structures::stable_hasher::{StableOrd, ToStableHashKey};
|
||||||
use rustc_errors::emitter::HumanReadableErrorType;
|
use rustc_errors::emitter::HumanReadableErrorType;
|
||||||
|
@ -39,6 +40,7 @@ use crate::utils::CanonicalizedPath;
|
||||||
use crate::{EarlyDiagCtxt, HashStableContext, Session, filesearch, lint};
|
use crate::{EarlyDiagCtxt, HashStableContext, Session, filesearch, lint};
|
||||||
|
|
||||||
mod cfg;
|
mod cfg;
|
||||||
|
mod externs;
|
||||||
mod native_libs;
|
mod native_libs;
|
||||||
pub mod sigpipe;
|
pub mod sigpipe;
|
||||||
|
|
||||||
|
@ -2205,44 +2207,13 @@ pub fn parse_externs(
|
||||||
matches: &getopts::Matches,
|
matches: &getopts::Matches,
|
||||||
unstable_opts: &UnstableOptions,
|
unstable_opts: &UnstableOptions,
|
||||||
) -> Externs {
|
) -> 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 is_unstable_enabled = unstable_opts.unstable_options;
|
||||||
let mut externs: BTreeMap<String, ExternEntry> = BTreeMap::new();
|
let mut externs: BTreeMap<String, ExternEntry> = BTreeMap::new();
|
||||||
for arg in matches.opt_strs("extern") {
|
for arg in matches.opt_strs("extern") {
|
||||||
let (name, path) = match arg.split_once('=') {
|
let ExternOpt { crate_name: name, path, options } =
|
||||||
None => (arg, None),
|
split_extern_opt(early_dcx, unstable_opts, &arg).unwrap_or_else(|e| e.emit());
|
||||||
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 path = path.map(|p| CanonicalizedPath::new(p.as_path()));
|
||||||
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 path = path.map(|p| CanonicalizedPath::new(p));
|
|
||||||
|
|
||||||
let entry = externs.entry(name.to_owned());
|
let entry = externs.entry(name.to_owned());
|
||||||
|
|
||||||
|
|
79
compiler/rustc_session/src/config/externs.rs
Normal file
79
compiler/rustc_session/src/config/externs.rs
Normal file
|
@ -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
compiler/rustc_session/src/config/externs/tests.rs
Normal file
92
compiler/rustc_session/src/config/externs/tests.rs
Normal file
|
@ -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());
|
||||||
|
}
|
|
@ -2331,6 +2331,8 @@ options! {
|
||||||
"the size at which the `large_assignments` lint starts to be emitted"),
|
"the size at which the `large_assignments` lint starts to be emitted"),
|
||||||
mutable_noalias: bool = (true, parse_bool, [TRACKED],
|
mutable_noalias: bool = (true, parse_bool, [TRACKED],
|
||||||
"emit noalias metadata for mutable references (default: yes)"),
|
"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],
|
next_solver: NextSolverConfig = (NextSolverConfig::default(), parse_next_solver_config, [TRACKED],
|
||||||
"enable and configure the next generation trait solver used by rustc"),
|
"enable and configure the next generation trait solver used by rustc"),
|
||||||
nll_facts: bool = (false, parse_bool, [UNTRACKED],
|
nll_facts: bool = (false, parse_bool, [UNTRACKED],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue