Rust: add proc-macro capabilities to QL tests

This adds the possibility to add a special `proc_macro.rs` source file
to QL tests, which will be generated into a `proc_macro` crate the
usual `lib` crate depends on.

This allow to define procedural macros in QL tests, and is here used to
move the `macro-expansion` integration test to be a language test
instead.

As the generated manifests involved were starting to get a bit complex,
they are now generated from a `mustache` template.
This commit is contained in:
Paolo Tranquilli
2025-06-17 16:19:50 +02:00
parent 17cd58bd3c
commit 299fe2eb3f
33 changed files with 2428 additions and 380 deletions

View File

@@ -1,3 +0,0 @@
[workspace]
members = [ "attributes", "calls", "proc_macros"]
resolver = "2"

View File

@@ -1,7 +0,0 @@
[package]
name = "attributes"
version = "0.1.0"
edition = "2024"
[dependencies]
proc_macros = { path = "../proc_macros" }

View File

@@ -1,16 +0,0 @@
use proc_macros::repeat;
#[repeat(3)]
fn foo() {
_ = concat!("Hello ", "world!");
#[repeat(2)]
fn inner() {}
}
#[repeat(2)]
#[repeat(3)]
fn bar() {}
#[repeat(0)]
fn baz() {}

View File

@@ -1,6 +0,0 @@
[package]
name = "calls"
version = "0.1.0"
edition = "2024"
[dependencies]

View File

@@ -1,3 +0,0 @@
fn included() {
_ = concat!("Hello", " ", "world!"); // this doesn't expand (in included.rs) since 0.0.274
}

View File

@@ -1,30 +0,0 @@
struct S;
macro_rules! def_x {
() => {
fn x() {}
};
}
impl S {
def_x!(); // this doesn't expand since 0.0.274
}
macro_rules! my_macro {
($head:expr, $($tail:tt)*) => { format!($head, $($tail)*) };
}
fn test() {
_ = concat!("x", "y");
_ = my_macro!(
concat!("<", "{}", ">"), // this doesn't expand since 0.0.274
"hi",
);
}
include!("included.rs");
#[doc = include_str!("some.txt")] // this doesn't expand since 0.0.274
fn documented() {}

View File

@@ -1,55 +0,0 @@
{
"attributes": {
"durations": {
"crateGraph": {
"ms": "__REDACTED__",
"pretty": "__REDACTED__"
},
"extract": {
"ms": "__REDACTED__",
"pretty": "__REDACTED__"
},
"extractLibrary": {
"ms": "__REDACTED__",
"pretty": "__REDACTED__"
},
"findManifests": {
"ms": "__REDACTED__",
"pretty": "__REDACTED__"
},
"loadManifest": {
"ms": "__REDACTED__",
"pretty": "__REDACTED__"
},
"loadSource": {
"ms": "__REDACTED__",
"pretty": "__REDACTED__"
},
"parse": {
"ms": "__REDACTED__",
"pretty": "__REDACTED__"
},
"parseLibrary": {
"ms": "__REDACTED__",
"pretty": "__REDACTED__"
},
"total": {
"ms": "__REDACTED__",
"pretty": "__REDACTED__"
}
},
"numberOfFiles": 4,
"numberOfManifests": 1
},
"severity": "note",
"source": {
"extractorName": "rust",
"id": "rust/extractor/telemetry",
"name": "telemetry"
},
"visibility": {
"cliSummaryTable": false,
"statusPage": false,
"telemetry": true
}
}

View File

@@ -1,11 +0,0 @@
[package]
name = "proc_macros"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
quote = "1.0.40"
syn = { version = "2.0.100", features = ["full"] }

View File

@@ -1,18 +0,0 @@
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro_attribute]
pub fn repeat(attr: TokenStream, item: TokenStream) -> TokenStream {
let number = syn::parse_macro_input!(attr as syn::LitInt).base10_parse::<usize>().unwrap();
let ast = syn::parse_macro_input!(item as syn::ItemFn);
let items = (0..number)
.map(|i| {
let mut new_ast = ast.clone();
new_ast.sig.ident = syn::Ident::new(&format!("{}_{}", ast.sig.ident, i), ast.sig.ident.span());
new_ast
})
.collect::<Vec<_>>();
quote! {
#(#items)*
}.into()
}

View File

@@ -1,4 +0,0 @@
attributes/src/lib.rs
calls/src/included.rs
calls/src/lib.rs
proc_macros/src/lib.rs

View File

@@ -1,34 +0,0 @@
attribute_macros
| attributes/src/lib.rs:3:1:9:1 | fn foo | 0 | attributes/src/lib.rs:4:1:8:16 | fn foo_0 |
| attributes/src/lib.rs:3:1:9:1 | fn foo | 1 | attributes/src/lib.rs:4:1:8:16 | fn foo_1 |
| attributes/src/lib.rs:3:1:9:1 | fn foo | 2 | attributes/src/lib.rs:4:1:8:16 | fn foo_2 |
| attributes/src/lib.rs:7:5:8:16 | fn inner | 0 | attributes/src/lib.rs:8:5:8:16 | fn inner_0 |
| attributes/src/lib.rs:7:5:8:16 | fn inner | 0 | attributes/src/lib.rs:8:5:8:16 | fn inner_0 |
| attributes/src/lib.rs:7:5:8:16 | fn inner | 0 | attributes/src/lib.rs:8:5:8:16 | fn inner_0 |
| attributes/src/lib.rs:7:5:8:16 | fn inner | 1 | attributes/src/lib.rs:8:5:8:16 | fn inner_1 |
| attributes/src/lib.rs:7:5:8:16 | fn inner | 1 | attributes/src/lib.rs:8:5:8:16 | fn inner_1 |
| attributes/src/lib.rs:7:5:8:16 | fn inner | 1 | attributes/src/lib.rs:8:5:8:16 | fn inner_1 |
| attributes/src/lib.rs:11:1:13:11 | fn bar | 0 | attributes/src/lib.rs:12:1:13:10 | fn bar_0 |
| attributes/src/lib.rs:11:1:13:11 | fn bar | 1 | attributes/src/lib.rs:12:1:13:10 | fn bar_1 |
| attributes/src/lib.rs:12:1:13:10 | fn bar_0 | 0 | attributes/src/lib.rs:13:1:13:10 | fn bar_0_0 |
| attributes/src/lib.rs:12:1:13:10 | fn bar_0 | 1 | attributes/src/lib.rs:13:1:13:10 | fn bar_0_1 |
| attributes/src/lib.rs:12:1:13:10 | fn bar_0 | 2 | attributes/src/lib.rs:13:1:13:10 | fn bar_0_2 |
| attributes/src/lib.rs:12:1:13:10 | fn bar_1 | 0 | attributes/src/lib.rs:13:1:13:10 | fn bar_1_0 |
| attributes/src/lib.rs:12:1:13:10 | fn bar_1 | 1 | attributes/src/lib.rs:13:1:13:10 | fn bar_1_1 |
| attributes/src/lib.rs:12:1:13:10 | fn bar_1 | 2 | attributes/src/lib.rs:13:1:13:10 | fn bar_1_2 |
macro_calls
| attributes/src/lib.rs:5:9:5:34 | concat!... | attributes/src/lib.rs:5:17:5:34 | "Hello world!" |
| attributes/src/lib.rs:5:9:5:34 | concat!... | attributes/src/lib.rs:5:17:5:34 | "Hello world!" |
| attributes/src/lib.rs:5:9:5:34 | concat!... | attributes/src/lib.rs:5:17:5:34 | "Hello world!" |
| calls/src/included.rs:2:9:2:39 | concat!... | calls/src/included.rs:2:17:2:38 | "Hello world!" |
| calls/src/lib.rs:10:5:10:13 | def_x!... | calls/src/lib.rs:10:5:10:13 | MacroItems |
| calls/src/lib.rs:19:9:19:25 | concat!... | calls/src/lib.rs:19:17:19:24 | "xy" |
| calls/src/lib.rs:21:9:24:5 | my_macro!... | calls/src/lib.rs:22:9:23:13 | MacroExpr |
| calls/src/lib.rs:22:9:22:31 | concat!... | calls/src/lib.rs:22:17:22:30 | "<{}>" |
| calls/src/lib.rs:22:9:23:13 | ...::format_args!... | calls/src/lib.rs:22:9:23:13 | FormatArgsExpr |
| calls/src/lib.rs:22:9:23:13 | format!... | calls/src/lib.rs:22:9:23:13 | ...::must_use(...) |
| calls/src/lib.rs:27:1:27:24 | concat!... | calls/src/lib.rs:27:1:27:24 | "Hello world!" |
| calls/src/lib.rs:27:1:27:24 | include!... | calls/src/lib.rs:27:1:27:24 | MacroItems |
| calls/src/lib.rs:29:9:29:32 | include_str!... | calls/src/lib.rs:29:22:29:31 | "" |
unexpanded_macro_calls
| attributes/src/lib.rs:5:9:5:35 | concat!... |

View File

@@ -1,15 +0,0 @@
import rust
query predicate attribute_macros(Item i, int index, Item expanded) {
i.fromSource() and expanded = i.getAttributeMacroExpansion().getItem(index)
}
query predicate macro_calls(MacroCall c, AstNode expansion) {
c.fromSource() and
not c.getLocation().getFile().getAbsolutePath().matches("%proc_macros%") and
expansion = c.getMacroCallExpansion()
}
query predicate unexpanded_macro_calls(MacroCall c) {
c.fromSource() and not c.hasMacroCallExpansion()
}

View File

@@ -1,2 +0,0 @@
def test_macro_expansion(codeql, rust, check_source_archive, rust_check_diagnostics):
codeql.database.create()