Skip to content

Commit a0ed0f9

Browse files
authored
Merge pull request #1128 from dtolnay/rawident
Support raw identifiers in namespace attribute
2 parents 3e065f7 + 33432a8 commit a0ed0f9

File tree

4 files changed

+101
-18
lines changed

4 files changed

+101
-18
lines changed

macro/src/type_id.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::syntax::qualified::QualifiedName;
22
use proc_macro2::{TokenStream, TokenTree};
33
use quote::{format_ident, quote, ToTokens};
4+
use syn::ext::IdentExt;
45

56
pub enum Crate {
67
Cxx,
@@ -24,7 +25,7 @@ pub fn expand(krate: Crate, arg: QualifiedName) -> TokenStream {
2425
if !ids.is_empty() {
2526
ids.push(quote!(()));
2627
}
27-
for ch in word.to_string().chars() {
28+
for ch in word.unraw().to_string().chars() {
2829
ids.push(match ch {
2930
'A'..='Z' | 'a'..='z' => {
3031
let t = format_ident!("{}", ch);

syntax/qualified.rs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use syn::ext::IdentExt;
2-
use syn::parse::{ParseStream, Result};
2+
use syn::parse::{Error, ParseStream, Result};
33
use syn::{Ident, LitStr, Token};
44

55
pub struct QualifiedName {
@@ -8,21 +8,8 @@ pub struct QualifiedName {
88

99
impl QualifiedName {
1010
pub fn parse_unquoted(input: ParseStream) -> Result<Self> {
11-
let mut segments = Vec::new();
12-
let mut trailing_punct = true;
13-
let leading_colons: Option<Token![::]> = input.parse()?;
14-
while trailing_punct && input.peek(Ident::peek_any) {
15-
let ident = Ident::parse_any(input)?;
16-
segments.push(ident);
17-
let colons: Option<Token![::]> = input.parse()?;
18-
trailing_punct = colons.is_some();
19-
}
20-
if segments.is_empty() && leading_colons.is_none() {
21-
return Err(input.error("expected path"));
22-
} else if trailing_punct {
23-
return Err(input.error("expected path segment"));
24-
}
25-
Ok(QualifiedName { segments })
11+
let allow_raw = true;
12+
parse_unquoted(input, allow_raw)
2613
}
2714

2815
pub fn parse_quoted_or_unquoted(input: ParseStream) -> Result<Self> {
@@ -32,10 +19,41 @@ impl QualifiedName {
3219
let segments = Vec::new();
3320
Ok(QualifiedName { segments })
3421
} else {
35-
lit.parse_with(Self::parse_unquoted)
22+
lit.parse_with(|input: ParseStream| {
23+
let allow_raw = false;
24+
parse_unquoted(input, allow_raw)
25+
})
3626
}
3727
} else {
3828
Self::parse_unquoted(input)
3929
}
4030
}
4131
}
32+
33+
fn parse_unquoted(input: ParseStream, allow_raw: bool) -> Result<QualifiedName> {
34+
let mut segments = Vec::new();
35+
let mut trailing_punct = true;
36+
let leading_colons: Option<Token![::]> = input.parse()?;
37+
while trailing_punct && input.peek(Ident::peek_any) {
38+
let mut ident = Ident::parse_any(input)?;
39+
if let Some(unraw) = ident.to_string().strip_prefix("r#") {
40+
if !allow_raw {
41+
let msg = format!(
42+
"raw identifier `{}` is not allowed in a quoted namespace; use `{}`, or remove quotes",
43+
ident, unraw,
44+
);
45+
return Err(Error::new(ident.span(), msg));
46+
}
47+
ident = Ident::new(unraw, ident.span());
48+
}
49+
segments.push(ident);
50+
let colons: Option<Token![::]> = input.parse()?;
51+
trailing_punct = colons.is_some();
52+
}
53+
if segments.is_empty() && leading_colons.is_none() {
54+
return Err(input.error("expected path"));
55+
} else if trailing_punct {
56+
return Err(input.error("expected path segment"));
57+
}
58+
Ok(QualifiedName { segments })
59+
}

tests/ui/raw_ident_namespace.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use cxx::{type_id, ExternType};
2+
3+
#[repr(transparent)]
4+
pub struct QuotedRaw(usize);
5+
6+
unsafe impl ExternType for QuotedRaw {
7+
type Id = type_id!("org::r#box::implementation::QuotedRaw");
8+
type Kind = cxx::kind::Trivial;
9+
}
10+
11+
#[repr(transparent)]
12+
pub struct QuotedKeyword(usize);
13+
14+
unsafe impl ExternType for QuotedKeyword {
15+
type Id = type_id!("org::box::implementation::QuotedKeyword");
16+
type Kind = cxx::kind::Trivial;
17+
}
18+
19+
#[repr(transparent)]
20+
pub struct UnquotedRaw(usize);
21+
22+
unsafe impl ExternType for UnquotedRaw {
23+
type Id = type_id!(org::r#box::implementation::UnquotedRaw);
24+
type Kind = cxx::kind::Trivial;
25+
}
26+
27+
#[repr(transparent)]
28+
pub struct UnquotedKeyword(usize);
29+
30+
unsafe impl ExternType for UnquotedKeyword {
31+
type Id = type_id!(org::box::implementation::UnquotedKeyword);
32+
type Kind = cxx::kind::Trivial;
33+
}
34+
35+
#[cxx::bridge]
36+
pub mod ffi {
37+
extern "C++" {
38+
#[namespace = "org::r#box::implementation"]
39+
type QuotedRaw = crate::QuotedRaw;
40+
41+
#[namespace = "org::box::implementation"]
42+
type QuotedKeyword = crate::QuotedKeyword;
43+
44+
#[namespace = org::r#box::implementation]
45+
type UnquotedRaw = crate::UnquotedRaw;
46+
47+
// Not allowed by rustc (independent of cxx):
48+
// #[namespace = org::box::implementation]
49+
// type UnquotedKeyword = crate::UnquotedKeyword;
50+
}
51+
}
52+
53+
fn main() {}

tests/ui/raw_ident_namespace.stderr

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error: raw identifier `r#box` is not allowed in a quoted namespace; use `box`, or remove quotes
2+
--> tests/ui/raw_ident_namespace.rs:7:24
3+
|
4+
7 | type Id = type_id!("org::r#box::implementation::QuotedRaw");
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
7+
error: raw identifier `r#box` is not allowed in a quoted namespace; use `box`, or remove quotes
8+
--> tests/ui/raw_ident_namespace.rs:38:23
9+
|
10+
38 | #[namespace = "org::r#box::implementation"]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)