blob: 9c9769ba501ae1e2fa1a9316ae2966133af3478a [file] [log] [blame]
Devin Jeanpierredcf51432023-04-13 14:49:25 -07001// 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
5//! Macros for testing, at runtime, whether an item exists at runtime.
6//! Note that these must be procedural macros because they are fundamentally
7//! non-hygienic in how they attempts to test whether an item exists: each tries
8//! to introduce shadowing from inside the macro, which hygiene normally
9//! prevents.
10
11use proc_macro::TokenStream;
12use quote::quote;
13use syn::punctuated::Pair;
14use syn::spanned::Spanned as _;
15
16/// Returns whether the named type exists.
17///
18/// For example, `type_exists!(std::num::NonZeroU8) == true`, but at the time
19/// of writing, `type_exists!(std::num::ThisDoesntExist) == false`.
20///
21/// If the provided name does not refer to a type, it returns `false`.
22///
23/// This is a handy macro to have at hand for integration tests of the bindings
24/// generator, as it allows one to assert at _runtime_ whether bindings were
25/// generated for a type, instead of at compile-time.
26///
27/// Known limitations:
28///
29/// * Does not support names that are already in scope (e.g. `i32`).
30/// * Does not support generics (e.g. `std::vec::Vec<i32>`).
31/// * Does not support associated types (e.g. `Option<i32>::Item`).
32#[proc_macro]
33pub fn type_exists(path: TokenStream) -> TokenStream {
34 let (path, name) = match extract_last(syn::parse_macro_input!(path as syn::Path)) {
35 Ok((path, name)) => (path, name),
36 Err(e) => return e.into_compile_error().into(),
37 };
38
39 TokenStream::from(quote! {
40 {
41 #[allow(non_camel_case_types)]
42 struct #name;
43 let fallback_id = ::core::any::TypeId::of::<#name>();
44
45 // introduce a new scope, so that we can shadow `#name`.
46 let type_id = {
47 #[allow(unused_imports)]
48 use #path *;
49 ::core::any::TypeId::of::<#name>()
50 };
51 fallback_id != type_id
52 }
53 })
54}
55
56/// Returns whether the named static, constant, or function exists.
57///
58/// For example, `value_exists!(std::f32::consts::E) == true`, but at the time
59/// of writing, `value_exists!(std::f32::consts::DOES_NOT_EXIST) == false`.
60///
61/// If the provided name does not refer to a runtime value
62/// (such as a non-templated function, constant, or static), it returns `false`.
63///
64/// This is a handy macro to have at hand for integration tests of the bindings
65/// generator, as it allows one to assert at _runtime_ whether bindings were
66/// generated for a function or constant, instead of at compile-time.
67///
68/// Known limitations:
69///
70/// * Does not support names that are already in scope (e.g. `i32`).
71/// * Does not support generics (e.g. `std::vec::Vec::<i32>`).
72/// * Does not support methods or associated constants (e.g.
73/// `Option<i32>::default`).
74#[proc_macro]
75pub fn value_exists(path: TokenStream) -> TokenStream {
76 let (path, name) = match extract_last(syn::parse_macro_input!(path as syn::Path)) {
77 Ok((path, name)) => (path, name),
78 Err(e) => return e.into_compile_error().into(),
79 };
80
81 TokenStream::from(quote! {
82 {
83 /// A new type, introduced to differentiate types.
84 /// If we made it a non-ZST, we could also differentiate based on address.
85 /// Technically, that'd have a slight performance cost, which this avoids. ;)))
86 #[allow(non_camel_case_types)]
87 struct #name {}
88 #[allow(non_upper_case_globals)]
89 static #name : #name = #name {};
90 // Alternatively: let fallback_address = &#name as *const _ as usize;
91 let fallback_id = ::std::any::Any::type_id(&#name);
92
93 // introduce a new scope, so that we can shadow `#name`.
94 let type_id = {
95 #[allow(unused_imports)]
96 use #path *;
97 // Alternatively: &#name as *const _ as usize
98 ::std::any::Any::type_id(&#name)
99 };
100 fallback_id != type_id
101 }
102 })
103}
104
105fn extract_last(mut path: syn::Path) -> Result<(syn::Path, syn::PathSegment), syn::Error> {
106 let name = match path.segments.pop() {
107 None => {
108 return Err(syn::Error::new(
109 path.span(),
110 "Path must have at least two elements (e.g. A::B)",
111 ));
112 }
113 Some(Pair::Punctuated(..)) => {
114 return Err(syn::Error::new(path.span(), "Path must not end in `::`"));
115 }
116 Some(Pair::End(name)) => name,
117 };
118
119 if path.segments.is_empty() {
120 // TODO(jeanpierreda): If it were desirable, we could allow `type_exists!(X)` etc, by
121 // creating a `mod` to hold it for shadowing purposes.
122 return Err(syn::Error::new(
123 path.span(),
124 "Path must have at least two elements (e.g. A::B)",
125 ));
126 }
127 Ok((path, name))
128}