44
55use std:: borrow:: Cow ;
66use std:: collections:: HashMap ;
7+ use std:: fmt:: Write as _;
78use std:: io:: Write ;
89
910use syn:: ext:: IdentExt ;
@@ -13,8 +14,8 @@ use crate::bindgen::config::{Config, Language};
1314use crate :: bindgen:: declarationtyperesolver:: DeclarationTypeResolver ;
1415use crate :: bindgen:: dependencies:: Dependencies ;
1516use crate :: bindgen:: ir:: {
16- AnnotationSet , Cfg , ConditionWrite , Documentation , GenericParams , Item , ItemContainer , Path ,
17- Struct , ToCondition , Type ,
17+ AnnotationSet , Cfg , ConditionWrite , ConstExpr , Documentation , GenericParams , IntKind , Item ,
18+ ItemContainer , Path , PrimitiveType , Struct , ToCondition , Type ,
1819} ;
1920use crate :: bindgen:: language_backend:: LanguageBackend ;
2021use crate :: bindgen:: library:: Library ;
@@ -29,10 +30,140 @@ fn member_to_ident(member: &syn::Member) -> String {
2930 }
3031}
3132
33+ /// Format a byte slice as a double-quoted string literal valid in both C and C++.
34+ ///
35+ /// Printable ASCII passes through; `"`, `\`, `?`, and the common whitespace
36+ /// controls use their named escapes; every other byte (other control characters
37+ /// and the raw bytes of non-ASCII UTF-8) becomes `\xNN`. Rust's own `{:?}` /
38+ /// `escape_default` is unsuitable here because it renders non-ASCII as `\u{...}`,
39+ /// which is not valid C.
40+ ///
41+ /// A `\xNN` escape in C greedily consumes every following hex digit, so when such an
42+ /// escape is followed by a literal hex-digit character the literal is split
43+ /// (`"...\xNN" "f..."`); adjacent string literals concatenate, keeping the escape a
44+ /// single byte.
45+ fn to_c_string_literal ( bytes : & [ u8 ] ) -> String {
46+ let mut out = String :: with_capacity ( bytes. len ( ) * 4 + 2 ) ;
47+ out. push ( '"' ) ;
48+ let mut prev_was_hex_escape = false ;
49+ for & byte in bytes {
50+ match byte {
51+ b'"' => out. push_str ( "\\ \" " ) ,
52+ b'\\' => out. push_str ( "\\ \\ " ) ,
53+ b'\n' => out. push_str ( "\\ n" ) ,
54+ b'\r' => out. push_str ( "\\ r" ) ,
55+ b'\t' => out. push_str ( "\\ t" ) ,
56+ // Escape `?` so no `??x` trigraph can form (trigraphs are translated
57+ // even inside string literals in C before C23 / C++ before C++17).
58+ b'?' => out. push_str ( "\\ ?" ) ,
59+ 0x20 ..=0x7e => {
60+ if prev_was_hex_escape && byte. is_ascii_hexdigit ( ) {
61+ out. push_str ( "\" \" " ) ;
62+ }
63+ out. push ( byte as char ) ;
64+ }
65+ _ => {
66+ let _ = write ! ( out, "\\ x{byte:02x}" ) ;
67+ prev_was_hex_escape = true ;
68+ continue ;
69+ }
70+ }
71+ prev_was_hex_escape = false ;
72+ }
73+ out. push ( '"' ) ;
74+ out
75+ }
76+
77+ /// Rewrite the Rust standard string reference types (`&str`, `&CStr`) a string
78+ /// constant carries into the C `char` pointer they are exposed as.
79+ ///
80+ /// Whether `ty` is the `str` / `CStr` path a `&str` / `&CStr` points to. Neither
81+ /// is a real C type, so a constant of that pointee is lowered to `char`.
82+ fn is_string_path ( ty : & Type ) -> bool {
83+ matches ! ( ty, Type :: Path ( path) if path. generics( ) . is_empty( ) && matches!( path. name( ) , "str" | "CStr" ) )
84+ }
85+
86+ /// Rewrites a `&str` / `&CStr` constant's type into its C form.
87+ ///
88+ /// A scalar `&str` / `&CStr` becomes `char[]` -- an array, not a pointer -- so
89+ /// `sizeof` yields the length at compile time and the declaration mirrors its
90+ /// string-literal initializer. Inside an array each element instead stays a
91+ /// `char *`: a `[&CStr; N]` maps to `const char *[N]`, since ragged element
92+ /// lengths can't form a `char[N][]`.
93+ ///
94+ /// In C the constant emits as a bare `#define`, so the type is unused; this only
95+ /// shapes the C++ typed-constant and Cython declarations.
96+ fn rewrite_string_reference_type ( ty : & mut Type ) {
97+ if matches ! ( ty, Type :: Ptr { ty, .. } if is_string_path( ty) ) {
98+ * ty = Type :: Array (
99+ Box :: new ( Type :: Primitive ( PrimitiveType :: Char ) ) ,
100+ ConstExpr :: Value ( String :: new ( ) ) ,
101+ ) ;
102+ } else if let Type :: Array ( inner, _) = ty {
103+ reduce_string_reference_pointee ( inner) ;
104+ }
105+ }
106+
107+ /// Reduces the `str` / `CStr` pointee of a `&str` / `&CStr` array element to
108+ /// `char`, keeping it a pointer, and recurses through nested arrays.
109+ fn reduce_string_reference_pointee ( ty : & mut Type ) {
110+ match ty {
111+ Type :: Ptr { ty, .. } if is_string_path ( ty) => {
112+ * * ty = Type :: Primitive ( PrimitiveType :: Char ) ;
113+ }
114+ Type :: Ptr { ty, .. } | Type :: Array ( ty, _) => reduce_string_reference_pointee ( ty) ,
115+ _ => { }
116+ }
117+ }
118+
119+ /// Whether `write_field` will itself render a leading `const` for `ty`, so the
120+ /// constant writers must not prepend a second one. True for a const pointer and
121+ /// for an array whose element is (recursively) a const pointer.
122+ fn write_field_prepends_const ( ty : & Type ) -> bool {
123+ match ty {
124+ Type :: Ptr { is_const, .. } => * is_const,
125+ Type :: Array ( inner, _) => write_field_prepends_const ( inner) ,
126+ _ => false ,
127+ }
128+ }
129+
130+ /// Loads a byte-string literal (`b"..."`) as a `uint8_t[N]` constant.
131+ ///
132+ /// Returns the constant's type and initializer, or `None` if `expr` is not a
133+ /// byte string. A byte string is exposed as a `uint8_t` array rather than a C
134+ /// string literal so that non-printable and non-NUL-terminated data survives (a
135+ /// `b"..."` is not NUL-terminated and may contain interior NULs). Its declared
136+ /// type (`&[u8; N]` or the unsized `&[u8]`) does not load into a bare array, so
137+ /// both the type and the initializer are derived together from the literal
138+ /// bytes -- which also makes the sized and unsized forms behave identically.
139+ fn load_byte_string ( expr : & syn:: Expr ) -> Option < ( Type , Literal ) > {
140+ let bytes = match expr {
141+ syn:: Expr :: Lit ( syn:: ExprLit {
142+ lit : syn:: Lit :: ByteStr ( value) ,
143+ ..
144+ } ) => value. value ( ) ,
145+ _ => return None ,
146+ } ;
147+
148+ let ty = Type :: Array (
149+ Box :: new ( Type :: Primitive ( PrimitiveType :: Integer {
150+ zeroable : true ,
151+ signed : false ,
152+ kind : IntKind :: B8 ,
153+ } ) ) ,
154+ ConstExpr :: Value ( bytes. len ( ) . to_string ( ) ) ,
155+ ) ;
156+ let lit = Literal :: Array {
157+ items : bytes
158+ . iter ( )
159+ . map ( |byte| Literal :: Expr ( byte. to_string ( ) ) )
160+ . collect ( ) ,
161+ } ;
162+ Some ( ( ty, lit) )
163+ }
164+
32165// TODO: Maybe add support to more std associated constants.
33166pub ( crate ) fn to_known_assoc_constant ( associated_to : & Path , name : & str ) -> Option < String > {
34- use crate :: bindgen:: ir:: { IntKind , PrimitiveType } ;
35-
36167 if name != "MAX" && name != "MIN" {
37168 return None ;
38169 }
@@ -413,7 +544,15 @@ impl Literal {
413544 Ok ( Literal :: Expr ( value. base10_digits ( ) . to_string ( ) ) )
414545 }
415546 syn:: Lit :: Bool ( ref value) => Ok ( Literal :: Expr ( format ! ( "{}" , value. value) ) ) ,
416- // TODO: Add support for byte string and Verbatim
547+ syn:: Lit :: Str ( ref value) => {
548+ Ok ( Literal :: Expr ( to_c_string_literal ( value. value ( ) . as_bytes ( ) ) ) )
549+ }
550+ syn:: Lit :: CStr ( ref value) => {
551+ Ok ( Literal :: Expr ( to_c_string_literal ( value. value ( ) . to_bytes ( ) ) ) )
552+ }
553+ // Byte-string literals are handled by `load_byte_string`, which
554+ // synthesizes the array type; nested byte strings stay unsupported.
555+ // TODO: Add support for Verbatim
417556 _ => Err ( format ! ( "Unsupported literal expression. {:?}" , * lit) ) ,
418557 }
419558 }
@@ -562,16 +701,16 @@ impl Constant {
562701 attrs : & [ syn:: Attribute ] ,
563702 associated_to : Option < Path > ,
564703 ) -> Result < Constant , String > {
565- let ty = Type :: load ( ty) ?;
566- let mut ty = match ty {
567- Some ( ty) => ty,
704+ let ( mut ty, mut lit) = match load_byte_string ( expr) {
705+ Some ( byte_string) => byte_string,
568706 None => {
569- return Err ( "Cannot have a zero sized const definition." . to_owned ( ) ) ;
707+ let mut ty = Type :: load ( ty) ?
708+ . ok_or_else ( || "Cannot have a zero sized const definition." . to_owned ( ) ) ?;
709+ rewrite_string_reference_type ( & mut ty) ;
710+ ( ty, Literal :: load ( expr) ?)
570711 }
571712 } ;
572713
573- let mut lit = Literal :: load ( expr) ?;
574-
575714 if let Some ( ref associated_to) = associated_to {
576715 ty. replace_self_with ( associated_to) ;
577716 lit. replace_self_with ( associated_to) ;
@@ -690,7 +829,7 @@ impl Constant {
690829
691830 let condition = self . cfg . to_condition ( config) ;
692831 condition. write_before ( config, out) ;
693- if let Type :: Ptr { is_const : true , .. } = self . ty {
832+ if write_field_prepends_const ( & self . ty ) {
694833 out. write ( "static " ) ;
695834 } else {
696835 out. write ( "static const " ) ;
@@ -790,9 +929,7 @@ impl Constant {
790929 out. write ( if in_body { "inline " } else { "static " } ) ;
791930 }
792931
793- if let Type :: Ptr { is_const : true , .. } = self . ty {
794- // Nothing.
795- } else {
932+ if !write_field_prepends_const ( & self . ty ) {
796933 out. write ( "const " ) ;
797934 }
798935 crate :: bindgen:: cdecl:: write_field ( language_backend, out, & self . ty , & name, config) ;
@@ -805,7 +942,9 @@ impl Constant {
805942 language_backend. write_literal ( out, value) ;
806943 }
807944 Language :: Cython => {
808- out. write ( "const " ) ;
945+ if !write_field_prepends_const ( & self . ty ) {
946+ out. write ( "const " ) ;
947+ }
809948 // For extern Cython declarations the initializer is ignored,
810949 // but still useful as documentation, so we write it as a comment.
811950 crate :: bindgen:: cdecl:: write_field ( language_backend, out, & self . ty , & name, config) ;
0 commit comments