Skip to content

Commit 448534e

Browse files
committed
ban a leading @, e.g. @3.11
1 parent d8657fc commit 448534e

2 files changed

Lines changed: 27 additions & 6 deletions

File tree

crates/uv-python/src/discovery.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,8 +1483,8 @@ impl PythonRequest {
14831483
/// - On Windows only, this allows `pythonw` as an alias for `python`.
14841484
/// - This allows `python` by itself (and on Windows, `pythonw`) as an alias for `default`.
14851485
///
1486-
/// This only returns `Err` if `@` is used, and the prefix is a match, but the version is
1487-
/// invalid. Otherwise, if no match is found, it returns `Ok(None)`.
1486+
/// This can only return `Err` if `@` is used, see try_parse_tool_executable. Otherwise, if no
1487+
/// match is found, it returns `Ok(None)`.
14881488
pub fn try_parse_tool_executable(value: &str) -> Result<Option<PythonRequest>, Error> {
14891489
let lowercase_value = &value.to_ascii_lowercase();
14901490
// Omitting the empty string from these lists excludes bare versions like "39".
@@ -1504,14 +1504,14 @@ impl PythonRequest {
15041504
)
15051505
}
15061506

1507-
// This only returns Err() if @ is used, and the prefix is a match, but the version is invalid.
1508-
// Otherwise, if no match is found, it returns Ok(None).
1507+
// This can only return Err() if @ is used, see try_split_prefix_and_version below. Otherwise,
1508+
// if no match is found, it returns Ok(None).
15091509
fn parse_versions_and_implementations<'a>(
15101510
// typically "python", possibly also "pythonw" or "" (for bare versions)
15111511
abstract_version_prefixes: impl IntoIterator<Item = &'a str>,
15121512
// expected to be either long_names() or all names
15131513
implementation_names: impl IntoIterator<Item = &'a str>,
1514-
// the string to parse, case-insensitive
1514+
// the string to parse
15151515
lowercase_value: &str,
15161516
) -> Result<Option<PythonRequest>, Error> {
15171517
for prefix in abstract_version_prefixes {
@@ -1546,11 +1546,16 @@ impl PythonRequest {
15461546
Ok(None)
15471547
}
15481548

1549-
// This only returns Err() if @ is used, and the prefix is a match, but the version is invalid.
1549+
// This can only returns Err() if @ is used. There are two error cases:
1550+
// - The value starts with @ (e.g. `@3.11`).
1551+
// - The prefix is a match, but the version is invalid (e.g. `[email protected]`).
15501552
fn try_split_prefix_and_version(
15511553
prefix: &str,
15521554
lowercase_value: &str,
15531555
) -> Result<Option<VersionRequest>, Error> {
1556+
if lowercase_value.starts_with('@') {
1557+
return Err(Error::InvalidVersionRequest(lowercase_value.to_string()));
1558+
}
15541559
let Some(rest) = lowercase_value.strip_prefix(prefix) else {
15551560
return Ok(None);
15561561
};
@@ -3428,5 +3433,7 @@ mod tests {
34283433
assert!(
34293434
PythonRequest::try_split_prefix_and_version("prefix", "prefix@3notaversion").is_err()
34303435
);
3436+
// @ is not allowed if the prefix is empty.
3437+
assert!(PythonRequest::try_split_prefix_and_version("", "@3").is_err());
34313438
}
34323439
}

crates/uv/tests/it/tool_run.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,6 +1986,20 @@ fn tool_run_python_at_version() {
19861986
Resolved in [TIME]
19871987
"###);
19881988

1989+
// But @ with nothing in front of it is not.
1990+
uv_snapshot!(context.filters(), context.tool_run()
1991+
.arg("-p")
1992+
.arg("@311")
1993+
.arg("python")
1994+
.arg("--version"), @r"
1995+
success: false
1996+
exit_code: 2
1997+
----- stdout -----
1998+
1999+
----- stderr -----
2000+
error: No interpreter found for executable name `@311` in [PYTHON SOURCES]
2001+
");
2002+
19892003
// Request a version in the tool and `-p`
19902004
uv_snapshot!(context.filters(), context.tool_run()
19912005
.arg("-p")

0 commit comments

Comments
 (0)