Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -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 | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 5 | use anyhow::{anyhow, bail, Context, Result}; |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 6 | use code_gen_utils::{format_cc_ident, format_cc_includes, CcInclude}; |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 7 | use itertools::Itertools; |
Lukasz Anforowicz | 2b38d27 | 2022-09-23 08:08:18 -0700 | [diff] [blame] | 8 | use proc_macro2::TokenStream; |
| 9 | use quote::quote; |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 10 | use rustc_hir::{Item, ItemKind, Node, Unsafety}; |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 11 | use rustc_interface::Queries; |
Lukasz Anforowicz | ed1c4f0 | 2022-09-29 11:11:20 -0700 | [diff] [blame] | 12 | use rustc_middle::dep_graph::DepContext; |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 13 | use rustc_middle::middle::exported_symbols::ExportedSymbol; |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 14 | use rustc_middle::ty::{self, Ty, TyCtxt}; // See <internal link>/ty.html#import-conventions |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 15 | use rustc_span::def_id::{DefId, LocalDefId, LOCAL_CRATE}; |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 16 | use rustc_target::spec::abi::Abi; |
Lukasz Anforowicz | 24160d5 | 2022-10-19 06:45:45 -0700 | [diff] [blame] | 17 | use rustc_target::spec::PanicStrategy; |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 18 | use std::collections::BTreeSet; |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 19 | use std::iter::Sum; |
| 20 | use std::ops::{Add, AddAssign}; |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 21 | |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 22 | pub struct GeneratedBindings { |
Lukasz Anforowicz | 2b38d27 | 2022-09-23 08:08:18 -0700 | [diff] [blame] | 23 | pub h_body: TokenStream, |
Lukasz Anforowicz | e7a2500 | 2022-11-10 06:21:42 -0800 | [diff] [blame^] | 24 | pub rs_body: TokenStream, |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 25 | } |
| 26 | |
| 27 | impl GeneratedBindings { |
Lukasz Anforowicz | 24160d5 | 2022-10-19 06:45:45 -0700 | [diff] [blame] | 28 | pub fn generate(tcx: TyCtxt) -> Result<Self> { |
| 29 | match tcx.sess().panic_strategy() { |
| 30 | PanicStrategy::Unwind => bail!("No support for panic=unwind strategy (b/254049425)"), |
| 31 | PanicStrategy::Abort => (), |
| 32 | }; |
| 33 | |
Lukasz Anforowicz | d9ff4ab | 2022-09-23 08:11:18 -0700 | [diff] [blame] | 34 | let top_comment = { |
| 35 | let crate_name = tcx.crate_name(LOCAL_CRATE); |
| 36 | let txt = format!( |
| 37 | "Automatically @generated C++ bindings for the following Rust crate:\n\ |
| 38 | {crate_name}" |
| 39 | ); |
| 40 | quote! { __COMMENT__ #txt __NEWLINE__ } |
| 41 | }; |
| 42 | |
| 43 | let h_body = { |
Lukasz Anforowicz | 4aa1ff9 | 2022-10-10 11:22:22 -0700 | [diff] [blame] | 44 | let crate_content = format_crate(tcx).unwrap_or_else(|err| { |
Lukasz Anforowicz | d0f0a84 | 2022-11-03 12:40:13 -0700 | [diff] [blame] | 45 | let txt = format!("Failed to generate bindings for the crate: {err}"); |
Lukasz Anforowicz | 4aa1ff9 | 2022-10-10 11:22:22 -0700 | [diff] [blame] | 46 | quote! { __COMMENT__ #txt } |
| 47 | }); |
Lukasz Anforowicz | 31b29cd | 2022-10-10 11:33:41 -0700 | [diff] [blame] | 48 | // TODO(b/251445877): Replace `#pragma once` with include guards. |
Lukasz Anforowicz | 4aa1ff9 | 2022-10-10 11:22:22 -0700 | [diff] [blame] | 49 | quote! { |
| 50 | #top_comment |
Lukasz Anforowicz | 31b29cd | 2022-10-10 11:33:41 -0700 | [diff] [blame] | 51 | __HASH_TOKEN__ pragma once __NEWLINE__ |
| 52 | __NEWLINE__ |
Lukasz Anforowicz | 4aa1ff9 | 2022-10-10 11:22:22 -0700 | [diff] [blame] | 53 | #crate_content |
| 54 | } |
Lukasz Anforowicz | d9ff4ab | 2022-09-23 08:11:18 -0700 | [diff] [blame] | 55 | }; |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 56 | |
Lukasz Anforowicz | e7a2500 | 2022-11-10 06:21:42 -0800 | [diff] [blame^] | 57 | let rs_body = quote! { |
| 58 | #top_comment |
| 59 | |
| 60 | // TODO(b/254097223): Include Rust thunks here. |
| 61 | }; |
| 62 | |
| 63 | Ok(Self { h_body, rs_body }) |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 64 | } |
| 65 | } |
| 66 | |
Lukasz Anforowicz | ed1c4f0 | 2022-09-29 11:11:20 -0700 | [diff] [blame] | 67 | /// Helper (used by `bindings_driver` and `test::run_compiler`) for invoking |
| 68 | /// functions operating on `TyCtxt`. |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 69 | pub fn enter_tcx<'tcx, F, T>( |
| 70 | queries: &'tcx Queries<'tcx>, |
| 71 | f: F, |
| 72 | ) -> rustc_interface::interface::Result<T> |
| 73 | where |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 74 | F: FnOnce(TyCtxt<'tcx>) -> T + Send, |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 75 | T: Send, |
| 76 | { |
| 77 | let query_context = queries.global_ctxt()?; |
| 78 | Ok(query_context.peek_mut().enter(f)) |
| 79 | } |
| 80 | |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 81 | #[derive(Debug)] |
| 82 | struct CcSnippet { |
| 83 | snippet: TokenStream, |
| 84 | |
| 85 | /// Set of `#include`s that the `snippet` depends on. For example if |
| 86 | /// `snippet` expands to `std::int32_t`, then `includes` need to cover |
| 87 | /// the `cstdint`. |
| 88 | includes: BTreeSet<CcInclude>, |
| 89 | } |
| 90 | |
| 91 | impl CcSnippet { |
| 92 | /// Consumes `self` and returns the main `snippet`, while preserving |
| 93 | /// `includes` into the `external_includes` out parameter. |
| 94 | fn into_tokens(mut self, external_includes: &mut BTreeSet<CcInclude>) -> TokenStream { |
| 95 | external_includes.append(&mut self.includes); |
| 96 | self.snippet |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | fn format_ret_ty(ty: Ty) -> Result<CcSnippet> { |
| 101 | let void = Ok(CcSnippet { snippet: quote! { void }, includes: BTreeSet::new() }); |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 102 | match ty.kind() { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 103 | ty::TyKind::Never => void, // `!` |
| 104 | ty::TyKind::Tuple(types) if types.len() == 0 => void, // `()` |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 105 | _ => format_ty(ty), |
| 106 | } |
| 107 | } |
| 108 | |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 109 | /// Formats `ty` into a `CcSnippet` that represents how the type should be |
| 110 | /// spelled in a C++ declaration of an `extern "C"` function. |
| 111 | fn format_ty(ty: Ty) -> Result<CcSnippet> { |
| 112 | fn cstdint(snippet: TokenStream) -> CcSnippet { |
| 113 | let mut includes = BTreeSet::new(); |
| 114 | includes.insert(CcInclude::cstdint()); |
| 115 | CcSnippet { snippet, includes } |
| 116 | } |
| 117 | fn keyword(snippet: TokenStream) -> CcSnippet { |
| 118 | CcSnippet { snippet, includes: BTreeSet::new() } |
| 119 | } |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 120 | Ok(match ty.kind() { |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 121 | ty::TyKind::Never => { |
| 122 | // TODO(b/254507801): Maybe translate into `crubit::Never`? |
| 123 | bail!("The never type `!` is only supported as a return type (b/254507801)"); |
| 124 | }, |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 125 | ty::TyKind::Tuple(types) => { |
| 126 | if types.len() == 0 { |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 127 | // TODO(b/254507801): Maybe translate into `crubit::Unit`? |
| 128 | bail!("The unit type `()` / `void` is only supported as a return type"); |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 129 | } else { |
Lukasz Anforowicz | 13794df | 2022-10-21 07:56:34 -0700 | [diff] [blame] | 130 | // TODO(b/254099023): Add support for tuples. |
| 131 | bail!("Tuples are not supported yet: {} (b/254099023)", ty); |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 132 | } |
| 133 | } |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 134 | |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 135 | ty::TyKind::Bool => keyword(quote! { bool }), |
| 136 | |
| 137 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#fixed-width-floating-point-types |
| 138 | // documents that "When the platforms' "math.h" header defines the __STDC_IEC_559__ macro, |
| 139 | // Rust's floating-point types are safe to use directly in C FFI where the appropriate C |
| 140 | // types are expected (f32 for float, f64 for double)." |
| 141 | // |
| 142 | // TODO(b/255768062): Generated bindings should explicitly check `__STDC_IEC_559__` |
| 143 | ty::TyKind::Float(ty::FloatTy::F32) => keyword(quote! { float }), |
| 144 | ty::TyKind::Float(ty::FloatTy::F64) => keyword(quote! { double }), |
| 145 | |
| 146 | ty::TyKind::Char => { |
| 147 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#char |
| 148 | // documents that "Rust char is 32-bit wide and represents an unicode scalar value". |
| 149 | // |
| 150 | // We don't map Rust's `char` to C++ `char32_t` because |
| 151 | // - It may be wider than 32 bits - |
| 152 | // https://en.cppreference.com/w/c/string/multibyte/char32_t says that "char32_t is |
| 153 | // an unsigned integer type used for 32-bit wide characters and is the same type as |
| 154 | // uint_least32_t. uint_least32_t is the smallest unsigned integer type with width |
| 155 | // of at least 32 bits" |
| 156 | // - It is problematic on MacOS - https://github.com/eqrion/cbindgen/issues/423 |
| 157 | // points out that `uchar.h` is missing on that platform. |
| 158 | cstdint(quote!{ std::uint32_t }) |
| 159 | }, |
| 160 | |
| 161 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#isize-and-usize |
| 162 | // documents that "Rust's signed and unsigned fixed-width integer types {i,u}{8,16,32,64} |
| 163 | // have the same layout the C fixed-width integer types from the <stdint.h> header |
| 164 | // {u,}int{8,16,32,64}_t. These fixed-width integer types are therefore safe to use |
| 165 | // directly in C FFI where the corresponding C fixed-width integer types are expected. |
| 166 | // |
| 167 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#layout-compatibility-with-c-native-integer-types |
| 168 | // documents that "Rust does not support C platforms on which the C native integer type are |
| 169 | // not compatible with any of Rust's fixed-width integer type (e.g. because of |
| 170 | // padding-bits, lack of 2's complement, etc.)." |
| 171 | ty::TyKind::Int(ty::IntTy::I8) => cstdint(quote!{ std::int8_t }), |
| 172 | ty::TyKind::Int(ty::IntTy::I16) => cstdint(quote!{ std::int16_t }), |
| 173 | ty::TyKind::Int(ty::IntTy::I32) => cstdint(quote!{ std::int32_t }), |
| 174 | ty::TyKind::Int(ty::IntTy::I64) => cstdint(quote!{ std::int64_t }), |
| 175 | ty::TyKind::Uint(ty::UintTy::U8) => cstdint(quote!{ std::uint8_t }), |
| 176 | ty::TyKind::Uint(ty::UintTy::U16) => cstdint(quote!{ std::uint16_t }), |
| 177 | ty::TyKind::Uint(ty::UintTy::U32) => cstdint(quote!{ std::uint32_t }), |
| 178 | ty::TyKind::Uint(ty::UintTy::U64) => cstdint(quote!{ std::uint64_t }), |
| 179 | |
| 180 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#isize-and-usize |
| 181 | // documents that "The isize and usize types are [...] layout compatible with C's uintptr_t |
| 182 | // and intptr_t types.". |
| 183 | ty::TyKind::Int(ty::IntTy::Isize) => cstdint(quote!{ std::intptr_t }), |
| 184 | ty::TyKind::Uint(ty::UintTy::Usize) => cstdint(quote!{ std::uintptr_t }), |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 185 | |
| 186 | ty::TyKind::Int(ty::IntTy::I128) | ty::TyKind::Uint(ty::UintTy::U128) => { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 187 | // Note that "the alignment of Rust's {i,u}128 is unspecified and allowed to |
| 188 | // change" according to |
| 189 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#fixed-width-integer-types |
| 190 | // |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 191 | // TODO(b/254094650): Consider mapping this to Clang's (and GCC's) `__int128` |
| 192 | // or to `absl::in128`. |
| 193 | bail!("C++ doesn't have a standard equivalent of `{ty}` (b/254094650)"); |
| 194 | } |
| 195 | |
| 196 | ty::TyKind::Adt(..) |
| 197 | | ty::TyKind::Foreign(..) |
| 198 | | ty::TyKind::Str |
| 199 | | ty::TyKind::Array(..) |
| 200 | | ty::TyKind::Slice(..) |
| 201 | | ty::TyKind::RawPtr(..) |
| 202 | | ty::TyKind::Ref(..) |
| 203 | | ty::TyKind::FnPtr(..) |
| 204 | | ty::TyKind::Dynamic(..) |
| 205 | | ty::TyKind::Generator(..) |
| 206 | | ty::TyKind::GeneratorWitness(..) |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 207 | | ty::TyKind::Projection(..) |
| 208 | | ty::TyKind::Opaque(..) |
| 209 | | ty::TyKind::Param(..) |
| 210 | | ty::TyKind::Bound(..) |
| 211 | | ty::TyKind::Placeholder(..) => { |
| 212 | bail!("The following Rust type is not supported yet: {ty}") |
| 213 | } |
| 214 | ty::TyKind::Closure(..) |
| 215 | | ty::TyKind::FnDef(..) |
| 216 | | ty::TyKind::Infer(..) |
| 217 | | ty::TyKind::Error(..) => { |
| 218 | // `Closure` types are assumed to never appear in a public API of a crate (only |
| 219 | // function-body-local variables/values should be able to have a closure type). |
| 220 | // |
| 221 | // `FnDef` is assumed to never appear in a public API of a crate - this seems to |
| 222 | // be an internal, compiler-only type similar to `Closure` (e.g. |
| 223 | // based on the statement from https://doc.rust-lang.org/stable/nightly-rustc/rustc_middle/ty/enum.TyKind.html#variant.FnDef |
| 224 | // that "each function has a unique type" |
| 225 | // |
| 226 | // `Infer` and `Error` types should be impossible at the time when Crubit's code |
| 227 | // runs (after the "analysis" phase of the Rust compiler). |
| 228 | panic!("Unexpected TyKind: {:?}", ty.kind()); |
| 229 | } |
| 230 | }) |
Lukasz Anforowicz | ed1c4f0 | 2022-09-29 11:11:20 -0700 | [diff] [blame] | 231 | } |
| 232 | |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 233 | #[derive(Debug)] |
| 234 | struct BindingsSnippet { |
| 235 | /// `#include`s that go at the top of the generated `..._cc_api.h` file. |
| 236 | includes: BTreeSet<CcInclude>, |
| 237 | |
| 238 | /// Public API of the bindings in the generated `..._cc_api.h` file. |
| 239 | api: TokenStream, |
| 240 | |
| 241 | /// Internal implementation details for `..._cc_api.h` file (e.g. |
| 242 | /// declarations of Rust thunks, `static_assert`s about `struct` layout, |
| 243 | /// etc.). |
| 244 | internals: Option<TokenStream>, |
| 245 | // TODO(b/254097223): Add `impl_: Option<TokenStream>` to carry Rust thunks. |
| 246 | } |
| 247 | |
| 248 | impl BindingsSnippet { |
| 249 | fn new() -> Self { |
| 250 | Self { includes: BTreeSet::new(), api: quote! {}, internals: None } |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | impl AddAssign for BindingsSnippet { |
| 255 | fn add_assign(&mut self, rhs: Self) { |
| 256 | let Self { includes: mut rhs_includes, api: rhs_api, internals: rhs_internals } = rhs; |
| 257 | |
| 258 | self.includes.append(&mut rhs_includes); |
| 259 | self.api.extend(rhs_api); |
| 260 | |
| 261 | fn concat_optional_tokens( |
| 262 | lhs: Option<TokenStream>, |
| 263 | rhs: Option<TokenStream>, |
| 264 | ) -> Option<TokenStream> { |
| 265 | match (lhs, rhs) { |
| 266 | (None, None) => None, |
| 267 | (Some(lhs), None) => Some(lhs), |
| 268 | (None, Some(rhs)) => Some(rhs), |
| 269 | (Some(mut lhs), Some(rhs)) => { |
| 270 | lhs.extend(rhs); |
| 271 | Some(lhs) |
| 272 | } |
| 273 | } |
| 274 | } |
| 275 | self.internals = concat_optional_tokens(self.internals.take(), rhs_internals); |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | impl Add for BindingsSnippet { |
| 280 | type Output = BindingsSnippet; |
| 281 | |
| 282 | fn add(mut self, rhs: Self) -> Self { |
| 283 | self += rhs; |
| 284 | self |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | impl Sum for BindingsSnippet { |
| 289 | fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { |
| 290 | iter.fold(BindingsSnippet::new(), Add::add) |
| 291 | } |
| 292 | } |
| 293 | |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 294 | /// Formats a function with the given `def_id`. |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 295 | /// |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 296 | /// Will panic if `def_id` |
| 297 | /// - is invalid |
| 298 | /// - doesn't identify a function, |
Lukasz Anforowicz | 27914f5 | 2022-11-08 10:55:03 -0800 | [diff] [blame] | 299 | /// - has generic parameters of any kind - lifetime parameters (see also b/258235219), type |
| 300 | /// parameters, or const parameters. |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 301 | fn format_fn(tcx: TyCtxt, def_id: LocalDefId) -> Result<BindingsSnippet> { |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 302 | let def_id: DefId = def_id.to_def_id(); // Convert LocalDefId to DefId. |
| 303 | |
| 304 | let item_name = tcx.item_name(def_id); |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 305 | let symbol_name = { |
| 306 | // Call to `mono` is ok - doc comment requires no generic parameters (although |
| 307 | // lifetime parameters would have been okay). |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 308 | let instance = ty::Instance::mono(tcx, def_id); |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 309 | tcx.symbol_name(instance) |
| 310 | }; |
| 311 | |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 312 | let sig = tcx |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 313 | .fn_sig(def_id) |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 314 | .no_bound_vars() |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 315 | .expect("Doc comment points out there should be no generic parameters"); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 316 | |
| 317 | if sig.c_variadic { |
| 318 | // TODO(b/254097223): Add support for variadic functions. |
| 319 | bail!("C variadic functions are not supported (b/254097223)"); |
| 320 | } |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 321 | |
| 322 | match sig.unsafety { |
| 323 | Unsafety::Normal => (), |
| 324 | Unsafety::Unsafe => { |
| 325 | // TODO(b/254095482): Figure out how to handle `unsafe` functions. |
| 326 | bail!("Bindings for `unsafe` functions are not fully designed yet (b/254095482)"); |
| 327 | } |
| 328 | } |
| 329 | |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 330 | match sig.abi { |
| 331 | // "C" ABI is okay: Before https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html a Rust |
| 332 | // panic that "escapes" a "C" ABI function leads to Undefined Behavior. This is |
| 333 | // unfortunate, but Crubit's `panics_and_exceptions.md` documents that `-Cpanic=abort` is |
| 334 | // the only supported configuration. |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 335 | // |
| 336 | // After https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html a Rust panic that |
| 337 | // tries to "escape" a "C" ABI function will terminate the program. This is okay. |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 338 | Abi::C { unwind: false } => (), |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 339 | |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 340 | // "C-unwind" ABI is okay: After https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html a |
| 341 | // new "C-unwind" ABI may be used by Rust functions that want to safely propagate Rust |
| 342 | // panics through frames that may belong to another language. |
| 343 | Abi::C { unwind: true } => (), |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 344 | |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 345 | // TODO(b/254097223): Add support for Rust thunks. |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 346 | _ => bail!("Non-C ABI is not supported yet (b/254097223)"), |
| 347 | }; |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 348 | |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 349 | let mut includes = BTreeSet::new(); |
| 350 | let ret_type = format_ret_ty(sig.output()) |
| 351 | .context("Error formatting function return type")? |
| 352 | .into_tokens(&mut includes); |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 353 | let fn_name = format_cc_ident(item_name.as_str()).context("Error formatting function name")?; |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 354 | let arg_names = tcx |
| 355 | .fn_arg_names(def_id) |
| 356 | .iter() |
| 357 | .enumerate() |
| 358 | .map(|(index, ident)| { |
| 359 | format_cc_ident(ident.as_str()) |
| 360 | .unwrap_or_else(|_err| format_cc_ident(&format!("__param_{index}")).unwrap()) |
| 361 | }) |
| 362 | .collect_vec(); |
| 363 | let arg_types = sig |
| 364 | .inputs() |
| 365 | .iter() |
| 366 | .enumerate() |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 367 | .map(|(index, ty)| Ok( |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 368 | format_ty(*ty) |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 369 | .with_context(|| format!("Error formatting the type of parameter #{index}"))? |
| 370 | .into_tokens(&mut includes) |
| 371 | )) |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 372 | .collect::<Result<Vec<_>>>()?; |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 373 | let api: TokenStream; |
| 374 | let internals: Option<TokenStream>; |
| 375 | if item_name.as_str() == symbol_name.name { |
| 376 | api = quote! { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 377 | extern "C" #ret_type #fn_name ( |
| 378 | #( #arg_types #arg_names ),* |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 379 | ); |
| 380 | }; |
| 381 | internals = None; |
| 382 | } else { |
| 383 | let exported_name = |
| 384 | format_cc_ident(symbol_name.name).context("Error formatting exported name")?; |
| 385 | api = quote! { |
| 386 | inline #ret_type #fn_name ( |
| 387 | #( #arg_types #arg_names ),* ) { |
| 388 | return :: __crubit_internal :: #exported_name( #( #arg_names ),* ); |
| 389 | } |
| 390 | }; |
| 391 | internals = Some(quote! { |
| 392 | extern "C" #ret_type #exported_name ( |
| 393 | #( #arg_types #arg_names ),* |
| 394 | ); |
| 395 | }); |
| 396 | }; |
| 397 | Ok(BindingsSnippet { includes, api, internals }) |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 398 | } |
| 399 | |
| 400 | /// Formats a Rust item idenfied by `def_id`. |
| 401 | /// |
| 402 | /// Will panic if `def_id` is invalid (i.e. doesn't identify a Rust node or |
| 403 | /// item). |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 404 | fn format_def(tcx: TyCtxt, def_id: LocalDefId) -> Result<BindingsSnippet> { |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 405 | match tcx.hir().get_by_def_id(def_id) { |
| 406 | Node::Item(item) => match item { |
Lukasz Anforowicz | 27914f5 | 2022-11-08 10:55:03 -0800 | [diff] [blame] | 407 | Item { kind: ItemKind::Fn(_, generics, _) | |
| 408 | ItemKind::Struct(_, generics) | |
| 409 | ItemKind::Enum(_, generics) | |
| 410 | ItemKind::Union(_, generics), |
| 411 | .. } if !generics.params.is_empty() => { |
| 412 | // TODO(b/258235219): Supporting function parameter types (or return types) that |
| 413 | // are references requires adding support for generic lifetime parameters. The |
| 414 | // required changes may cascade into `format_fn`'s usage of `no_bound_vars`. |
| 415 | bail!("Generics (even lifetime generics) are not supported yet"); |
| 416 | }, |
| 417 | Item { kind: ItemKind::Fn(..), .. } => { |
| 418 | format_fn(tcx, def_id) |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 419 | } |
| 420 | Item { kind, .. } => bail!("Unsupported rustc_hir::hir::ItemKind: {}", kind.descr()), |
| 421 | }, |
| 422 | _unsupported_node => bail!("Unsupported rustc_hir::hir::Node"), |
| 423 | } |
| 424 | } |
| 425 | |
| 426 | /// Formats a C++ comment explaining why no bindings have been generated for |
| 427 | /// `local_def_id`. |
Lukasz Anforowicz | ed1c4f0 | 2022-09-29 11:11:20 -0700 | [diff] [blame] | 428 | fn format_unsupported_def( |
| 429 | tcx: TyCtxt, |
| 430 | local_def_id: LocalDefId, |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 431 | err: anyhow::Error, |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 432 | ) -> BindingsSnippet { |
Lukasz Anforowicz | ed1c4f0 | 2022-09-29 11:11:20 -0700 | [diff] [blame] | 433 | let span = tcx.sess().source_map().span_to_embeddable_string(tcx.def_span(local_def_id)); |
| 434 | let name = tcx.def_path_str(local_def_id.to_def_id()); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 435 | |
| 436 | // https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations |
| 437 | // says: To print causes as well [...], use the alternate selector “{:#}”. |
| 438 | let msg = format!("Error generating bindings for `{name}` defined at {span}: {err:#}"); |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 439 | let comment = quote! { __NEWLINE__ __NEWLINE__ __COMMENT__ #msg __NEWLINE__ }; |
| 440 | |
| 441 | BindingsSnippet { api: comment, ..BindingsSnippet::new() } |
Lukasz Anforowicz | ed1c4f0 | 2022-09-29 11:11:20 -0700 | [diff] [blame] | 442 | } |
| 443 | |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 444 | /// Formats all public items from the Rust crate being compiled (aka the |
| 445 | /// `LOCAL_CRATE`). |
Lukasz Anforowicz | 4aa1ff9 | 2022-10-10 11:22:22 -0700 | [diff] [blame] | 446 | fn format_crate(tcx: TyCtxt) -> Result<TokenStream> { |
Lukasz Anforowicz | 60ccaf3 | 2022-09-30 15:04:22 -0700 | [diff] [blame] | 447 | // TODO(lukasza): We probably shouldn't be using `exported_symbols` as the main |
| 448 | // entry point for finding Rust definitions that need to be wrapping in C++ |
| 449 | // bindings. For example, it _seems_ that things like `type` aliases or |
| 450 | // `struct`s (without an `impl`) won't be visible to a linker and therefore |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 451 | // won't have exported symbols. Additionally, walking Rust's modules top-down |
| 452 | // might result in easier translation into C++ namespaces. |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 453 | let snippets: BindingsSnippet = |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 454 | tcx.exported_symbols(LOCAL_CRATE).iter().filter_map(|(symbol, _)| match symbol { |
Lukasz Anforowicz | ed1c4f0 | 2022-09-29 11:11:20 -0700 | [diff] [blame] | 455 | ExportedSymbol::NonGeneric(def_id) => { |
Lukasz Anforowicz | 60ccaf3 | 2022-09-30 15:04:22 -0700 | [diff] [blame] | 456 | // It seems that non-generic exported symbols should all be defined in the |
| 457 | // `LOCAL_CRATE`. Furthermore, `def_id` seems to be a `LocalDefId`. OTOH, it |
| 458 | // isn't clear why `ExportedSymbol::NonGeneric` holds a `DefId` rather than a |
| 459 | // `LocalDefId`. For now, we assert `expect_local` below (and if it fails, then |
| 460 | // hopefully it will help us understand these things better and maybe add |
| 461 | // extra unit tests against out code). |
| 462 | let local_id = def_id.expect_local(); |
| 463 | |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 464 | Some(format_def(tcx, local_id).unwrap_or_else(|err| |
| 465 | format_unsupported_def(tcx, local_id, err))) |
Lukasz Anforowicz | ed1c4f0 | 2022-09-29 11:11:20 -0700 | [diff] [blame] | 466 | } |
Lukasz Anforowicz | 60ccaf3 | 2022-09-30 15:04:22 -0700 | [diff] [blame] | 467 | ExportedSymbol::Generic(def_id, _substs) => { |
| 468 | // Ignore non-local defs. Map local defs to an unsupported comment. |
| 469 | // |
| 470 | // We are guessing that a non-local `def_id` can happen when the `LOCAL_CRATE` |
| 471 | // exports a monomorphization/specialization of a generic defined in a different |
| 472 | // crate. One specific example (covered via `async fn` in one of the tests) is |
| 473 | // `DefId(2:14250 ~ core[ef75]::future::from_generator)`. |
| 474 | def_id.as_local().map(|local_id| { |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 475 | format_unsupported_def(tcx, local_id, anyhow!("Generics are not supported yet.")) |
Lukasz Anforowicz | 60ccaf3 | 2022-09-30 15:04:22 -0700 | [diff] [blame] | 476 | }) |
Lukasz Anforowicz | ed1c4f0 | 2022-09-29 11:11:20 -0700 | [diff] [blame] | 477 | } |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 478 | ExportedSymbol::DropGlue(..) | ExportedSymbol::NoDefId(..) => None, |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 479 | }) |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 480 | .sum(); |
Lukasz Anforowicz | 4aa1ff9 | 2022-10-10 11:22:22 -0700 | [diff] [blame] | 481 | |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 482 | let includes = format_cc_includes(&snippets.includes); |
| 483 | let api = { |
| 484 | // TODO(b/254690602): Decide whether using `#crate_name` as the name of the |
| 485 | // top-level namespace is okay (e.g. investigate if this name is globally |
| 486 | // unique + ergonomic). |
| 487 | let crate_name = format_cc_ident(tcx.crate_name(LOCAL_CRATE).as_str())?; |
| 488 | let api_body = &snippets.api; |
| 489 | quote! { |
| 490 | namespace #crate_name { |
| 491 | #api_body |
| 492 | } |
| 493 | } |
| 494 | }; |
| 495 | let internals = { |
| 496 | match snippets.internals { |
| 497 | None => quote! {}, |
| 498 | Some(details_body) => quote! { |
| 499 | namespace __crubit_internal { |
| 500 | #details_body |
| 501 | } |
| 502 | __NEWLINE__ |
| 503 | }, |
| 504 | } |
| 505 | }; |
Lukasz Anforowicz | 4aa1ff9 | 2022-10-10 11:22:22 -0700 | [diff] [blame] | 506 | Ok(quote! { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 507 | #includes __NEWLINE__ |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 508 | #internals |
| 509 | #api |
Lukasz Anforowicz | 4aa1ff9 | 2022-10-10 11:22:22 -0700 | [diff] [blame] | 510 | }) |
Lukasz Anforowicz | ed1c4f0 | 2022-09-29 11:11:20 -0700 | [diff] [blame] | 511 | } |
| 512 | |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 513 | #[cfg(test)] |
Lukasz Anforowicz | f018e4d | 2022-09-28 07:35:59 -0700 | [diff] [blame] | 514 | pub mod tests { |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 515 | use super::{format_def, format_ret_ty, format_ty, BindingsSnippet, GeneratedBindings}; |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 516 | |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 517 | use anyhow::Result; |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 518 | use code_gen_utils::{format_cc_ident, format_cc_includes}; |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 519 | use itertools::Itertools; |
| 520 | use proc_macro2::TokenStream; |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 521 | use quote::quote; |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 522 | use rustc_middle::ty::{Ty, TyCtxt}; |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 523 | use rustc_span::def_id::LocalDefId; |
Lukasz Anforowicz | f018e4d | 2022-09-28 07:35:59 -0700 | [diff] [blame] | 524 | use std::path::PathBuf; |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 525 | |
Lukasz Anforowicz | e7a2500 | 2022-11-10 06:21:42 -0800 | [diff] [blame^] | 526 | use token_stream_matchers::{assert_cc_matches, assert_cc_not_matches, assert_rs_not_matches}; |
Lukasz Anforowicz | 2b38d27 | 2022-09-23 08:08:18 -0700 | [diff] [blame] | 527 | |
Lukasz Anforowicz | f018e4d | 2022-09-28 07:35:59 -0700 | [diff] [blame] | 528 | pub fn get_sysroot_for_testing() -> PathBuf { |
| 529 | let runfiles = runfiles::Runfiles::create().unwrap(); |
| 530 | runfiles.rlocation(if std::env::var("LEGACY_TOOLCHAIN_RUST_TEST").is_ok() { |
| 531 | "google3/third_party/unsupported_toolchains/rust/toolchains/nightly" |
| 532 | } else { |
| 533 | "google3/nowhere/llvm/rust" |
| 534 | }) |
| 535 | } |
| 536 | |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 537 | #[test] |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 538 | #[should_panic(expected = "Test inputs shouldn't cause compilation errors")] |
Lukasz Anforowicz | e959958 | 2022-09-30 15:54:46 -0700 | [diff] [blame] | 539 | fn test_infra_panic_when_test_input_contains_syntax_errors() { |
Lukasz Anforowicz | 88bde9b | 2022-10-14 14:48:07 -0700 | [diff] [blame] | 540 | run_compiler("syntax error here", |_tcx| panic!("This part shouldn't execute")) |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 541 | } |
| 542 | |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 543 | #[test] |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 544 | #[should_panic(expected = "Test inputs shouldn't cause compilation errors")] |
Lukasz Anforowicz | e959958 | 2022-09-30 15:54:46 -0700 | [diff] [blame] | 545 | fn test_infra_panic_when_test_input_triggers_analysis_errors() { |
Lukasz Anforowicz | 88bde9b | 2022-10-14 14:48:07 -0700 | [diff] [blame] | 546 | run_compiler("#![feature(no_such_feature)]", |_tcx| panic!("This part shouldn't execute")) |
| 547 | } |
| 548 | |
| 549 | #[test] |
| 550 | #[should_panic(expected = "Test inputs shouldn't cause compilation errors")] |
| 551 | fn test_infra_panic_when_test_input_triggers_warnings() { |
| 552 | run_compiler("pub fn foo(unused_parameter: i32) {}", |_tcx| { |
| 553 | panic!("This part shouldn't execute") |
| 554 | }) |
Lukasz Anforowicz | e959958 | 2022-09-30 15:54:46 -0700 | [diff] [blame] | 555 | } |
| 556 | |
| 557 | #[test] |
Lukasz Anforowicz | 5bddf18 | 2022-09-30 16:06:59 -0700 | [diff] [blame] | 558 | fn test_infra_nightly_features_ok_in_test_input() { |
Lukasz Anforowicz | 88bde9b | 2022-10-14 14:48:07 -0700 | [diff] [blame] | 559 | // This test arbitrarily picks `yeet_expr` as an example of a feature that |
| 560 | // hasn't yet been stabilized. |
| 561 | let test_src = r#" |
| 562 | // This test is supposed to test that *nightly* features are ok |
| 563 | // in the test input. The `forbid` directive below helps to |
| 564 | // ensure that we'll realize in the future when the `yeet_expr` |
| 565 | // feature gets stabilized, making it not quite fitting for use |
| 566 | // in this test. |
| 567 | #![forbid(stable_features)] |
| 568 | |
| 569 | #![feature(yeet_expr)] |
| 570 | "#; |
| 571 | run_compiler(test_src, |_tcx| ()) |
| 572 | } |
| 573 | |
| 574 | #[test] |
| 575 | fn test_infra_stabilized_features_ok_in_test_input() { |
| 576 | // This test arbitrarily picks `const_ptr_offset_from` as an example of a |
| 577 | // feature that has been already stabilized. |
| 578 | run_compiler("#![feature(const_ptr_offset_from)]", |_tcx| ()) |
Lukasz Anforowicz | 5bddf18 | 2022-09-30 16:06:59 -0700 | [diff] [blame] | 579 | } |
| 580 | |
| 581 | #[test] |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 582 | #[should_panic(expected = "No items named `missing_name`.\n\ |
| 583 | Instead found:\n`bar`,\n`foo`,\n`m1`,\n`m2`,\n`std`")] |
| 584 | fn test_find_def_id_by_name_panic_when_no_item_with_matching_name() { |
| 585 | let test_src = r#" |
| 586 | pub extern "C" fn foo() {} |
| 587 | |
| 588 | pub mod m1 { |
| 589 | pub fn bar() {} |
| 590 | } |
| 591 | pub mod m2 { |
| 592 | pub fn bar() {} |
| 593 | } |
| 594 | "#; |
| 595 | run_compiler(test_src, |tcx| find_def_id_by_name(tcx, "missing_name")); |
| 596 | } |
| 597 | |
| 598 | #[test] |
| 599 | #[should_panic(expected = "More than one item named `some_name`")] |
| 600 | fn test_find_def_id_by_name_panic_when_multiple_items_with_matching_name() { |
| 601 | let test_src = r#" |
| 602 | pub mod m1 { |
| 603 | pub fn some_name() {} |
| 604 | } |
| 605 | pub mod m2 { |
| 606 | pub fn some_name() {} |
| 607 | } |
| 608 | "#; |
| 609 | run_compiler(test_src, |tcx| find_def_id_by_name(tcx, "some_name")); |
| 610 | } |
| 611 | |
| 612 | #[test] |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 613 | fn test_generated_bindings_fn_extern_c() { |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 614 | // This test covers only a single example of a function that should get a C++ |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 615 | // binding. Additional coverage of how items are formatted is provided by |
| 616 | // `test_format_def_...` tests. |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 617 | let test_src = r#" |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 618 | #[no_mangle] |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 619 | pub extern "C" fn public_function() { |
| 620 | println!("foo"); |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 621 | } |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 622 | "#; |
| 623 | test_generated_bindings(test_src, |bindings| { |
Lukasz Anforowicz | 24160d5 | 2022-10-19 06:45:45 -0700 | [diff] [blame] | 624 | let bindings = bindings.expect("Test expects success"); |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 625 | assert_cc_matches!( |
| 626 | bindings.h_body, |
| 627 | quote! { |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 628 | extern "C" void public_function(); |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 629 | } |
Lukasz Anforowicz | d9ff4ab | 2022-09-23 08:11:18 -0700 | [diff] [blame] | 630 | ); |
Lukasz Anforowicz | e7a2500 | 2022-11-10 06:21:42 -0800 | [diff] [blame^] | 631 | // TODO(b/254097223): Verify Rust thunks here (once they actually get generated). |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 632 | }); |
| 633 | } |
| 634 | |
| 635 | #[test] |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 636 | fn test_generated_bindings_fn_export_name() { |
| 637 | // Coverage of how `BindingsSnippet::internals` are propagated when there are no |
| 638 | // `BindingsSnippet::impl` (e.g. no Rust thunks are needed). |
| 639 | let test_src = r#" |
| 640 | #[export_name = "export_name"] |
| 641 | pub extern "C" fn public_function(x: f64, y: f64) -> f64 { x + y } |
| 642 | "#; |
| 643 | test_generated_bindings(test_src, |bindings| { |
| 644 | let bindings = bindings.expect("Test expects success"); |
| 645 | assert_cc_matches!( |
| 646 | bindings.h_body, |
| 647 | quote! { |
| 648 | namespace __crubit_internal { |
| 649 | extern "C" double export_name(double x, double y); |
| 650 | } |
| 651 | namespace rust_out { |
| 652 | inline double public_function(double x, double y) { |
| 653 | return ::__crubit_internal::export_name(x, y); |
| 654 | } |
| 655 | } |
| 656 | } |
| 657 | ); |
| 658 | }); |
| 659 | } |
| 660 | |
| 661 | #[test] |
| 662 | fn test_generated_bindings_includes() { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 663 | let test_src = r#" |
| 664 | #[no_mangle] |
| 665 | pub extern "C" fn public_function(i: i32, d: isize, u: u64) { |
| 666 | dbg!(i); |
| 667 | dbg!(d); |
| 668 | dbg!(u); |
| 669 | } |
| 670 | "#; |
| 671 | test_generated_bindings(test_src, |bindings| { |
| 672 | let bindings = bindings.expect("Test expects success"); |
| 673 | assert_cc_matches!( |
| 674 | bindings.h_body, |
| 675 | quote! { |
| 676 | __HASH_TOKEN__ include <cstdint> ... |
| 677 | namespace ... { |
| 678 | extern "C" void public_function( |
| 679 | std::int32_t i, |
| 680 | std::intptr_t d, |
| 681 | std::uint64_t u); |
| 682 | } |
| 683 | } |
| 684 | ); |
| 685 | }); |
| 686 | } |
| 687 | |
| 688 | #[test] |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 689 | fn test_generated_bindings_fn_non_pub() { |
| 690 | let test_src = r#" |
Lukasz Anforowicz | 88bde9b | 2022-10-14 14:48:07 -0700 | [diff] [blame] | 691 | #![allow(dead_code)] |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 692 | extern "C" fn private_function() { |
| 693 | println!("foo"); |
| 694 | } |
| 695 | "#; |
| 696 | test_generated_bindings(test_src, |bindings| { |
Lukasz Anforowicz | 24160d5 | 2022-10-19 06:45:45 -0700 | [diff] [blame] | 697 | let bindings = bindings.expect("Test expects success"); |
| 698 | |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 699 | // Non-public functions should not be present in the generated bindings. |
| 700 | assert_cc_not_matches!(bindings.h_body, quote! { private_function }); |
Lukasz Anforowicz | e7a2500 | 2022-11-10 06:21:42 -0800 | [diff] [blame^] | 701 | assert_rs_not_matches!(bindings.rs_body, quote! { private_function }); |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 702 | }); |
| 703 | } |
| 704 | |
| 705 | #[test] |
Lukasz Anforowicz | 4aa1ff9 | 2022-10-10 11:22:22 -0700 | [diff] [blame] | 706 | fn test_generated_bindings_top_level_items() { |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 707 | let test_src = "pub fn public_function() {}"; |
| 708 | test_generated_bindings(test_src, |bindings| { |
Lukasz Anforowicz | 24160d5 | 2022-10-19 06:45:45 -0700 | [diff] [blame] | 709 | let bindings = bindings.expect("Test expects success"); |
Lukasz Anforowicz | 4aa1ff9 | 2022-10-10 11:22:22 -0700 | [diff] [blame] | 710 | let expected_comment_txt = |
| 711 | "Automatically @generated C++ bindings for the following Rust crate:\n\ |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 712 | rust_out"; |
| 713 | assert_cc_matches!( |
| 714 | bindings.h_body, |
| 715 | quote! { |
| 716 | __COMMENT__ #expected_comment_txt |
Lukasz Anforowicz | 4aa1ff9 | 2022-10-10 11:22:22 -0700 | [diff] [blame] | 717 | ... |
Lukasz Anforowicz | 31b29cd | 2022-10-10 11:33:41 -0700 | [diff] [blame] | 718 | __HASH_TOKEN__ pragma once |
| 719 | ... |
Lukasz Anforowicz | 4aa1ff9 | 2022-10-10 11:22:22 -0700 | [diff] [blame] | 720 | namespace rust_out { |
| 721 | ... |
| 722 | } |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 723 | } |
| 724 | ); |
Lukasz Anforowicz | e7a2500 | 2022-11-10 06:21:42 -0800 | [diff] [blame^] | 725 | assert_cc_matches!( |
| 726 | bindings.rs_body, |
| 727 | quote! { |
| 728 | __COMMENT__ #expected_comment_txt |
| 729 | } |
| 730 | ); |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 731 | }) |
| 732 | } |
| 733 | |
| 734 | #[test] |
| 735 | fn test_generated_bindings_unsupported_item() { |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 736 | // This test verifies how `Err` from `format_def` is formatted as a C++ comment |
| 737 | // (in `format_crate` and `format_unsupported_def`). |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 738 | // - This test covers only a single example of an unsupported item. Additional |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 739 | // coverage is provided by `test_format_def_unsupported_...` tests. |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 740 | // - This test somewhat arbitrarily chooses an example of an unsupported item, |
| 741 | // trying to pick one that 1) will never be supported (b/254104998 has some extra |
| 742 | // notes about APIs named after reserved C++ keywords) and 2) tests that the |
| 743 | // full error chain is included in the message. |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 744 | let test_src = r#" |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 745 | #[no_mangle] |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 746 | pub extern "C" fn reinterpret_cast() {} |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 747 | "#; |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 748 | test_generated_bindings(test_src, |bindings| { |
Lukasz Anforowicz | 24160d5 | 2022-10-19 06:45:45 -0700 | [diff] [blame] | 749 | let bindings = bindings.expect("Test expects success"); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 750 | let expected_comment_txt = "Error generating bindings for `reinterpret_cast` \ |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 751 | defined at <crubit_unittests.rs>:3:17: 3:53: \ |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 752 | Error formatting function name: \ |
| 753 | `reinterpret_cast` is a C++ reserved keyword \ |
| 754 | and can't be used as a C++ identifier"; |
Lukasz Anforowicz | 2f2510a | 2022-09-29 13:06:44 -0700 | [diff] [blame] | 755 | assert_cc_matches!( |
| 756 | bindings.h_body, |
| 757 | quote! { |
| 758 | __COMMENT__ #expected_comment_txt |
| 759 | } |
| 760 | ); |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 761 | }) |
| 762 | } |
| 763 | |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 764 | #[test] |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 765 | fn test_format_def_fn_extern_c_no_mangle_no_params_no_return_type() { |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 766 | let test_src = r#" |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 767 | #[no_mangle] |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 768 | pub extern "C" fn public_function() {} |
| 769 | "#; |
| 770 | test_format_def(test_src, "public_function", |result| { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 771 | let result = result.expect("Test expects success here"); |
| 772 | assert!(result.includes.is_empty()); |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 773 | assert!(result.internals.is_none()); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 774 | assert_cc_matches!( |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 775 | result.api, |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 776 | quote! { |
| 777 | extern "C" void public_function(); |
| 778 | } |
| 779 | ); |
| 780 | }); |
| 781 | } |
| 782 | |
| 783 | #[test] |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 784 | fn test_format_def_fn_explicit_unit_return_type() { |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 785 | // This test is very similar to the |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 786 | // `test_format_def_fn_extern_c_no_mangle_no_params_no_return_type` above, except that the |
| 787 | // return type is explicitly spelled out. There is no difference in `ty::FnSig` so our |
| 788 | // code behaves exactly the same, but the test has been planned based on earlier, |
| 789 | // hir-focused approach and having this extra test coverage shouldn't hurt. (`hir::FnSig` |
| 790 | // and `hir::FnRetTy` _do_ see a difference between the two tests). |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 791 | let test_src = r#" |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 792 | #[no_mangle] |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 793 | pub extern "C" fn explicit_unit_return_type() -> () {} |
| 794 | "#; |
| 795 | test_format_def(test_src, "explicit_unit_return_type", |result| { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 796 | let result = result.expect("Test expects success here"); |
| 797 | assert!(result.includes.is_empty()); |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 798 | assert!(result.internals.is_none()); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 799 | assert_cc_matches!( |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 800 | result.api, |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 801 | quote! { |
| 802 | extern "C" void explicit_unit_return_type(); |
| 803 | } |
| 804 | ); |
| 805 | }); |
| 806 | } |
| 807 | |
| 808 | #[test] |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 809 | fn test_format_def_fn_never_return_type() { |
| 810 | let test_src = r#" |
| 811 | #[no_mangle] |
| 812 | pub extern "C" fn never_returning_function() -> ! { |
| 813 | panic!("This function panics and therefore never returns"); |
| 814 | } |
| 815 | "#; |
| 816 | test_format_def(test_src, "never_returning_function", |result| { |
| 817 | // TODO(b/254507801): The function should be annotated with the `[[noreturn]]` |
| 818 | // attribute. |
| 819 | // TODO(b/254507801): Expect `crubit::Never` instead (see the bug for more |
| 820 | // details). |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 821 | let result = result.expect("Test expects success here"); |
| 822 | assert!(result.includes.is_empty()); |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 823 | assert!(result.internals.is_none()); |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 824 | assert_cc_matches!( |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 825 | result.api, |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 826 | quote! { |
| 827 | extern "C" void never_returning_function(); |
| 828 | } |
| 829 | ); |
| 830 | }) |
| 831 | } |
| 832 | |
| 833 | #[test] |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 834 | fn test_format_def_fn_mangling() { |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 835 | // This test checks that bindings can be generated for `extern "C"` functions |
| 836 | // that do *not* have `#[no_mangle]` attribute. The test elides away |
| 837 | // the mangled name in the `assert_cc_matches` checks below, but |
| 838 | // end-to-end test coverage is provided by `test/functions`. |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 839 | let test_src = r#" |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 840 | pub extern "C" fn public_function(x: f64, y: f64) -> f64 { x + y } |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 841 | "#; |
| 842 | test_format_def(test_src, "public_function", |result| { |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 843 | let result = result.expect("Test expects success here"); |
| 844 | assert!(result.includes.is_empty()); |
| 845 | assert_cc_matches!( |
| 846 | result.api, |
| 847 | quote! { |
| 848 | inline double public_function(double x, double y) { |
| 849 | return ...(x, y); |
| 850 | } |
| 851 | } |
| 852 | ); |
| 853 | assert_cc_matches!( |
| 854 | result.internals.expect("This test expects separate extern-C decl"), |
| 855 | quote! { |
| 856 | extern "C" double ...(double x, double y); |
| 857 | } |
| 858 | ); |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 859 | }); |
| 860 | } |
| 861 | |
| 862 | #[test] |
| 863 | fn test_format_def_fn_export_name() { |
| 864 | let test_src = r#" |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 865 | #[export_name = "export_name"] |
| 866 | pub extern "C" fn public_function(x: f64, y: f64) -> f64 { x + y } |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 867 | "#; |
| 868 | test_format_def(test_src, "public_function", |result| { |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 869 | let result = result.expect("Test expects success here"); |
| 870 | assert!(result.includes.is_empty()); |
| 871 | assert_cc_matches!( |
| 872 | result.api, |
| 873 | quote! { |
| 874 | inline double public_function(double x, double y) { |
| 875 | return ::__crubit_internal::export_name(x, y); |
| 876 | } |
| 877 | } |
| 878 | ); |
| 879 | assert_cc_matches!( |
| 880 | result.internals.expect("This test expects separate extern-C decl"), |
| 881 | quote! { |
| 882 | extern "C" double export_name(double x, double y); |
| 883 | } |
| 884 | ); |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 885 | }); |
| 886 | } |
| 887 | |
| 888 | #[test] |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 889 | fn test_format_def_unsupported_fn_unsafe() { |
| 890 | // This tests how bindings for an `unsafe fn` are generated. |
| 891 | let test_src = r#" |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 892 | #[no_mangle] |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 893 | pub unsafe extern "C" fn foo() {} |
| 894 | "#; |
| 895 | test_format_def(test_src, "foo", |result| { |
| 896 | let err = result.expect_err("Test expects an error here"); |
| 897 | assert_eq!( |
| 898 | err, |
| 899 | "Bindings for `unsafe` functions \ |
| 900 | are not fully designed yet (b/254095482)" |
| 901 | ); |
| 902 | }); |
| 903 | } |
| 904 | |
| 905 | #[test] |
| 906 | fn test_format_def_fn_const() { |
| 907 | // This tests how bindings for an `const fn` are generated. |
| 908 | // |
| 909 | // Right now the `const` qualifier is ignored, but one can imagine that in the |
| 910 | // (very) long-term future such functions (including their bodies) could |
| 911 | // be translated into C++ `consteval` functions. |
| 912 | let test_src = r#" |
| 913 | pub const fn foo(i: i32) -> i32 { i * 42 } |
| 914 | "#; |
| 915 | test_format_def(test_src, "foo", |result| { |
| 916 | // TODO(lukasza): Update test expectations below once `const fn` example from |
| 917 | // the testcase doesn't just error out (and is instead supported as |
| 918 | // a non-`consteval` binding). |
| 919 | // TODO(b/254095787): Update test expectations below once `const fn` from Rust |
| 920 | // is translated into a `consteval` C++ function. |
| 921 | let err = result.expect_err("Test expects an error here"); |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 922 | assert_eq!(err, "Non-C ABI is not supported yet (b/254097223)"); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 923 | }); |
| 924 | } |
| 925 | |
| 926 | #[test] |
| 927 | fn test_format_def_fn_with_c_unwind_abi() { |
| 928 | // See also https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html |
| 929 | let test_src = r#" |
| 930 | #![feature(c_unwind)] |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 931 | |
| 932 | #[no_mangle] |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 933 | pub extern "C-unwind" fn may_throw() {} |
| 934 | "#; |
| 935 | test_format_def(test_src, "may_throw", |result| { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 936 | let result = result.expect("Test expects success here"); |
| 937 | assert!(result.includes.is_empty()); |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 938 | assert!(result.internals.is_none()); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 939 | assert_cc_matches!( |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 940 | result.api, |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 941 | quote! { |
| 942 | extern "C" void may_throw(); |
| 943 | } |
| 944 | ); |
| 945 | }); |
| 946 | } |
| 947 | |
| 948 | #[test] |
| 949 | fn test_format_def_fn_with_type_aliased_return_type() { |
| 950 | // Type aliases disappear at the `rustc_middle::ty::Ty` level and therefore in |
| 951 | // the short-term the generated bindings also ignore type aliases. |
| 952 | // |
| 953 | // TODO(b/254096006): Consider preserving `type` aliases when generating |
| 954 | // bindings. |
| 955 | let test_src = r#" |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 956 | type MyTypeAlias = f64; |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 957 | |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 958 | #[no_mangle] |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 959 | pub extern "C" fn type_aliased_return() -> MyTypeAlias { 42.0 } |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 960 | "#; |
| 961 | test_format_def(test_src, "type_aliased_return", |result| { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 962 | let result = result.expect("Test expects success here"); |
| 963 | assert!(result.includes.is_empty()); |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 964 | assert!(result.internals.is_none()); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 965 | assert_cc_matches!( |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 966 | result.api, |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 967 | quote! { |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 968 | extern "C" double type_aliased_return(); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 969 | } |
| 970 | ); |
| 971 | }); |
| 972 | } |
| 973 | |
| 974 | #[test] |
| 975 | fn test_format_def_unsupported_fn_name_is_reserved_cpp_keyword() { |
| 976 | let test_src = r#" |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 977 | #[no_mangle] |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 978 | pub extern "C" fn reinterpret_cast() -> () {} |
| 979 | "#; |
| 980 | test_format_def(test_src, "reinterpret_cast", |result| { |
| 981 | let err = result.expect_err("Test expects an error here"); |
| 982 | assert_eq!( |
| 983 | err, |
| 984 | "Error formatting function name: \ |
| 985 | `reinterpret_cast` is a C++ reserved keyword \ |
| 986 | and can't be used as a C++ identifier" |
| 987 | ); |
| 988 | }); |
| 989 | } |
| 990 | |
| 991 | #[test] |
| 992 | fn test_format_def_unsupported_fn_ret_type() { |
| 993 | let test_src = r#" |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 994 | #[no_mangle] |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 995 | pub extern "C" fn foo() -> *const i32 { std::ptr::null() } |
| 996 | "#; |
| 997 | test_format_def(test_src, "foo", |result| { |
| 998 | let err = result.expect_err("Test expects an error here"); |
| 999 | assert_eq!( |
| 1000 | err, |
| 1001 | "Error formatting function return type: \ |
| 1002 | The following Rust type is not supported yet: *const i32" |
| 1003 | ); |
| 1004 | }); |
| 1005 | } |
| 1006 | |
| 1007 | #[test] |
| 1008 | fn test_format_def_unsupported_fn_with_late_bound_lifetimes() { |
Lukasz Anforowicz | 27914f5 | 2022-11-08 10:55:03 -0800 | [diff] [blame] | 1009 | // TODO(b/258235219): Expect success after adding support for references. |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1010 | let test_src = r#" |
| 1011 | pub fn foo(arg: &i32) -> &i32 { arg } |
| 1012 | |
| 1013 | // Lifetime inference translates the above into: |
| 1014 | // pub fn foo<'a>(arg: &'a i32) -> &'a i32 { ... } |
| 1015 | // leaving 'a lifetime late-bound (it is bound with a lifetime |
| 1016 | // taken from each of the callsites). In other words, we can't |
| 1017 | // just call `no_bound_vars` on this `FnSig`'s `Binder`. |
| 1018 | "#; |
| 1019 | test_format_def(test_src, "foo", |result| { |
| 1020 | let err = result.expect_err("Test expects an error here"); |
| 1021 | assert_eq!( |
| 1022 | err, |
Lukasz Anforowicz | 27914f5 | 2022-11-08 10:55:03 -0800 | [diff] [blame] | 1023 | "Generics (even lifetime generics) are not supported yet" |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1024 | ); |
| 1025 | }); |
| 1026 | } |
| 1027 | |
| 1028 | #[test] |
| 1029 | fn test_format_def_unsupported_generic_fn() { |
| 1030 | let test_src = r#" |
| 1031 | use std::default::Default; |
| 1032 | use std::fmt::Display; |
| 1033 | pub fn generic_function<T: Default + Display>() { |
| 1034 | println!("{}", T::default()); |
| 1035 | } |
| 1036 | "#; |
| 1037 | test_format_def(test_src, "generic_function", |result| { |
| 1038 | let err = result.expect_err("Test expects an error here"); |
| 1039 | assert_eq!( |
| 1040 | err, |
Lukasz Anforowicz | 27914f5 | 2022-11-08 10:55:03 -0800 | [diff] [blame] | 1041 | "Generics (even lifetime generics) are not supported yet" |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1042 | ); |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1043 | }); |
| 1044 | } |
| 1045 | |
| 1046 | #[test] |
Lukasz Anforowicz | 27914f5 | 2022-11-08 10:55:03 -0800 | [diff] [blame] | 1047 | fn test_format_def_unsupported_generic_struct() { |
| 1048 | let test_src = r#" |
| 1049 | pub struct Point<T> { |
| 1050 | pub x: T, |
| 1051 | pub y: T, |
| 1052 | } |
| 1053 | "#; |
| 1054 | test_format_def(test_src, "Point", |result| { |
| 1055 | let err = result.expect_err("Test expects an error here"); |
| 1056 | assert_eq!(err, "Generics (even lifetime generics) are not supported yet"); |
| 1057 | }); |
| 1058 | } |
| 1059 | |
| 1060 | #[test] |
| 1061 | fn test_format_def_unsupported_generic_enum() { |
| 1062 | let test_src = r#" |
| 1063 | pub enum Point<T> { |
| 1064 | Cartesian{x: T, y: T}, |
| 1065 | Polar{angle: T, dist: T}, |
| 1066 | } |
| 1067 | "#; |
| 1068 | test_format_def(test_src, "Point", |result| { |
| 1069 | let err = result.expect_err("Test expects an error here"); |
| 1070 | assert_eq!(err, "Generics (even lifetime generics) are not supported yet"); |
| 1071 | }); |
| 1072 | } |
| 1073 | |
| 1074 | #[test] |
| 1075 | fn test_format_def_unsupported_generic_union() { |
| 1076 | let test_src = r#" |
| 1077 | pub union SomeUnion<T> { |
| 1078 | pub x: std::mem::ManuallyDrop<T>, |
| 1079 | pub y: i32, |
| 1080 | } |
| 1081 | "#; |
| 1082 | test_format_def(test_src, "SomeUnion", |result| { |
| 1083 | let err = result.expect_err("Test expects an error here"); |
| 1084 | assert_eq!(err, "Generics (even lifetime generics) are not supported yet"); |
| 1085 | }); |
| 1086 | } |
| 1087 | |
| 1088 | #[test] |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1089 | fn test_format_def_unsupported_fn_async() { |
| 1090 | let test_src = r#" |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1091 | pub async fn async_function() {} |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1092 | "#; |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1093 | test_format_def(test_src, "async_function", |result| { |
| 1094 | let err = result.expect_err("Test expects an error here"); |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 1095 | assert_eq!(err, "Non-C ABI is not supported yet (b/254097223)"); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1096 | }); |
| 1097 | } |
| 1098 | |
| 1099 | #[test] |
| 1100 | fn test_format_def_unsupported_fn_non_c_abi() { |
| 1101 | let test_src = r#" |
| 1102 | pub fn default_rust_abi_function() {} |
| 1103 | "#; |
| 1104 | test_format_def(test_src, "default_rust_abi_function", |result| { |
| 1105 | let err = result.expect_err("Test expects an error here"); |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 1106 | assert_eq!(err, "Non-C ABI is not supported yet (b/254097223)"); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1107 | }) |
| 1108 | } |
| 1109 | |
| 1110 | #[test] |
| 1111 | fn test_format_def_unsupported_fn_variadic() { |
| 1112 | let test_src = r#" |
| 1113 | #![feature(c_variadic)] |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 1114 | |
| 1115 | #[no_mangle] |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1116 | pub unsafe extern "C" fn variadic_function(_fmt: *const u8, ...) {} |
| 1117 | "#; |
| 1118 | test_format_def(test_src, "variadic_function", |result| { |
Lukasz Anforowicz | 13794df | 2022-10-21 07:56:34 -0700 | [diff] [blame] | 1119 | // TODO(b/254097223): Add support for variadic functions. |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1120 | let err = result.expect_err("Test expects an error here"); |
| 1121 | assert_eq!(err, "C variadic functions are not supported (b/254097223)"); |
| 1122 | }); |
| 1123 | } |
| 1124 | |
| 1125 | #[test] |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 1126 | fn test_format_def_fn_params() { |
| 1127 | let test_src = r#" |
| 1128 | #[allow(unused_variables)] |
| 1129 | #[no_mangle] |
| 1130 | pub extern "C" fn foo(b: bool, f: f64) {} |
| 1131 | "#; |
| 1132 | test_format_def(test_src, "foo", |result| { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 1133 | let result = result.expect("Test expects success here"); |
| 1134 | assert!(result.includes.is_empty()); |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 1135 | assert!(result.internals.is_none()); |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 1136 | assert_cc_matches!( |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 1137 | result.api, |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 1138 | quote! { |
| 1139 | extern "C" void foo(bool b, double f); |
| 1140 | } |
| 1141 | ); |
| 1142 | }); |
| 1143 | } |
| 1144 | |
| 1145 | #[test] |
| 1146 | fn test_format_def_fn_param_name_reserved_keyword() { |
| 1147 | let test_src = r#" |
| 1148 | #[allow(unused_variables)] |
| 1149 | #[no_mangle] |
| 1150 | pub extern "C" fn some_function(reinterpret_cast: f64) {} |
| 1151 | "#; |
| 1152 | test_format_def(test_src, "some_function", |result| { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 1153 | let result = result.expect("Test expects success here"); |
| 1154 | assert!(result.includes.is_empty()); |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 1155 | assert!(result.internals.is_none()); |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 1156 | assert_cc_matches!( |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 1157 | result.api, |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 1158 | quote! { |
| 1159 | extern "C" void some_function(double __param_0); |
| 1160 | } |
| 1161 | ); |
| 1162 | }); |
| 1163 | } |
| 1164 | |
| 1165 | #[test] |
Lukasz Anforowicz | c51aeb1 | 2022-11-07 10:56:18 -0800 | [diff] [blame] | 1166 | fn test_format_def_fn_export_name_with_anonymous_parameter_names() { |
| 1167 | let test_src = r#" |
| 1168 | #[export_name = "export_name"] |
| 1169 | pub extern "C" fn public_function(_: f64, _: f64) {} |
| 1170 | "#; |
| 1171 | test_format_def(test_src, "public_function", |result| { |
| 1172 | let result = result.expect("Test expects success here"); |
| 1173 | assert!(result.includes.is_empty()); |
| 1174 | assert_cc_matches!( |
| 1175 | result.api, |
| 1176 | quote! { |
| 1177 | inline void public_function(double __param_0, double __param_1) { |
| 1178 | return ::__crubit_internal::export_name(__param_0, __param_1); |
| 1179 | } |
| 1180 | } |
| 1181 | ); |
| 1182 | assert_cc_matches!( |
| 1183 | result.internals.expect("This test expects separate extern-C decl"), |
| 1184 | quote! { |
| 1185 | extern "C" void export_name(double __param_0, double __param_1); |
| 1186 | } |
| 1187 | ); |
| 1188 | }); |
| 1189 | } |
| 1190 | |
| 1191 | |
| 1192 | #[test] |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 1193 | fn test_format_def_unsupported_fn_param_type() { |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1194 | let test_src = r#" |
Lukasz Anforowicz | 7123f21 | 2022-10-20 08:51:04 -0700 | [diff] [blame] | 1195 | #[no_mangle] |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 1196 | pub extern "C" fn fn_with_params(_param: *const i32) {} |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1197 | "#; |
| 1198 | test_format_def(test_src, "fn_with_params", |result| { |
| 1199 | let err = result.expect_err("Test expects an error here"); |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 1200 | assert_eq!(err, "Error formatting the type of parameter #0: \ |
| 1201 | The following Rust type is not supported yet: \ |
| 1202 | *const i32"); |
| 1203 | }); |
| 1204 | } |
| 1205 | |
| 1206 | #[test] |
| 1207 | fn test_format_def_unsupported_fn_param_type_unit() { |
| 1208 | let test_src = r#" |
| 1209 | #[no_mangle] |
| 1210 | pub fn fn_with_params(_param: ()) {} |
| 1211 | "#; |
| 1212 | test_format_def(test_src, "fn_with_params", |result| { |
| 1213 | // TODO(b/254097223): Change the expectations once Rust-ABI functions are |
| 1214 | // supported. Note that the test cannot use `extern "C"` in the |
| 1215 | // meantime, because `()` is not FFI-safe (i.e. Rust won't allow |
| 1216 | // using it with `extern "C"`). |
| 1217 | let err = result.expect_err("Test expects an error here"); |
| 1218 | assert_eq!(err, "Non-C ABI is not supported yet (b/254097223)"); |
| 1219 | }); |
| 1220 | } |
| 1221 | |
| 1222 | #[test] |
| 1223 | fn test_format_def_unsupported_fn_param_type_never() { |
| 1224 | let test_src = r#" |
| 1225 | #![feature(never_type)] |
| 1226 | |
| 1227 | #[no_mangle] |
| 1228 | pub extern "C" fn fn_with_params(_param: !) {} |
| 1229 | "#; |
| 1230 | test_format_def(test_src, "fn_with_params", |result| { |
| 1231 | let err = result.expect_err("Test expects an error here"); |
| 1232 | assert_eq!( |
| 1233 | err, |
| 1234 | "Error formatting the type of parameter #0: \ |
| 1235 | The never type `!` is only supported as a return type (b/254507801)" |
| 1236 | ); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1237 | }); |
| 1238 | } |
| 1239 | |
| 1240 | #[test] |
| 1241 | fn test_format_def_unsupported_hir_item_kind() { |
| 1242 | let test_src = r#" |
| 1243 | pub struct SomeStruct(i32); |
| 1244 | "#; |
| 1245 | test_format_def(test_src, "SomeStruct", |result| { |
| 1246 | let err = result.expect_err("Test expects an error here"); |
| 1247 | assert_eq!(err, "Unsupported rustc_hir::hir::ItemKind: struct"); |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1248 | }); |
| 1249 | } |
| 1250 | |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1251 | #[test] |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 1252 | fn test_format_ret_ty_successes() { |
| 1253 | // Test coverage for cases where `format_ret_ty` returns an `Ok(...)`. |
| 1254 | // Additional testcases are covered by `test_format_ty_successes` |
| 1255 | // (because `format_ret_ty` delegates most cases to `format_ty`). |
| 1256 | let testcases = [ |
| 1257 | // ( <Rust type>, <expected C++ type> ) |
| 1258 | ("bool", "bool"), // TyKind::Bool |
| 1259 | ("()", "void"), |
| 1260 | // TODO(b/254507801): Expect `crubit::Never` instead (see the bug for more |
| 1261 | // details). |
| 1262 | ("!", "void"), |
| 1263 | ]; |
Lukasz Anforowicz | 4047272 | 2022-11-08 13:29:08 -0800 | [diff] [blame] | 1264 | test_ty(&testcases, quote! {}, |desc, ty, expected| { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 1265 | let actual = { |
| 1266 | let cc_snippet = format_ret_ty(ty).unwrap(); |
| 1267 | assert!(cc_snippet.includes.is_empty()); |
| 1268 | cc_snippet.snippet.to_string() |
| 1269 | }; |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 1270 | let expected = expected.parse::<TokenStream>().unwrap().to_string(); |
| 1271 | assert_eq!(actual, expected, "{desc}"); |
| 1272 | }); |
| 1273 | } |
| 1274 | |
| 1275 | #[test] |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1276 | fn test_format_ty_successes() { |
| 1277 | // Test coverage for cases where `format_ty` returns an `Ok(...)`. |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 1278 | // |
| 1279 | // Using `std::int8_t` (instead of `::std::int8_t`) has been an explicit decision. The |
| 1280 | // "Google C++ Style Guide" suggests to "avoid nested namespaces that match well-known |
| 1281 | // top-level namespaces" and "in particular, [...] not create any nested std namespaces.". |
| 1282 | // It seems desirable if the generated bindings conform to this aspect of the style guide, |
| 1283 | // because it makes things easier for *users* of these bindings. |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1284 | let testcases = [ |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 1285 | // ( <Rust type>, (<expected C++ type>, <expected #include>) ) |
| 1286 | ("bool", ("bool", "")), |
| 1287 | ("f32", ("float", "")), |
| 1288 | ("f64", ("double", "")), |
| 1289 | ("i8", ("std::int8_t", "cstdint")), |
| 1290 | ("i16", ("std::int16_t", "cstdint")), |
| 1291 | ("i32", ("std::int32_t", "cstdint")), |
| 1292 | ("i64", ("std::int64_t", "cstdint")), |
| 1293 | ("isize", ("std::intptr_t", "cstdint")), |
| 1294 | ("u8", ("std::uint8_t", "cstdint")), |
| 1295 | ("u16", ("std::uint16_t", "cstdint")), |
| 1296 | ("u32", ("std::uint32_t", "cstdint")), |
| 1297 | ("u64", ("std::uint64_t", "cstdint")), |
| 1298 | ("usize", ("std::uintptr_t", "cstdint")), |
| 1299 | ("char", ("std::uint32_t", "cstdint")), |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1300 | // Extra parens/sugar are expected to be ignored: |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 1301 | ("(bool)", ("bool", "")), |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1302 | ]; |
Lukasz Anforowicz | 4047272 | 2022-11-08 13:29:08 -0800 | [diff] [blame] | 1303 | let preamble = quote! { |
| 1304 | #![allow(unused_parens)] |
| 1305 | }; |
| 1306 | test_ty(&testcases, preamble, |desc, ty, (expected_snippet, expected_include)| { |
Lukasz Anforowicz | ed17d05 | 2022-11-02 12:07:28 -0700 | [diff] [blame] | 1307 | let (actual_snippet, actual_includes) = { |
| 1308 | let cc_snippet = format_ty(ty).unwrap(); |
| 1309 | (cc_snippet.snippet.to_string(), cc_snippet.includes) |
| 1310 | }; |
| 1311 | |
| 1312 | let expected_snippet = expected_snippet.parse::<TokenStream>().unwrap().to_string(); |
| 1313 | assert_eq!(actual_snippet, expected_snippet, "{desc}"); |
| 1314 | |
| 1315 | if expected_include.is_empty() { |
| 1316 | assert!(actual_includes.is_empty()); |
| 1317 | } else { |
| 1318 | let expected_header = format_cc_ident(expected_include).unwrap(); |
| 1319 | assert_cc_matches!( |
| 1320 | format_cc_includes(&actual_includes), |
| 1321 | quote! { include <#expected_header> } |
| 1322 | ); |
| 1323 | } |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1324 | }); |
| 1325 | } |
| 1326 | |
| 1327 | #[test] |
| 1328 | fn test_format_ty_failures() { |
| 1329 | // This test provides coverage for cases where `format_ty` returns an |
| 1330 | // `Err(...)`. |
| 1331 | // |
| 1332 | // TODO(lukasza): Add test coverage for: |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1333 | // - TyKind::Bound |
| 1334 | // - TyKind::Dynamic (`dyn Eq`) |
| 1335 | // - TyKind::Foreign (`extern type T`) |
| 1336 | // - https://doc.rust-lang.org/beta/unstable-book/language-features/generators.html: |
| 1337 | // TyKind::Generator, TyKind::GeneratorWitness |
| 1338 | // - TyKind::Param |
| 1339 | // - TyKind::Placeholder |
| 1340 | // - TyKind::Projection |
| 1341 | // |
| 1342 | // It seems okay to have no test coverage for now for the following types (which |
| 1343 | // should never be encountered when generating bindings and where |
| 1344 | // `format_ty` should panic): |
| 1345 | // - TyKind::Closure |
| 1346 | // - TyKind::Error |
| 1347 | // - TyKind::FnDef |
| 1348 | // - TyKind::Infer */ |
| 1349 | let testcases = [ |
| 1350 | // ( <Rust type>, <expected error message> ) |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1351 | ( |
Lukasz Anforowicz | 903fc63 | 2022-10-25 08:55:33 -0700 | [diff] [blame] | 1352 | "()", // Empty TyKind::Tuple |
| 1353 | "The unit type `()` / `void` is only supported as a return type" |
| 1354 | ), |
| 1355 | ( |
| 1356 | // TODO(b/254507801): Expect `crubit::Never` instead (see the bug for more |
| 1357 | // details). |
| 1358 | "!", // TyKind::Never |
| 1359 | "The never type `!` is only supported as a return type (b/254507801)" |
| 1360 | ), |
| 1361 | ( |
| 1362 | "(i32, i32)", // Non-empty TyKind::Tuple |
Lukasz Anforowicz | 13794df | 2022-10-21 07:56:34 -0700 | [diff] [blame] | 1363 | "Tuples are not supported yet: (i32, i32) (b/254099023)", |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1364 | ), |
| 1365 | ( |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1366 | "*const i32", // TyKind::Ptr |
| 1367 | "The following Rust type is not supported yet: *const i32", |
| 1368 | ), |
| 1369 | ( |
| 1370 | "&'static i32", // TyKind::Ref |
| 1371 | "The following Rust type is not supported yet: &'static i32", |
| 1372 | ), |
| 1373 | ( |
| 1374 | "[i32; 42]", // TyKind::Array |
| 1375 | "The following Rust type is not supported yet: [i32; 42]", |
| 1376 | ), |
| 1377 | ( |
| 1378 | "&'static [i32]", // TyKind::Slice (nested underneath TyKind::Ref) |
| 1379 | "The following Rust type is not supported yet: &'static [i32]", |
| 1380 | ), |
| 1381 | ( |
| 1382 | "&'static str", // TyKind::Str (nested underneath TyKind::Ref) |
| 1383 | "The following Rust type is not supported yet: &'static str", |
| 1384 | ), |
| 1385 | ( |
| 1386 | "impl Eq", // TyKind::Opaque |
| 1387 | "The following Rust type is not supported yet: impl std::cmp::Eq", |
| 1388 | ), |
| 1389 | ( |
| 1390 | "fn(i32) -> i32", // TyKind::FnPtr |
| 1391 | "The following Rust type is not supported yet: fn(i32) -> i32", |
| 1392 | ), |
| 1393 | // TODO(b/254094650): Consider mapping this to Clang's (and GCC's) `__int128` |
| 1394 | // or to `absl::in128`. |
| 1395 | ("i128", "C++ doesn't have a standard equivalent of `i128` (b/254094650)"), |
| 1396 | ("u128", "C++ doesn't have a standard equivalent of `u128` (b/254094650)"), |
Lukasz Anforowicz | 4047272 | 2022-11-08 13:29:08 -0800 | [diff] [blame] | 1397 | ("SomeStruct", "The following Rust type is not supported yet: SomeStruct"), |
| 1398 | ("SomeEnum", "The following Rust type is not supported yet: SomeEnum"), |
| 1399 | ("SomeUnion", "The following Rust type is not supported yet: SomeUnion"), |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1400 | ]; |
Lukasz Anforowicz | 4047272 | 2022-11-08 13:29:08 -0800 | [diff] [blame] | 1401 | let preamble = quote! { |
| 1402 | pub struct SomeStruct { |
| 1403 | pub x: i32, |
| 1404 | pub y: i32, |
| 1405 | } |
| 1406 | pub enum SomeEnum { |
| 1407 | Cartesian{x: f64, y: f64}, |
| 1408 | Polar{angle: f64, dist: f64}, |
| 1409 | } |
| 1410 | pub union SomeUnion { |
| 1411 | pub x: i32, |
| 1412 | pub y: i32, |
| 1413 | } |
| 1414 | }; |
| 1415 | test_ty(&testcases, preamble, |desc, ty, expected_err| { |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1416 | let anyhow_err = format_ty(ty).unwrap_err(); |
| 1417 | let actual_err = format!("{anyhow_err:#}"); |
| 1418 | assert_eq!(&actual_err, *expected_err, "{desc}"); |
| 1419 | }); |
| 1420 | } |
| 1421 | |
Lukasz Anforowicz | 4047272 | 2022-11-08 13:29:08 -0800 | [diff] [blame] | 1422 | fn test_ty<TestFn, Expectation>( |
| 1423 | testcases: &[(&str, Expectation)], |
| 1424 | preamble: TokenStream, |
| 1425 | test_fn: TestFn, |
| 1426 | ) where |
Lukasz Anforowicz | d0f0a84 | 2022-11-03 12:40:13 -0700 | [diff] [blame] | 1427 | TestFn: Fn(/* testcase_description: */ &str, Ty, &Expectation) + Sync, |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1428 | Expectation: Sync, |
| 1429 | { |
Lukasz Anforowicz | d0f0a84 | 2022-11-03 12:40:13 -0700 | [diff] [blame] | 1430 | for (index, (input, expected)) in testcases.iter().enumerate() { |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1431 | let desc = format!("test #{index}: test input: `{input}`"); |
| 1432 | let input = { |
| 1433 | let ty_tokens: TokenStream = input.parse().unwrap(); |
| 1434 | let input = quote! { |
Lukasz Anforowicz | 4047272 | 2022-11-08 13:29:08 -0800 | [diff] [blame] | 1435 | #preamble |
Lukasz Anforowicz | 4f9f552 | 2022-10-17 15:03:37 -0700 | [diff] [blame] | 1436 | pub fn test_function() -> #ty_tokens { panic!("") } |
| 1437 | }; |
| 1438 | input.to_string() |
| 1439 | }; |
| 1440 | run_compiler(input, |tcx| { |
| 1441 | let def_id = find_def_id_by_name(tcx, "test_function"); |
| 1442 | let ty = tcx.fn_sig(def_id.to_def_id()).no_bound_vars().unwrap().output(); |
| 1443 | test_fn(&desc, ty, expected); |
| 1444 | }); |
| 1445 | } |
| 1446 | } |
| 1447 | |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1448 | /// Tests invoking `format_def` on the item with the specified `name` from |
| 1449 | /// the given Rust `source`. Returns the result of calling |
| 1450 | /// `test_function` with `format_def`'s result as an argument. |
| 1451 | /// (`test_function` should typically `assert!` that it got the expected |
| 1452 | /// result from `format_def`.) |
| 1453 | fn test_format_def<F, T>(source: &str, name: &str, test_function: F) -> T |
| 1454 | where |
Lukasz Anforowicz | f04bc52 | 2022-11-04 09:19:51 -0700 | [diff] [blame] | 1455 | F: FnOnce(Result<BindingsSnippet, String>) -> T + Send, |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1456 | T: Send, |
| 1457 | { |
| 1458 | run_compiler(source, |tcx| { |
| 1459 | let def_id = find_def_id_by_name(tcx, name); |
Lukasz Anforowicz | e433306 | 2022-10-17 14:47:53 -0700 | [diff] [blame] | 1460 | let result = format_def(tcx, def_id); |
| 1461 | |
| 1462 | // https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations says: |
| 1463 | // To print causes as well [...], use the alternate selector “{:#}”. |
| 1464 | let result = result.map_err(|anyhow_err| format!("{anyhow_err:#}")); |
| 1465 | |
| 1466 | test_function(result) |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1467 | }) |
| 1468 | } |
| 1469 | |
| 1470 | /// Finds the definition id of a Rust item with the specified `name`. |
| 1471 | /// Panics if no such item is found, or if there is more than one match. |
| 1472 | fn find_def_id_by_name(tcx: TyCtxt, name: &str) -> LocalDefId { |
| 1473 | let hir_items = || tcx.hir().items().map(|item_id| tcx.hir().item(item_id)); |
| 1474 | let items_with_matching_name = |
| 1475 | hir_items().filter(|item| item.ident.name.as_str() == name).collect_vec(); |
Lukasz Anforowicz | d0f0a84 | 2022-11-03 12:40:13 -0700 | [diff] [blame] | 1476 | match *items_with_matching_name.as_slice() { |
| 1477 | [] => { |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1478 | let found_names = hir_items() |
| 1479 | .map(|item| item.ident.name.as_str()) |
| 1480 | .filter(|s| !s.is_empty()) |
| 1481 | .sorted() |
| 1482 | .dedup() |
| 1483 | .map(|name| format!("`{name}`")) |
Lukasz Anforowicz | d0f0a84 | 2022-11-03 12:40:13 -0700 | [diff] [blame] | 1484 | .join(",\n"); |
| 1485 | panic!("No items named `{name}`.\nInstead found:\n{found_names}"); |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1486 | } |
Lukasz Anforowicz | 61cb1e3 | 2022-11-04 09:08:35 -0700 | [diff] [blame] | 1487 | [item] => item.owner_id.def_id, |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1488 | _ => panic!("More than one item named `{name}`"), |
| 1489 | } |
| 1490 | } |
| 1491 | |
| 1492 | /// Tests invoking `GeneratedBindings::generate` on the given Rust `source`. |
| 1493 | /// Returns the result of calling `test_function` with the generated |
| 1494 | /// bindings as an argument. (`test_function` should typically `assert!` |
| 1495 | /// that it got the expected `GeneratedBindings`.) |
| 1496 | fn test_generated_bindings<F, T>(source: &str, test_function: F) -> T |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 1497 | where |
Lukasz Anforowicz | 24160d5 | 2022-10-19 06:45:45 -0700 | [diff] [blame] | 1498 | F: FnOnce(Result<GeneratedBindings>) -> T + Send, |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 1499 | T: Send, |
| 1500 | { |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1501 | run_compiler(source, |tcx| test_function(GeneratedBindings::generate(tcx))) |
Lukasz Anforowicz | 581fd75 | 2022-09-21 11:30:15 -0700 | [diff] [blame] | 1502 | } |
| 1503 | |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1504 | /// Invokes the Rust compiler on the given Rust `source` and then calls `f` |
| 1505 | /// on the `TyCtxt` representation of the compiled `source`. |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 1506 | fn run_compiler<F, T>(source: impl Into<String>, f: F) -> T |
| 1507 | where |
Lukasz Anforowicz | 8a83341 | 2022-10-14 14:44:13 -0700 | [diff] [blame] | 1508 | F: for<'tcx> FnOnce(TyCtxt<'tcx>) -> T + Send, |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 1509 | T: Send, |
| 1510 | { |
Lukasz Anforowicz | 24160d5 | 2022-10-19 06:45:45 -0700 | [diff] [blame] | 1511 | use rustc_session::config::{ |
| 1512 | CodegenOptions, CrateType, Input, Options, OutputType, OutputTypes, |
| 1513 | }; |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 1514 | |
| 1515 | const TEST_FILENAME: &str = "crubit_unittests.rs"; |
| 1516 | |
| 1517 | // Setting `output_types` that will trigger code gen - otherwise some parts of |
| 1518 | // the analysis will be missing (e.g. `tcx.exported_symbols()`). |
| 1519 | // The choice of `Bitcode` is somewhat arbitrary (e.g. `Assembly`, |
| 1520 | // `Mir`, etc. would also trigger code gen). |
| 1521 | let output_types = OutputTypes::new(&[(OutputType::Bitcode, None /* PathBuf */)]); |
| 1522 | |
| 1523 | let opts = Options { |
| 1524 | crate_types: vec![CrateType::Rlib], // Test inputs simulate library crates. |
Lukasz Anforowicz | f018e4d | 2022-09-28 07:35:59 -0700 | [diff] [blame] | 1525 | maybe_sysroot: Some(get_sysroot_for_testing()), |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 1526 | output_types, |
Lukasz Anforowicz | 60ccaf3 | 2022-09-30 15:04:22 -0700 | [diff] [blame] | 1527 | edition: rustc_span::edition::Edition::Edition2021, |
Lukasz Anforowicz | 5bddf18 | 2022-09-30 16:06:59 -0700 | [diff] [blame] | 1528 | unstable_features: rustc_feature::UnstableFeatures::Allow, |
Lukasz Anforowicz | 88bde9b | 2022-10-14 14:48:07 -0700 | [diff] [blame] | 1529 | lint_opts: vec![ |
| 1530 | ("warnings".to_string(), rustc_lint_defs::Level::Deny), |
| 1531 | ("stable_features".to_string(), rustc_lint_defs::Level::Allow), |
| 1532 | ], |
Lukasz Anforowicz | 24160d5 | 2022-10-19 06:45:45 -0700 | [diff] [blame] | 1533 | cg: CodegenOptions { |
| 1534 | panic: Some(rustc_target::spec::PanicStrategy::Abort), |
| 1535 | ..Default::default() |
| 1536 | }, |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 1537 | ..Default::default() |
| 1538 | }; |
| 1539 | |
| 1540 | let config = rustc_interface::interface::Config { |
| 1541 | opts, |
| 1542 | crate_cfg: Default::default(), |
| 1543 | crate_check_cfg: Default::default(), |
| 1544 | input: Input::Str { |
| 1545 | name: rustc_span::FileName::Custom(TEST_FILENAME.to_string()), |
| 1546 | input: source.into(), |
| 1547 | }, |
| 1548 | input_path: None, |
| 1549 | output_file: None, |
| 1550 | output_dir: None, |
| 1551 | file_loader: None, |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 1552 | lint_caps: Default::default(), |
| 1553 | parse_sess_created: None, |
| 1554 | register_lints: None, |
| 1555 | override_queries: None, |
| 1556 | make_codegen_backend: None, |
| 1557 | registry: rustc_errors::registry::Registry::new(rustc_error_codes::DIAGNOSTICS), |
| 1558 | }; |
| 1559 | |
Lukasz Anforowicz | 88bde9b | 2022-10-14 14:48:07 -0700 | [diff] [blame] | 1560 | rustc_interface::interface::run_compiler(config, |compiler| { |
| 1561 | compiler.enter(|queries| { |
| 1562 | use rustc_interface::interface::Result; |
| 1563 | let result: Result<Result<()>> = super::enter_tcx(queries, |tcx| { |
| 1564 | // Explicitly force full `analysis` stage to detect compilation |
| 1565 | // errors that the earlier stages might miss. This helps ensure that the |
| 1566 | // test inputs are valid Rust (even if `f` wouldn't |
| 1567 | // have triggered full analysis). |
| 1568 | tcx.analysis(()) |
| 1569 | }); |
Lukasz Anforowicz | e959958 | 2022-09-30 15:54:46 -0700 | [diff] [blame] | 1570 | |
Lukasz Anforowicz | 88bde9b | 2022-10-14 14:48:07 -0700 | [diff] [blame] | 1571 | // Flatten the outer and inner results into a single result. (outer result |
| 1572 | // comes from `enter_tcx`; inner result comes from `analysis`). |
| 1573 | // |
| 1574 | // TODO(lukasza): Use `Result::flatten` API when it gets stabilized. See also |
| 1575 | // https://github.com/rust-lang/rust/issues/70142 |
| 1576 | let result: Result<()> = result.and_then(|result| result); |
Lukasz Anforowicz | e959958 | 2022-09-30 15:54:46 -0700 | [diff] [blame] | 1577 | |
Lukasz Anforowicz | 88bde9b | 2022-10-14 14:48:07 -0700 | [diff] [blame] | 1578 | // `analysis` might succeed even if there are some lint / warning errors. |
| 1579 | // Detecting these requires explicitly checking `compile_status`. |
| 1580 | let result: Result<()> = result.and_then(|()| compiler.session().compile_status()); |
| 1581 | |
| 1582 | // Run the provided callback. |
| 1583 | let result: Result<T> = result.and_then(|()| super::enter_tcx(queries, f)); |
| 1584 | result.expect("Test inputs shouldn't cause compilation errors") |
| 1585 | }) |
| 1586 | }) |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 1587 | } |
Lukasz Anforowicz | bda1cfe | 2022-09-20 06:25:43 -0700 | [diff] [blame] | 1588 | } |