1#![cfg(windows)]
9#![warn(unused_results)]
10
11use std::io;
12use std::iter;
13use std::mem::MaybeUninit;
14use std::result;
15
16use proc_macro::Delimiter;
17use proc_macro::Group;
18use proc_macro::Ident;
19use proc_macro::Literal;
20use proc_macro::Punct;
21use proc_macro::Spacing;
22use proc_macro::Span;
23use proc_macro::TokenStream;
24use proc_macro::TokenTree;
25
26use windows_sys::Wdk::System::SystemServices::RtlGetVersion;
27use windows_sys::Win32::Foundation::STATUS_SUCCESS;
28use windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW;
29
30trait TokenStreamExt {
31 fn push<T>(&mut self, token: T)
32 where
33 T: Into<TokenTree>;
34}
35
36impl TokenStreamExt for TokenStream {
37 fn push<T>(&mut self, token: T)
38 where
39 T: Into<TokenTree>,
40 {
41 self.extend(iter::once(token.into()));
42 }
43}
44
45fn macro_path(module: &str, name: &str) -> impl Iterator<Item = TokenTree> {
46 [
47 Punct::new(':', Spacing::Joint).into(),
48 Punct::new(':', Spacing::Alone).into(),
49 Ident::new(module, Span::call_site()).into(),
50 Punct::new(':', Spacing::Joint).into(),
51 Punct::new(':', Spacing::Alone).into(),
52 Ident::new(name, Span::call_site()).into(),
53 Punct::new('!', Spacing::Alone).into(),
54 ]
55 .into_iter()
56}
57
58struct Error {
60 start: Span,
61 end: Span,
62 message: String,
63}
64
65impl Error {
66 const fn new(span: Span, message: String) -> Self {
67 Self {
68 start: span,
69 end: span,
70 message,
71 }
72 }
73
74 fn new_spanned<T>(tokens: T, message: &'static str) -> Self
75 where
76 T: Into<TokenStream>,
77 {
78 let mut tokens = tokens.into().into_iter();
79 let start = tokens
80 .next()
81 .map(|x| x.span())
82 .unwrap_or_else(Span::call_site);
83 Self {
84 start,
85 end: tokens.last().map(|x| x.span()).unwrap_or(start),
86 message: message.to_owned(),
87 }
88 }
89
90 fn into_compile_error(self) -> TokenStream {
91 let mut result: TokenStream = macro_path("std", "compile_error")
92 .map(|mut token| {
93 token.set_span(self.start);
94 token
95 })
96 .collect();
97
98 let mut literal = Literal::string(&self.message);
99 literal.set_span(self.end);
100
101 let mut group =
102 Group::new(Delimiter::Brace, TokenTree::Literal(literal).into());
103 group.set_span(self.end);
104
105 result.push(group);
106 result
107 }
108}
109
110type Result<T> = result::Result<T, Error>;
112
113fn get_windows_version() -> Result<OSVERSIONINFOW> {
114 let mut version_info = MaybeUninit::uninit();
115 let result = unsafe { RtlGetVersion(version_info.as_mut_ptr()) };
116 if result == STATUS_SUCCESS {
117 Ok(unsafe { version_info.assume_init() })
118 } else {
119 let error = io::Error::last_os_error();
120 Err(Error::new(
121 Span::call_site(),
122 format!("failed syscall: {}", error),
123 ))
124 }
125}
126
127#[proc_macro_attribute]
128pub fn cfg_supports_joined_device_paths(
129 args: TokenStream,
130 item: TokenStream,
131) -> TokenStream {
132 let Ok(enable) = args.to_string().parse::<bool>() else {
133 return Error::new_spanned(args, "argument must be a boolean")
134 .into_compile_error();
135 };
136
137 let supported = match get_windows_version() {
138 Ok(version_info) => version_info.dwBuildNumber < 21000,
139 Err(error) => return error.into_compile_error(),
140 };
141
142 if enable == supported {
143 item
144 } else {
145 TokenStream::new()
146 }
147}