Lukasz Anforowicz | ccf55cb | 2022-10-05 06:00:57 -0700 | [diff] [blame] | 1 | // Part of the Crubit project, under the Apache License v2.0 with LLVM |
| 2 | // Exceptions. See /LICENSE for license information. |
| 3 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 4 | |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 5 | use anyhow::{anyhow, ensure, Result}; |
Lukasz Anforowicz | 20a3c69 | 2022-10-06 08:48:15 -0700 | [diff] [blame] | 6 | use once_cell::sync::Lazy; |
Lukasz Anforowicz | e1aff8c | 2022-11-15 08:42:31 -0800 | [diff] [blame] | 7 | use proc_macro2::{Ident, TokenStream}; |
| 8 | use quote::{format_ident, quote, ToTokens}; |
Lukasz Anforowicz | 434c469 | 2022-11-01 14:05:24 -0700 | [diff] [blame] | 9 | use std::collections::{BTreeSet, HashSet}; |
| 10 | use std::rc::Rc; |
Lukasz Anforowicz | ccf55cb | 2022-10-05 06:00:57 -0700 | [diff] [blame] | 11 | |
| 12 | // TODO(lukasza): Consider adding more items into `code_gen_utils` (this crate). |
| 13 | // For example, the following items from `src_code_gen.rs` will be most likely |
| 14 | // reused from `cc_bindings_from_rs`: |
| 15 | // - `make_rs_ident` |
| 16 | // - `NamespaceQualifier` |
| 17 | |
Lukasz Anforowicz | e1aff8c | 2022-11-15 08:42:31 -0800 | [diff] [blame] | 18 | /// Formats a C++ identifier. Returns an error when `ident` is a C++ reserved |
| 19 | /// keyword or is an invalid identifier. |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 20 | pub fn format_cc_ident(ident: &str) -> Result<TokenStream> { |
Lukasz Anforowicz | c51aeb1 | 2022-11-07 10:56:18 -0800 | [diff] [blame] | 21 | ensure!(!ident.is_empty(), "Empty string is not a valid C++ identifier"); |
| 22 | |
Lukasz Anforowicz | 20a3c69 | 2022-10-06 08:48:15 -0700 | [diff] [blame] | 23 | // C++ doesn't have an equivalent of |
| 24 | // https://doc.rust-lang.org/rust-by-example/compatibility/raw_identifiers.html and therefore |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 25 | // an error is returned when `ident` is a C++ reserved keyword. |
| 26 | ensure!( |
Lukasz Anforowicz | 20a3c69 | 2022-10-06 08:48:15 -0700 | [diff] [blame] | 27 | !RESERVED_CC_KEYWORDS.contains(ident), |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 28 | "`{}` is a C++ reserved keyword and can't be used as a C++ identifier", |
Lukasz Anforowicz | 20a3c69 | 2022-10-06 08:48:15 -0700 | [diff] [blame] | 29 | ident |
| 30 | ); |
| 31 | |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 32 | ident.parse().map_err( |
| 33 | // Explicitly mapping the error via `anyhow!`, because `LexError` is not `Sync` |
| 34 | // (required for `anyhow::Error` to implement `From<LexError>`) and |
| 35 | // therefore we can't just use `?`. |
| 36 | |lex_error| anyhow!("Can't format `{ident}` as a C++ identifier: {lex_error}"), |
| 37 | ) |
Lukasz Anforowicz | ccf55cb | 2022-10-05 06:00:57 -0700 | [diff] [blame] | 38 | } |
| 39 | |
Lukasz Anforowicz | e1aff8c | 2022-11-15 08:42:31 -0800 | [diff] [blame] | 40 | /// Makes an 'Ident' to be used in the Rust source code. Escapes Rust keywords. |
| 41 | /// Panics if `ident` is empty or is otherwise an invalid identifier. |
| 42 | pub fn make_rs_ident(ident: &str) -> Ident { |
| 43 | // TODO(https://github.com/dtolnay/syn/pull/1098): Remove the hardcoded list once syn recognizes |
| 44 | // 2018 and 2021 keywords. |
| 45 | if ["async", "await", "try", "dyn"].contains(&ident) { |
| 46 | return format_ident!("r#{}", ident); |
| 47 | } |
| 48 | match syn::parse_str::<syn::Ident>(ident) { |
| 49 | Ok(_) => format_ident!("{}", ident), |
| 50 | Err(_) => format_ident!("r#{}", ident), |
| 51 | } |
| 52 | } |
| 53 | |
Lukasz Anforowicz | 35ba2fe | 2022-11-23 15:56:19 -0800 | [diff] [blame] | 54 | /// Representation of `foo::bar::baz::` where each component is either the name |
| 55 | /// of a C++ namespace, or the name of a Rust module. |
| 56 | #[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)] |
| 57 | // TODO(b/258265044): Make the `Vec<String>` payload private + guarantee |
| 58 | // additional invariants in an explicit, public `new` method. This will help to |
| 59 | // catch some error conditions early (e.g. an empty path component may trigger a |
| 60 | // panic in `make_rs_ident`; a reserved C++ keyword might trigger a late error |
| 61 | // in `format_for_cc` / `format_cc_ident`). |
Lukasz Anforowicz | 8c1a6c4 | 2022-11-23 16:18:09 -0800 | [diff] [blame] | 62 | pub struct NamespaceQualifier(pub Vec<Rc<str>>); |
Lukasz Anforowicz | 35ba2fe | 2022-11-23 15:56:19 -0800 | [diff] [blame] | 63 | |
| 64 | impl NamespaceQualifier { |
| 65 | pub fn format_for_rs(&self) -> TokenStream { |
| 66 | let namespace_rs_idents = self.0.iter().map(|ns| make_rs_ident(ns)); |
| 67 | quote! { #(#namespace_rs_idents::)* } |
| 68 | } |
| 69 | |
| 70 | pub fn format_for_cc(&self) -> Result<TokenStream> { |
Lukasz Anforowicz | a577d82 | 2022-12-12 15:00:46 -0800 | [diff] [blame] | 71 | let namespace_cc_idents = self.cc_idents()?; |
Lukasz Anforowicz | 35ba2fe | 2022-11-23 15:56:19 -0800 | [diff] [blame] | 72 | Ok(quote! { #(#namespace_cc_idents::)* }) |
| 73 | } |
Lukasz Anforowicz | a577d82 | 2022-12-12 15:00:46 -0800 | [diff] [blame] | 74 | |
Lukasz Anforowicz | 54efc16 | 2022-12-16 15:58:44 -0800 | [diff] [blame^] | 75 | fn format_with_cc_body(&self, body: TokenStream) -> Result<TokenStream> { |
Lukasz Anforowicz | a577d82 | 2022-12-12 15:00:46 -0800 | [diff] [blame] | 76 | if self.0.is_empty() { |
| 77 | Ok(body) |
| 78 | } else { |
| 79 | let namespace_cc_idents = self.cc_idents()?; |
| 80 | Ok(quote! { |
| 81 | namespace #(#namespace_cc_idents)::* { |
| 82 | #body |
| 83 | } |
| 84 | }) |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | fn cc_idents(&self) -> Result<Vec<TokenStream>> { |
| 89 | self.0.iter().map(|ns| format_cc_ident(ns)).collect() |
| 90 | } |
Lukasz Anforowicz | 35ba2fe | 2022-11-23 15:56:19 -0800 | [diff] [blame] | 91 | } |
| 92 | |
Lukasz Anforowicz | 54efc16 | 2022-12-16 15:58:44 -0800 | [diff] [blame^] | 93 | /// `format_namespace_bound_cc_tokens` formats a sequence of namespace-bound |
| 94 | /// snippets. For example, `[(ns, tokens)]` will be formatted as: |
| 95 | /// |
| 96 | /// ``` |
| 97 | /// namespace ns { |
| 98 | /// #tokens |
| 99 | /// } |
| 100 | /// ``` |
| 101 | /// |
| 102 | /// `format_namespace_bound_cc_tokens` tries to give a nice-looking output - for |
| 103 | /// example it combines consecutive items that belong to the same namespace, |
| 104 | /// when given `[(ns, tokens1), (ns, tokens2)]` as input: |
| 105 | /// |
| 106 | /// ``` |
| 107 | /// namespace ns { |
| 108 | /// #tokens1 |
| 109 | /// #tokens2 |
| 110 | /// } |
| 111 | /// ``` |
| 112 | /// |
| 113 | /// `format_namespace_bound_cc_tokens` also knows that top-level items (e.g. |
| 114 | /// ones where `NamespaceQualifier` doesn't contain any namespace names) should |
| 115 | /// be emitted at the top-level (not nesting them under a `namespace` keyword). |
| 116 | /// For example, `[(toplevel_ns, tokens)]` will be formatted as just: |
| 117 | /// |
| 118 | /// ``` |
| 119 | /// #tokens |
| 120 | /// ``` |
| 121 | pub fn format_namespace_bound_cc_tokens( |
| 122 | iter: impl IntoIterator<Item = (NamespaceQualifier, TokenStream)>, |
| 123 | ) -> Result<TokenStream> { |
| 124 | let mut iter = iter.into_iter().peekable(); |
| 125 | let mut tokens_in_curr_ns = Vec::with_capacity(iter.size_hint().0); |
| 126 | let mut result = TokenStream::default(); |
| 127 | while let Some((curr_ns, tokens)) = iter.next() { |
| 128 | tokens_in_curr_ns.push(tokens); |
| 129 | |
| 130 | // Flush `tokens_in_curr_ns` when at the end of the current namespace. |
| 131 | let next_ns = iter.peek().map(|(next_ns, _)| next_ns); |
| 132 | if next_ns != Some(&curr_ns) { |
| 133 | let tokens_in_curr_ns = tokens_in_curr_ns.drain(..); |
| 134 | result.extend(curr_ns.format_with_cc_body(quote! { #(#tokens_in_curr_ns)* })?); |
| 135 | } |
| 136 | |
| 137 | // Separate namespaces with a single empty line. |
| 138 | if iter.peek().is_some() { |
| 139 | result.extend(quote! { __NEWLINE__ __NEWLINE__ }); |
| 140 | } |
| 141 | } |
| 142 | Ok(result) |
| 143 | } |
| 144 | |
Lukasz Anforowicz | 434c469 | 2022-11-01 14:05:24 -0700 | [diff] [blame] | 145 | /// `CcInclude` represents a single `#include ...` directive in C++. |
| 146 | #[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] |
| 147 | pub enum CcInclude { |
| 148 | SystemHeader(&'static str), |
| 149 | UserHeader(Rc<str>), |
| 150 | } |
| 151 | |
| 152 | impl CcInclude { |
| 153 | /// Creates a `CcInclude` that represents `#include <cstddef>` and provides |
| 154 | /// C++ types like `std::size_t` or `std::ptrdiff_t`. See also |
| 155 | /// https://en.cppreference.com/w/cpp/header/cstddef |
| 156 | pub fn cstddef() -> Self { |
| 157 | Self::SystemHeader("cstddef") |
| 158 | } |
| 159 | |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 160 | /// Creates a `CcInclude` that represents `#include <cstdint>` and provides |
| 161 | /// C++ types like `std::int16_t` or `std::uint32_t`. See also |
| 162 | /// https://en.cppreference.com/w/cpp/header/cstdint |
| 163 | pub fn cstdint() -> Self { |
| 164 | Self::SystemHeader("cstdint") |
| 165 | } |
| 166 | |
Lukasz Anforowicz | 434c469 | 2022-11-01 14:05:24 -0700 | [diff] [blame] | 167 | /// Creates a `CcInclude` that represents `#include <memory>`. |
| 168 | /// See also https://en.cppreference.com/w/cpp/header/memory |
| 169 | pub fn memory() -> Self { |
| 170 | Self::SystemHeader("memory") |
| 171 | } |
| 172 | |
Lukasz Anforowicz | d16b6bf | 2022-11-22 18:35:08 -0800 | [diff] [blame] | 173 | /// Creates a `CcInclude` that represents `#include <utility>` and provides |
| 174 | /// C++ functions like `std::move` and C++ types like `std::tuple`. |
| 175 | /// See also https://en.cppreference.com/w/cpp/header/utility |
| 176 | pub fn utility() -> Self { |
| 177 | Self::SystemHeader("utility") |
| 178 | } |
| 179 | |
Lukasz Anforowicz | 434c469 | 2022-11-01 14:05:24 -0700 | [diff] [blame] | 180 | /// Creates a user include: `#include "some/path/to/header.h"`. |
| 181 | pub fn user_header(path: Rc<str>) -> Self { |
| 182 | Self::UserHeader(path) |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | impl ToTokens for CcInclude { |
| 187 | fn to_tokens(&self, tokens: &mut TokenStream) { |
| 188 | match self { |
| 189 | Self::SystemHeader(path) => { |
| 190 | let path: TokenStream = path |
| 191 | .parse() |
| 192 | .expect("`pub` API of `CcInclude` guarantees validity of system includes"); |
| 193 | quote! { __HASH_TOKEN__ include < #path > __NEWLINE__ }.to_tokens(tokens) |
| 194 | } |
| 195 | Self::UserHeader(path) => { |
| 196 | quote! { __HASH_TOKEN__ include #path __NEWLINE__ }.to_tokens(tokens) |
| 197 | } |
| 198 | } |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | /// Formats a set of `CcInclude`s, trying to follow the guidance from |
| 203 | /// [the Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Names_and_Order_of_Includes). |
| 204 | pub fn format_cc_includes(set_of_includes: &BTreeSet<CcInclude>) -> TokenStream { |
| 205 | let mut tokens = TokenStream::default(); |
| 206 | let mut iter = set_of_includes.iter().peekable(); |
| 207 | while let Some(include) = iter.next() { |
| 208 | include.to_tokens(&mut tokens); |
| 209 | |
| 210 | // Add an empty line between system headers and user headers. |
| 211 | if let (CcInclude::SystemHeader(_), Some(CcInclude::UserHeader(_))) = (include, iter.peek()) |
| 212 | { |
| 213 | quote! { __NEWLINE__ }.to_tokens(&mut tokens) |
| 214 | } |
| 215 | } |
| 216 | tokens |
| 217 | } |
| 218 | |
Lukasz Anforowicz | 20a3c69 | 2022-10-06 08:48:15 -0700 | [diff] [blame] | 219 | static RESERVED_CC_KEYWORDS: Lazy<HashSet<&'static str>> = Lazy::new(|| { |
| 220 | // `RESERVED_CC_KEYWORDS` are based on https://en.cppreference.com/w/cpp/keyword |
| 221 | [ |
| 222 | "alignas", |
| 223 | "alignof", |
| 224 | "and", |
| 225 | "and_eq", |
| 226 | "asm", |
| 227 | "atomic_cancel", |
| 228 | "atomic_commit", |
| 229 | "atomic_noexcept", |
| 230 | "auto", |
| 231 | "bitand", |
| 232 | "bitor", |
| 233 | "bool", |
| 234 | "break", |
| 235 | "case", |
| 236 | "catch", |
| 237 | "char", |
| 238 | "char8_t", |
| 239 | "char16_t", |
| 240 | "char32_t", |
| 241 | "class", |
| 242 | "compl", |
| 243 | "concept", |
| 244 | "const", |
| 245 | "consteval", |
| 246 | "constexpr", |
| 247 | "constinit", |
| 248 | "const_cast", |
| 249 | "continue", |
| 250 | "co_await", |
| 251 | "co_return", |
| 252 | "co_yield", |
| 253 | "decltype", |
| 254 | "default", |
| 255 | "delete", |
| 256 | "do", |
| 257 | "double", |
| 258 | "dynamic_cast", |
| 259 | "else", |
| 260 | "enum", |
| 261 | "explicit", |
| 262 | "export", |
| 263 | "extern", |
| 264 | "false", |
| 265 | "float", |
| 266 | "for", |
| 267 | "friend", |
| 268 | "goto", |
| 269 | "if", |
| 270 | "inline", |
| 271 | "int", |
| 272 | "long", |
| 273 | "mutable", |
| 274 | "namespace", |
| 275 | "new", |
| 276 | "noexcept", |
| 277 | "not", |
| 278 | "not_eq", |
| 279 | "nullptr", |
| 280 | "operator", |
| 281 | "or", |
| 282 | "or_eq", |
| 283 | "private", |
| 284 | "protected", |
| 285 | "public", |
| 286 | "reflexpr", |
| 287 | "register", |
| 288 | "reinterpret_cast", |
| 289 | "requires", |
| 290 | "return", |
| 291 | "short", |
| 292 | "signed", |
| 293 | "sizeof", |
| 294 | "static", |
| 295 | "static_assert", |
| 296 | "static_cast", |
| 297 | "struct", |
| 298 | "switch", |
| 299 | "synchronized", |
| 300 | "template", |
| 301 | "this", |
| 302 | "thread_local", |
| 303 | "throw", |
| 304 | "true", |
| 305 | "try", |
| 306 | "typedef", |
| 307 | "typeid", |
| 308 | "typename", |
| 309 | "union", |
| 310 | "unsigned", |
| 311 | "using", |
| 312 | "virtual", |
| 313 | "void", |
| 314 | "volatile", |
| 315 | "wchar_t", |
| 316 | "while", |
| 317 | "xor", |
| 318 | "xor_eq", |
| 319 | ] |
| 320 | .into_iter() |
| 321 | .collect() |
| 322 | }); |
| 323 | |
Lukasz Anforowicz | ccf55cb | 2022-10-05 06:00:57 -0700 | [diff] [blame] | 324 | #[cfg(test)] |
| 325 | pub mod tests { |
| 326 | use super::*; |
| 327 | use quote::quote; |
Lukasz Anforowicz | e1aff8c | 2022-11-15 08:42:31 -0800 | [diff] [blame] | 328 | use token_stream_matchers::{assert_cc_matches, assert_rs_matches}; |
Lukasz Anforowicz | 5bf4943 | 2022-12-12 12:17:24 -0800 | [diff] [blame] | 329 | use token_stream_printer::cc_tokens_to_formatted_string_for_tests; |
Lukasz Anforowicz | ccf55cb | 2022-10-05 06:00:57 -0700 | [diff] [blame] | 330 | |
| 331 | #[test] |
| 332 | fn test_format_cc_ident_basic() { |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 333 | assert_cc_matches!( |
Lukasz Anforowicz | 4c19ad9 | 2022-12-16 15:23:14 -0800 | [diff] [blame] | 334 | format_cc_ident("foo").unwrap(), |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 335 | quote! { foo } |
| 336 | ); |
Lukasz Anforowicz | ccf55cb | 2022-10-05 06:00:57 -0700 | [diff] [blame] | 337 | } |
| 338 | |
| 339 | #[test] |
| 340 | fn test_format_cc_ident_reserved_rust_keyword() { |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 341 | assert_cc_matches!( |
Lukasz Anforowicz | 4c19ad9 | 2022-12-16 15:23:14 -0800 | [diff] [blame] | 342 | format_cc_ident("impl").unwrap(), |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 343 | quote! { impl } |
| 344 | ); |
Lukasz Anforowicz | ccf55cb | 2022-10-05 06:00:57 -0700 | [diff] [blame] | 345 | } |
| 346 | |
| 347 | #[test] |
| 348 | fn test_format_cc_ident_reserved_cc_keyword() { |
Lukasz Anforowicz | 4c19ad9 | 2022-12-16 15:23:14 -0800 | [diff] [blame] | 349 | let err = format_cc_ident("reinterpret_cast").unwrap_err(); |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 350 | let msg = err.to_string(); |
| 351 | assert!(msg.contains("`reinterpret_cast`")); |
| 352 | assert!(msg.contains("C++ reserved keyword")); |
| 353 | } |
| 354 | |
| 355 | #[test] |
| 356 | fn test_format_cc_ident_unfinished_group() { |
| 357 | let err = format_cc_ident("(foo") // No closing `)`. |
Lukasz Anforowicz | 4c19ad9 | 2022-12-16 15:23:14 -0800 | [diff] [blame] | 358 | .unwrap_err(); |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 359 | let msg = err.to_string(); |
| 360 | assert!(msg.contains("Can't format `(foo` as a C++ identifier")); |
| 361 | assert!(msg.contains("cannot parse")); |
| 362 | } |
| 363 | |
| 364 | #[test] |
| 365 | fn test_format_cc_ident_unqualified_identifiers() { |
| 366 | // https://en.cppreference.com/w/cpp/language/identifiers#Unqualified_identifiers |
| 367 | |
| 368 | // These may appear in `IR::Func::name`. |
| 369 | assert_cc_matches!( |
Lukasz Anforowicz | 4c19ad9 | 2022-12-16 15:23:14 -0800 | [diff] [blame] | 370 | format_cc_ident("operator==").unwrap(), |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 371 | quote! { operator== } |
| 372 | ); |
| 373 | assert_cc_matches!( |
Lukasz Anforowicz | 4c19ad9 | 2022-12-16 15:23:14 -0800 | [diff] [blame] | 374 | format_cc_ident("operator new").unwrap(), |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 375 | quote! { operator new } |
| 376 | ); |
| 377 | |
| 378 | // This may appear in `IR::Record::cc_name` (although in practice these will |
| 379 | // be namespace-qualified most of the time). |
| 380 | assert_cc_matches!( |
Lukasz Anforowicz | 4c19ad9 | 2022-12-16 15:23:14 -0800 | [diff] [blame] | 381 | format_cc_ident("MyTemplate<int>").unwrap(), |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 382 | quote! { MyTemplate<int> } |
| 383 | ); |
| 384 | |
| 385 | // These forms of unqualified identifiers are not used by Crubit in practice, |
| 386 | assert_cc_matches!( |
Lukasz Anforowicz | 4c19ad9 | 2022-12-16 15:23:14 -0800 | [diff] [blame] | 387 | format_cc_ident("~MyClass").unwrap(), |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 388 | quote! { ~MyClass } |
| 389 | ); |
| 390 | assert_cc_matches!( |
Lukasz Anforowicz | 4c19ad9 | 2022-12-16 15:23:14 -0800 | [diff] [blame] | 391 | format_cc_ident(r#" operator "" _km "#).unwrap(), |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 392 | quote! { operator "" _km } |
| 393 | ); |
| 394 | } |
| 395 | |
| 396 | #[test] |
Lukasz Anforowicz | c51aeb1 | 2022-11-07 10:56:18 -0800 | [diff] [blame] | 397 | fn test_format_cc_ident_empty() { |
Lukasz Anforowicz | 4c19ad9 | 2022-12-16 15:23:14 -0800 | [diff] [blame] | 398 | let err = format_cc_ident("").unwrap_err(); |
Lukasz Anforowicz | c51aeb1 | 2022-11-07 10:56:18 -0800 | [diff] [blame] | 399 | let msg = err.to_string(); |
| 400 | assert_eq!(msg, "Empty string is not a valid C++ identifier"); |
| 401 | } |
| 402 | |
| 403 | #[test] |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 404 | fn test_format_cc_ident_qualified_identifiers() { |
| 405 | // https://en.cppreference.com/w/cpp/language/identifiers#Qualified_identifiers |
| 406 | |
| 407 | // This may appear in `IR::Record::cc_name`. |
| 408 | assert_cc_matches!( |
Lukasz Anforowicz | 4c19ad9 | 2022-12-16 15:23:14 -0800 | [diff] [blame] | 409 | format_cc_ident("std::vector<int>").unwrap(), |
Lukasz Anforowicz | 5561689 | 2022-10-06 09:16:57 -0700 | [diff] [blame] | 410 | quote! { std::vector<int> } |
| 411 | ); |
Lukasz Anforowicz | ccf55cb | 2022-10-05 06:00:57 -0700 | [diff] [blame] | 412 | } |
Lukasz Anforowicz | 434c469 | 2022-11-01 14:05:24 -0700 | [diff] [blame] | 413 | |
| 414 | #[test] |
Lukasz Anforowicz | e1aff8c | 2022-11-15 08:42:31 -0800 | [diff] [blame] | 415 | fn test_make_rs_ident_basic() { |
| 416 | let id = make_rs_ident("foo"); |
| 417 | assert_rs_matches!(quote! { #id }, quote! { foo }); |
| 418 | } |
| 419 | |
| 420 | #[test] |
| 421 | fn test_make_rs_ident_reserved_cc_keyword() { |
| 422 | let id = make_rs_ident("reinterpret_cast"); |
| 423 | assert_rs_matches!(quote! { #id }, quote! { reinterpret_cast }); |
| 424 | } |
| 425 | |
| 426 | #[test] |
| 427 | fn test_make_rs_ident_reserved_rust_keyword() { |
| 428 | let id = make_rs_ident("impl"); |
| 429 | assert_rs_matches!(quote! { #id }, quote! { r#impl }); |
| 430 | } |
| 431 | |
| 432 | #[test] |
| 433 | #[should_panic] |
| 434 | fn test_make_rs_ident_unfinished_group() { |
| 435 | make_rs_ident("(foo"); // No closing `)`. |
| 436 | } |
| 437 | |
| 438 | #[test] |
| 439 | #[should_panic] |
| 440 | fn test_make_rs_ident_empty() { |
| 441 | make_rs_ident(""); |
| 442 | } |
| 443 | |
| 444 | #[test] |
Lukasz Anforowicz | 434c469 | 2022-11-01 14:05:24 -0700 | [diff] [blame] | 445 | fn test_cc_include_to_tokens_for_system_header() { |
| 446 | let include = CcInclude::cstddef(); |
| 447 | assert_cc_matches!( |
| 448 | quote! { #include }, |
| 449 | quote! { |
| 450 | __HASH_TOKEN__ include <cstddef> |
| 451 | } |
| 452 | ); |
| 453 | } |
| 454 | |
| 455 | #[test] |
| 456 | fn test_cc_include_to_tokens_for_user_header() { |
| 457 | let include = CcInclude::user_header("some/path/to/header.h".into()); |
| 458 | assert_cc_matches!( |
| 459 | quote! { #include }, |
| 460 | quote! { |
| 461 | __HASH_TOKEN__ include "some/path/to/header.h" |
| 462 | } |
| 463 | ); |
| 464 | } |
| 465 | |
| 466 | #[test] |
| 467 | fn test_cc_include_ord() { |
| 468 | let cstddef = CcInclude::cstddef(); |
| 469 | let memory = CcInclude::memory(); |
| 470 | let a = CcInclude::user_header("a.h".into()); |
| 471 | let b = CcInclude::user_header("b.h".into()); |
| 472 | assert!(cstddef < memory); |
| 473 | assert!(cstddef < a); |
| 474 | assert!(cstddef < b); |
| 475 | assert!(memory < a); |
| 476 | assert!(memory < b); |
| 477 | assert!(a < b); |
| 478 | } |
| 479 | |
| 480 | #[test] |
| 481 | fn test_format_cc_includes() { |
| 482 | let includes = [ |
| 483 | CcInclude::cstddef(), |
| 484 | CcInclude::memory(), |
| 485 | CcInclude::user_header("a.h".into()), |
| 486 | CcInclude::user_header("b.h".into()), |
| 487 | ] |
| 488 | .into_iter() |
| 489 | .collect::<BTreeSet<_>>(); |
| 490 | |
| 491 | let tokens = format_cc_includes(&includes); |
Lukasz Anforowicz | 5bf4943 | 2022-12-12 12:17:24 -0800 | [diff] [blame] | 492 | let actual = |
| 493 | cc_tokens_to_formatted_string_for_tests(quote! { __NEWLINE__ #tokens }).unwrap(); |
Lukasz Anforowicz | 434c469 | 2022-11-01 14:05:24 -0700 | [diff] [blame] | 494 | assert_eq!( |
| 495 | actual, |
| 496 | r#" |
| 497 | #include <cstddef> |
| 498 | #include <memory> |
| 499 | |
| 500 | #include "a.h" |
| 501 | #include "b.h" |
| 502 | "# |
| 503 | ); |
| 504 | } |
Lukasz Anforowicz | 35ba2fe | 2022-11-23 15:56:19 -0800 | [diff] [blame] | 505 | |
| 506 | fn create_namespace_qualifier_for_tests(input: &[&str]) -> NamespaceQualifier { |
Lukasz Anforowicz | 8c1a6c4 | 2022-11-23 16:18:09 -0800 | [diff] [blame] | 507 | NamespaceQualifier(input.into_iter().map(|&s| s.into()).collect()) |
Lukasz Anforowicz | 35ba2fe | 2022-11-23 15:56:19 -0800 | [diff] [blame] | 508 | } |
| 509 | |
| 510 | #[test] |
| 511 | fn test_namespace_qualifier_empty() { |
| 512 | let ns = create_namespace_qualifier_for_tests(&[]); |
| 513 | let actual_rs = ns.format_for_rs(); |
| 514 | assert!(actual_rs.is_empty()); |
| 515 | let actual_cc = ns.format_for_cc().unwrap(); |
| 516 | assert!(actual_cc.is_empty()); |
| 517 | } |
| 518 | |
| 519 | #[test] |
| 520 | fn test_namespace_qualifier_basic() { |
| 521 | let ns = create_namespace_qualifier_for_tests(&["foo", "bar"]); |
| 522 | let actual_rs = ns.format_for_rs(); |
| 523 | assert_rs_matches!(actual_rs, quote! { foo::bar:: }); |
| 524 | let actual_cc = ns.format_for_cc().unwrap(); |
| 525 | assert_cc_matches!(actual_cc, quote! { foo::bar:: }); |
| 526 | } |
| 527 | |
| 528 | #[test] |
| 529 | fn test_namespace_qualifier_reserved_cc_keyword() { |
| 530 | let ns = create_namespace_qualifier_for_tests(&["foo", "impl", "bar"]); |
| 531 | let actual_rs = ns.format_for_rs(); |
| 532 | assert_rs_matches!(actual_rs, quote! { foo :: r#impl :: bar :: }); |
| 533 | let actual_cc = ns.format_for_cc().unwrap(); |
| 534 | assert_cc_matches!(actual_cc, quote! { foo::impl::bar:: }); |
| 535 | } |
| 536 | |
| 537 | #[test] |
| 538 | fn test_namespace_qualifier_reserved_rust_keyword() { |
| 539 | let ns = create_namespace_qualifier_for_tests(&["foo", "reinterpret_cast", "bar"]); |
| 540 | let actual_rs = ns.format_for_rs(); |
| 541 | assert_rs_matches!(actual_rs, quote! { foo :: reinterpret_cast :: bar :: }); |
Lukasz Anforowicz | 4c19ad9 | 2022-12-16 15:23:14 -0800 | [diff] [blame] | 542 | let cc_error = ns.format_for_cc().unwrap_err(); |
Lukasz Anforowicz | 35ba2fe | 2022-11-23 15:56:19 -0800 | [diff] [blame] | 543 | let msg = cc_error.to_string(); |
| 544 | assert!(msg.contains("`reinterpret_cast`")); |
| 545 | assert!(msg.contains("C++ reserved keyword")); |
| 546 | } |
Lukasz Anforowicz | a577d82 | 2022-12-12 15:00:46 -0800 | [diff] [blame] | 547 | |
| 548 | #[test] |
| 549 | fn test_namespace_qualifier_format_with_cc_body_top_level_namespace() { |
| 550 | let ns = create_namespace_qualifier_for_tests(&[]); |
| 551 | assert_cc_matches!( |
| 552 | ns.format_with_cc_body(quote! { cc body goes here }).unwrap(), |
| 553 | quote! { cc body goes here }, |
| 554 | ); |
| 555 | } |
| 556 | |
| 557 | #[test] |
| 558 | fn test_namespace_qualifier_format_with_cc_body_nested_namespace() { |
| 559 | let ns = create_namespace_qualifier_for_tests(&["foo", "bar", "baz"]); |
| 560 | assert_cc_matches!( |
| 561 | ns.format_with_cc_body(quote! { cc body goes here }).unwrap(), |
| 562 | quote! { |
| 563 | namespace foo::bar::baz { |
| 564 | cc body goes here |
| 565 | } // namespace foo::bar::baz |
| 566 | }, |
| 567 | ); |
| 568 | } |
Lukasz Anforowicz | 54efc16 | 2022-12-16 15:58:44 -0800 | [diff] [blame^] | 569 | |
| 570 | #[test] |
| 571 | fn test_format_namespace_bound_cc_tokens() { |
| 572 | let top_level = create_namespace_qualifier_for_tests(&[]); |
| 573 | let m1 = create_namespace_qualifier_for_tests(&["m1"]); |
| 574 | let m2 = create_namespace_qualifier_for_tests(&["m2"]); |
| 575 | let input = vec![ |
| 576 | (top_level.clone(), quote! { void f0a(); }), |
| 577 | (m1.clone(), quote! { void f1a(); }), |
| 578 | (m1.clone(), quote! { void f1b(); }), |
| 579 | (top_level.clone(), quote! { void f0b(); }), |
| 580 | (top_level.clone(), quote! { void f0c(); }), |
| 581 | (m2.clone(), quote! { void f2a(); }), |
| 582 | (m1.clone(), quote! { void f1c(); }), |
| 583 | (m1.clone(), quote! { void f1d(); }), |
| 584 | ]; |
| 585 | assert_cc_matches!( |
| 586 | format_namespace_bound_cc_tokens(input).unwrap(), |
| 587 | quote! { |
| 588 | void f0a(); |
| 589 | |
| 590 | namespace m1 { |
| 591 | void f1a(); |
| 592 | void f1b(); |
| 593 | } // namespace m1 |
| 594 | |
| 595 | void f0b(); |
| 596 | void f0c(); |
| 597 | |
| 598 | namespace m2 { |
| 599 | void f2a(); |
| 600 | } |
| 601 | |
| 602 | namespace m1 { |
| 603 | void f1c(); |
| 604 | void f1d(); |
| 605 | } // namespace m1 |
| 606 | }, |
| 607 | ); |
| 608 | } |
Lukasz Anforowicz | ccf55cb | 2022-10-05 06:00:57 -0700 | [diff] [blame] | 609 | } |