Skip to content

Commit efaa8f8

Browse files
committed
Auto merge of #2510 - dtolnay:plus, r=smarnach
Permit '+' in feature name, as in "c++20" Up front: I am familiar with the several discussions linked by rust-lang/crates-io-cargo-teams#41 (comment), and the conversation beginning at #1331 (comment). This PR is not motivated by attempting to match whatever behavior Cargo currently has. Instead, it's a small thing I think we can decide now whether to allow. But it's necessary to say no corresponding Cargo change is required to accommodate this crates.io change. This PR updates feature validation during publish to accept e.g. "c++20" and "dependency/c++20". We continue to not accept "c++20/feature" as the prefix before the slash would normally refer to a crate name of a dependency and a '+' would not be allowed in those. I am interested in using such feature names in https://github.com/dtolnay/cxx. In a Cargo.toml plusses appear as: ```toml [features] default = ["c++20"] "c++20" = ["my-dependency/c++20"] ``` To give some slight further justification for why this particular ascii character above other possible characters we might allow: `+` is pretty common in OS package names in a way that no other currently disallowed character is. Some examples pulled arbitrarily from `apt-cache pkgnames | rg '\+'`: - https://packages.debian.org/buster/dvd+rw-tools - https://packages.debian.org/buster/elpa-ghub+ - https://packages.debian.org/buster/libarpack++2-dev - https://packages.debian.org/buster/libdwarf++0 - https://packages.debian.org/buster/libelf++0 - https://packages.debian.org/buster/magics++ - https://packages.debian.org/buster/memtest86+ - https://packages.debian.org/buster/minisat+ - https://packages.debian.org/buster/paw++ - https://packages.debian.org/buster/swish++ - https://packages.debian.org/buster/vera++ - https://packages.debian.org/buster/voro++ The actual names of the projects contain `+`; various ones in the descriptions in the above links are styled as ARPACK++, Memtest86+, Magics++, Paw++, MinSat+, SWISH++, Vera++, Voro++. When binding to something like this behind a feature, using `+` in the feature name is the most intuitive.
2 parents d0a235f + 5f842f7 commit efaa8f8

File tree

3 files changed

+20
-12
lines changed

3 files changed

+20
-12
lines changed

src/models/krate.rs

+16-11
Original file line numberDiff line numberDiff line change
@@ -269,26 +269,31 @@ impl Crate {
269269
.unwrap_or(false)
270270
}
271271

272+
/// Validates the THIS parts of `features = ["THIS", "and/THIS"]`.
272273
pub fn valid_feature_name(name: &str) -> bool {
273274
!name.is_empty()
274275
&& name
275276
.chars()
276-
.all(|c| c.is_alphanumeric() || c == '_' || c == '-')
277-
&& name.chars().all(|c| c.is_ascii())
277+
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '+')
278278
}
279279

280+
/// Validates the prefix in front of the slash: `features = ["THIS/feature"]`.
281+
/// Normally this corresponds to the crate name of a dependency.
282+
fn valid_feature_prefix(name: &str) -> bool {
283+
!name.is_empty()
284+
&& name
285+
.chars()
286+
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
287+
}
288+
289+
/// Validates a whole feature string, `features = ["THIS", "ALL/THIS"]`.
280290
pub fn valid_feature(name: &str) -> bool {
281291
let mut parts = name.split('/');
282-
match parts.next() {
283-
Some(part) if !Crate::valid_feature_name(part) => return false,
284-
None => return false,
285-
_ => {}
286-
}
287-
match parts.next() {
288-
Some(part) if !Crate::valid_feature_name(part) => return false,
289-
_ => {}
290-
}
292+
let name_part = parts.next_back(); // required
293+
let prefix_part = parts.next_back(); // optional
291294
parts.next().is_none()
295+
&& name_part.map_or(false, Crate::valid_feature_name)
296+
&& prefix_part.map_or(true, Crate::valid_feature_prefix)
292297
}
293298

294299
pub fn minimal_encodable(

src/tests/krate.rs

+3
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,9 @@ fn valid_feature_names() {
983983
assert!(!Crate::valid_feature("%/%"));
984984
assert!(Crate::valid_feature("a/a"));
985985
assert!(Crate::valid_feature("32-column-tables"));
986+
assert!(Crate::valid_feature("c++20"));
987+
assert!(Crate::valid_feature("krate/c++20"));
988+
assert!(!Crate::valid_feature("c++20/wow"));
986989
}
987990

988991
#[test]

src/views/krate_publish.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ impl<'de> Deserialize<'de> for EncodableFeatureName {
113113
if !Crate::valid_feature_name(&s) {
114114
let value = de::Unexpected::Str(&s);
115115
let expected = "a valid feature name containing only letters, \
116-
numbers, hyphens, or underscores";
116+
numbers, '-', '+', or '_'";
117117
Err(de::Error::invalid_value(value, &expected))
118118
} else {
119119
Ok(EncodableFeatureName(s))

0 commit comments

Comments
 (0)