Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: macros checks of attribute macros usages #252

Merged
merged 13 commits into from
May 20, 2024
5 changes: 5 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[target.x86_64-pc-windows-msvc]
rustflags = [
"-Clink-arg=/force:unresolved",
"-Ctarget-feature=+crt-static",
]
2 changes: 2 additions & 0 deletions macros/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
//! Implemntation of the procedural macros exposed via the `sails-macros` crate.

pub use program::gprogram;
pub use program::gprogram_safe;
pub use route::groute;
pub use service::gservice;
pub use service::gservice_safe;

mod program;
mod route;
Expand Down
31 changes: 27 additions & 4 deletions macros/core/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,42 @@ use proc_macro_error::abort;
use quote::quote;
use std::collections::BTreeMap;
use syn::{
parse_quote, Ident, ImplItem, ImplItemFn, ItemImpl, Receiver, ReturnType, Type, TypePath,
Visibility,
parse_quote, spanned::Spanned, Ident, ImplItem, ImplItemFn, ItemImpl, Receiver, ReturnType,
Type, TypePath, Visibility,
};

pub fn gprogram_safe(program_impl_tokens: TokenStream2) -> TokenStream2 {
let program_impl = parse_program_impl(program_impl_tokens);
check_program_single(&program_impl);
gen_gprogram_impl(program_impl)
}

pub fn gprogram(program_impl_tokens: TokenStream2) -> TokenStream2 {
DennisInSky marked this conversation as resolved.
Show resolved Hide resolved
let program_impl = syn::parse2(program_impl_tokens).unwrap_or_else(|err| {
let program_impl = parse_program_impl(program_impl_tokens);
gen_gprogram_impl(program_impl)
}

fn parse_program_impl(program_impl_tokens: TokenStream2) -> ItemImpl {
DennisInSky marked this conversation as resolved.
Show resolved Hide resolved
syn::parse2(program_impl_tokens).unwrap_or_else(|err| {
abort!(
err.span(),
"`gprogram` attribute can be applied to impls only: {}",
err
)
});
})
}

fn check_program_single(program_impl: &ItemImpl) {
DennisInSky marked this conversation as resolved.
Show resolved Hide resolved
if unsafe { shared::PROGRAM_SPAN }.is_some() {
abort!(
program_impl.span(),
vobradovich marked this conversation as resolved.
Show resolved Hide resolved
"multiple `gprogram` attributes are not allowed"
)
}
unsafe { shared::PROGRAM_SPAN = Some(program_impl.span()) };
}

fn gen_gprogram_impl(program_impl: ItemImpl) -> TokenStream2 {
let services_ctors = discover_services_ctors(&program_impl);

let mut program_impl = program_impl.clone();
Expand Down
33 changes: 29 additions & 4 deletions macros/core/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,44 @@ use proc_macro_error::abort;
use quote::quote;
use std::collections::BTreeMap;
use syn::{
GenericArgument, Ident, ImplItemFn, ItemImpl, Path, PathArguments, Type, TypeParamBound,
Visibility, WhereClause, WherePredicate,
spanned::Spanned, GenericArgument, Ident, ImplItemFn, ItemImpl, Path, PathArguments, Type,
TypeParamBound, Visibility, WhereClause, WherePredicate,
};

pub fn gservice_safe(service_impl_tokens: TokenStream2) -> TokenStream2 {
DennisInSky marked this conversation as resolved.
Show resolved Hide resolved
let service_impl = parse_service_impl(service_impl_tokens);
check_service_single_by_name(&service_impl);
gen_gservice_impl(service_impl)
}

pub fn gservice(service_impl_tokens: TokenStream2) -> TokenStream2 {
let service_impl = syn::parse2(service_impl_tokens).unwrap_or_else(|err| {
let service_impl = parse_service_impl(service_impl_tokens);
gen_gservice_impl(service_impl)
}

fn parse_service_impl(service_impl_tokens: TokenStream2) -> ItemImpl {
DennisInSky marked this conversation as resolved.
Show resolved Hide resolved
syn::parse2(service_impl_tokens).unwrap_or_else(|err| {
abort!(
err.span(),
"`gservice` attribute can be applied to impls only: {}",
err
)
});
})
}

fn check_service_single_by_name(service_impl: &ItemImpl) {
DennisInSky marked this conversation as resolved.
Show resolved Hide resolved
let path = shared::impl_type_path(service_impl);
let type_ident = path.path.segments.last().unwrap().ident.to_string();
if unsafe { shared::SERVICE_TYPES.get(&type_ident) }.is_some() {
abort!(
service_impl.span(),
"multiple `gservice` attributes are not allowed"
)
}
unsafe { shared::SERVICE_TYPES.insert(type_ident, service_impl.span()) };
}

pub fn gen_gservice_impl(service_impl: ItemImpl) -> TokenStream2 {
DennisInSky marked this conversation as resolved.
Show resolved Hide resolved
let (service_type_path, service_type_args, service_type_constraints) = {
let service_type = ImplType::new(&service_impl);
(
Expand Down
33 changes: 20 additions & 13 deletions macros/core/src/shared.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::route;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro_error::abort;
use quote::{quote, ToTokens};
use std::collections::BTreeMap;
Expand All @@ -8,6 +8,11 @@ use syn::{
ReturnType, Signature, Type, TypePath, TypeTuple, WhereClause,
};

pub(crate) static mut SERVICE_TYPES: BTreeMap<String, Span> = BTreeMap::new();
DennisInSky marked this conversation as resolved.
Show resolved Hide resolved

/// Static Span of Program `impl` block
pub(crate) static mut PROGRAM_SPAN: Option<Span> = None;
DennisInSky marked this conversation as resolved.
Show resolved Hide resolved

/// A struct that represents the type of an `impl` block.
pub(crate) struct ImplType<'a> {
path: &'a TypePath,
Expand All @@ -17,18 +22,7 @@ pub(crate) struct ImplType<'a> {

impl<'a> ImplType<'a> {
pub(crate) fn new(item_impl: &'a ItemImpl) -> Self {
let path = {
let item_impl_type = item_impl.self_ty.as_ref();
if let Type::Path(type_path) = item_impl_type {
type_path
} else {
abort!(
item_impl_type.span(),
"failed to parse impl type: {}",
item_impl_type.to_token_stream()
)
}
};
let path = impl_type_path(item_impl);
let args = &path.path.segments.last().unwrap().arguments;
let constraints = item_impl.generics.where_clause.as_ref();
Self {
Expand All @@ -51,6 +45,19 @@ impl<'a> ImplType<'a> {
}
}

pub(crate) fn impl_type_path(item_impl: &ItemImpl) -> &TypePath {
let item_impl_type = item_impl.self_ty.as_ref();
if let Type::Path(type_path) = item_impl_type {
type_path
} else {
abort!(
item_impl_type.span(),
vobradovich marked this conversation as resolved.
Show resolved Hide resolved
"failed to parse impl type: {}",
item_impl_type.to_token_stream()
)
}
}

/// Represents parts of a handler function.
#[derive(Clone)]
pub(crate) struct Func<'a> {
Expand Down
4 changes: 2 additions & 2 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ use proc_macro_error::proc_macro_error;
#[proc_macro_error]
#[proc_macro_attribute]
pub fn gservice(_attrs: TokenStream, impl_tokens: TokenStream) -> TokenStream {
sails_macros_core::gservice(impl_tokens.into()).into()
sails_macros_core::gservice_safe(impl_tokens.into()).into()
}

#[proc_macro_error]
#[proc_macro_attribute]
pub fn gprogram(_attrs: TokenStream, impl_tokens: TokenStream) -> TokenStream {
sails_macros_core::gprogram(impl_tokens.into()).into()
sails_macros_core::gprogram_safe(impl_tokens.into()).into()
}

#[proc_macro_error]
Expand Down
20 changes: 20 additions & 0 deletions macros/tests/ui/gprogram_fails_multiple_not_allowed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use sails_macros::gprogram;

struct MyProgram;

#[gprogram]
impl MyProgram {
pub fn new() -> Self {
Self
}
}

#[gprogram]
impl MyProgram {
pub fn default() -> Self {
Self
}
}

#[tokio::main]
async fn main() {}
5 changes: 5 additions & 0 deletions macros/tests/ui/gprogram_fails_multiple_not_allowed.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: multiple `gprogram` attributes are not allowed
--> tests/ui/gprogram_fails_multiple_not_allowed.rs:13:1
|
13 | impl MyProgram {
| ^^^^
14 changes: 14 additions & 0 deletions macros/tests/ui/gprogram_fails_multiple_not_allowed_on_one_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use sails_macros::gprogram;

struct MyProgram;

#[gprogram]
#[gprogram]
impl MyProgram {
pub fn new() -> Self {
Self
}
}

#[tokio::main]
async fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: multiple `gprogram` attributes are not allowed
--> tests/ui/gprogram_fails_multiple_not_allowed_on_one_impl.rs:7:1
|
7 | impl MyProgram {
| ^^^^
20 changes: 20 additions & 0 deletions macros/tests/ui/gservice_fails_multiple_not_allowed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use sails_macros::gservice;

struct MyService;

#[gservice]
impl MyService {
pub fn this(&self, p1: bool) -> bool {
!p1
}
}

#[gservice]
impl MyService {
pub fn this(&self, p1: bool) -> bool {
!p1
}
}

#[tokio::main]
async fn main() {}
5 changes: 5 additions & 0 deletions macros/tests/ui/gservice_fails_multiple_not_allowed.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: multiple `gservice` attributes are not allowed
DennisInSky marked this conversation as resolved.
Show resolved Hide resolved
--> tests/ui/gservice_fails_multiple_not_allowed.rs:13:1
|
13 | impl MyService {
| ^^^^
14 changes: 14 additions & 0 deletions macros/tests/ui/gservice_fails_multiple_not_allowed_on_one_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use sails_macros::gservice;

struct MyService;

#[gservice]
#[gservice]
impl MyService {
pub fn this(&self, p1: bool) -> bool {
!p1
}
}

#[tokio::main]
async fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: multiple `gservice` attributes are not allowed
--> tests/ui/gservice_fails_multiple_not_allowed_on_one_impl.rs:7:1
|
7 | impl MyService {
| ^^^^

error[E0599]: no method named `this` found for struct `MyService` in the current scope
DennisInSky marked this conversation as resolved.
Show resolved Hide resolved
--> tests/ui/gservice_fails_multiple_not_allowed_on_one_impl.rs:8:12
|
3 | struct MyService;
| ---------------- method `this` not found for this struct
...
8 | pub fn this(&self, p1: bool) -> bool {
| ^^^^ method not found in `MyService`