Skip to main content

normpath_macros/
lib.rs

1//! This crate provides procedural macros for [normpath].
2//!
3//! **Do not add this crate as a dependency.** It has no backward compatibility
4//! guarantees. It is only used for development of [normpath].
5//!
6//! [normpath]: https://crates.io/crates/normpath
7
8#![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
58// https://docs.rs/syn/1.0/syn/struct.Error.html
59struct 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
110// https://docs.rs/syn/1.0/syn/type.Result.html
111type 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}