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}