cxx_qt_macro/
lib.rs

1// SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
3// SPDX-FileContributor: Gerhard de Clercq <gerhard.declercq@kdab.com>
4//
5// SPDX-License-Identifier: MIT OR Apache-2.0
6
7#![deny(missing_docs)]
8//! The cxx-qt-macro crate provides the procedural attribute macros which are used with cxx-qt.
9
10use proc_macro::TokenStream;
11use syn::{parse_macro_input, ItemMod};
12
13use cxx_qt_gen::{write_rust, GeneratedRustBlocks, Parser};
14
15/// A procedural macro which generates a QObject for a struct inside a module.
16///
17/// # Example
18///
19/// ```rust
20/// #[cxx_qt::bridge(namespace = "cxx_qt::my_object")]
21/// mod qobject {
22///     unsafe extern "RustQt" {
23///         #[qobject]
24///         # // Note that we can't use properties as this confuses the linker on Windows
25///         type MyObject = super::MyObjectRust;
26///
27///         #[qinvokable]
28///         fn invokable(self: &MyObject, a: i32, b: i32) -> i32;
29///     }
30/// }
31///
32/// #[derive(Default)]
33/// pub struct MyObjectRust;
34///
35/// impl qobject::MyObject {
36///     fn invokable(&self, a: i32, b: i32) -> i32 {
37///         a + b
38///     }
39/// }
40///
41/// # // Note that we need a fake main for doc tests to build
42/// # fn main() {}
43/// ```
44#[proc_macro_attribute]
45pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream {
46    // Parse the TokenStream of a macro
47    // this triggers a compile failure if the tokens fail to parse.
48    let mut module = parse_macro_input!(input as ItemMod);
49
50    // Macros do not typically need to do anything with their own attribute name,
51    // so rustc does not include that in the `args` or `input` TokenStreams.
52    //
53    // However, other code paths that use the parser do not enter from a macro invocation,
54    // so they rely on parsing the `cxx_qt::bridge` attribute to identify where to start parsing.
55    //
56    // To keep the inputs to the parser consistent for all code paths,
57    // add the attribute to the module before giving it to the parser.
58    let args_input = format!("#[cxx_qt::bridge({args})] mod dummy;");
59    let attrs = syn::parse_str::<ItemMod>(&args_input).unwrap().attrs;
60    module.attrs = attrs.into_iter().chain(module.attrs).collect();
61
62    // Extract and generate the rust code
63    extract_and_generate(module)
64}
65
66/// A macro which describes that a struct should be made into a QObject.
67///
68/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition.
69///
70/// # Example
71///
72/// ```rust
73/// #[cxx_qt::bridge]
74/// mod my_object {
75///     extern "RustQt" {
76///         #[qobject]
77///         # // Note that we can't use properties as this confuses the linker on Windows
78///         type MyObject = super::MyObjectRust;
79///     }
80/// }
81///
82/// #[derive(Default)]
83/// pub struct MyObjectRust;
84///
85/// # // Note that we need a fake main for doc tests to build
86/// # fn main() {}
87/// ```
88///
89/// You can also specify a custom base class by using `#[base = QStringListModel]`, you must then use CXX to add any includes needed.
90///
91/// # Example
92///
93/// ```rust
94/// #[cxx_qt::bridge]
95/// mod my_object {
96///     extern "RustQt" {
97///         #[qobject]
98///         #[base = QStringListModel]
99///         # // Note that we can't use properties as this confuses the linker on Windows
100///         type MyModel = super::MyModelRust;
101///     }
102///
103///     unsafe extern "C++" {
104///         include!(<QtCore/QStringListModel>);
105///         type QStringListModel;
106///     }
107/// }
108///
109/// #[derive(Default)]
110/// pub struct MyModelRust;
111///
112/// # // Note that we need a fake main for doc tests to build
113/// # fn main() {}
114/// ```
115#[proc_macro_attribute]
116pub fn qobject(_args: TokenStream, _input: TokenStream) -> TokenStream {
117    unreachable!("qobject should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition")
118}
119
120// Take the module and C++ namespace and generate the rust code
121fn extract_and_generate(module: ItemMod) -> TokenStream {
122    Parser::from(module)
123        .and_then(|parser| GeneratedRustBlocks::from(&parser))
124        .map(|generated_rust| write_rust(&generated_rust, None))
125        .unwrap_or_else(|err| err.to_compile_error())
126        .into()
127}