Compare commits

...

6 Commits

7 changed files with 58 additions and 75 deletions

View File

@@ -6,7 +6,7 @@ use convert_case::{Case, Casing};
use itertools::Itertools;
use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, quote};
use syn::{DataEnum, DataStruct, DeriveInput, LitStr, Token, parse_macro_input, spanned::Spanned};
use syn::{DataEnum, DataStruct, DeriveInput, LitStr, parse_macro_input, spanned::Spanned};
#[derive(Debug, Clone)]
struct StructField {
@@ -32,9 +32,9 @@ impl ToTokens for StructField {
<#ty as Typed>::generate_members().unwrap_or("".to_string())
});
} else {
let optional = if self.optional { "?" } else { "" };
let inner = if self.optional { "({})?" } else { "{}" };
let format = format!("---@field {} {{}}{}\n", name, optional);
let format = format!("---@field {} {inner}\n", name);
tokens.extend(quote! {
format!(#format, <#ty as Typed>::type_name())
@@ -287,40 +287,20 @@ fn parse_fields(
let mut as_name = LitStr::new(&name.to_string(), name.span());
for attr in &field.attrs {
if attr.path().is_ident("serde")
&& let Err(err) = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("default") {
default = true;
if meta.input.peek(Token![=]) {
meta.input.parse::<Token![=]>()?;
meta.input.parse::<LitStr>()?;
}
} else if meta.path.is_ident("flatten") {
flatten = true;
} else {
// Parse away any additional token that we don't care about
if meta.input.peek(Token![=]) {
meta.input.parse::<Token![=]>()?;
meta.input.parse::<LitStr>()?;
}
}
Ok(())
})
{
return Err(err);
}
if attr.path().is_ident("typed")
&& let Err(err) = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("as") {
let value = meta.value()?;
as_name = value.parse()?;
Ok(())
} else if meta.path.is_ident("default") {
default = true;
} else if meta.path.is_ident("flatten") {
flatten = true;
} else {
Err(syn::Error::new(meta.path.span(), "Unknown attribute"))
return Err(syn::Error::new(meta.path.span(), "Unknown attribute"));
}
Ok(())
})
{
return Err(err);
@@ -339,7 +319,7 @@ fn parse_fields(
Ok(fields)
}
#[proc_macro_derive(Typed, attributes(serde, typed))]
#[proc_macro_derive(Typed, attributes(typed))]
pub fn typed(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
@@ -353,12 +333,17 @@ fn typed_inner(ast: DeriveInput) -> syn::Result<TokenStream2> {
let name = ast.ident;
let mut as_name = LitStr::new(&name.to_string(), name.span());
let generics = &ast.generics;
let mut case = None;
let mut tag = None;
for attr in &ast.attrs {
if attr.path().is_ident("serde")
if attr.path().is_ident("typed")
&& let Err(err) = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("rename_all") {
if meta.path.is_ident("as") {
let value = meta.value()?;
as_name = value.parse()?;
} else if meta.path.is_ident("rename_all") {
let value = meta.value()?;
let case_name: LitStr = value.parse()?;
@@ -376,11 +361,11 @@ fn typed_inner(ast: DeriveInput) -> syn::Result<TokenStream2> {
"Typed does not support this type of rename",
)),
}?);
}
if meta.path.is_ident("tag") {
meta.input.parse::<Token![=]>()?;
tag = Some(meta.input.parse::<LitStr>()?);
} else if meta.path.is_ident("tag") {
let value = meta.value()?;
tag = Some(value.parse::<LitStr>()?);
} else {
return Err(syn::Error::new(meta.path.span(), "Unknown attribute"));
}
Ok(())
@@ -388,21 +373,6 @@ fn typed_inner(ast: DeriveInput) -> syn::Result<TokenStream2> {
{
return Err(err);
}
if attr.path().is_ident("typed")
&& let Err(err) = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("as") {
let value = meta.value()?;
as_name = value.parse()?;
Ok(())
} else {
Err(syn::Error::new(meta.path.span(), "Unknown attribute"))
}
})
{
return Err(err);
}
}
if as_name.value().is_empty() {
@@ -412,13 +382,23 @@ fn typed_inner(ast: DeriveInput) -> syn::Result<TokenStream2> {
));
}
let generics_name: TokenStream2 = generics
.type_params()
.map(|ty| {
let ident = &ty.ident;
quote! {
+ &#ident::type_name()
}
})
.collect();
let type_name_fn = quote! {
fn type_name() -> ::std::string::String {
#as_name.to_string()
#as_name.to_string() #generics_name
}
};
let test: TokenStream2 = match ast.data {
let definitions: TokenStream2 = match ast.data {
syn::Data::Struct(data_struct) => Struct::from_data(data_struct, case)?.into_token_stream(),
syn::Data::Enum(data_enum) if let Some(tag) = tag => {
ExtTaggedEnum::from_data(data_enum, tag, case)?.into_token_stream()
@@ -432,11 +412,13 @@ fn typed_inner(ast: DeriveInput) -> syn::Result<TokenStream2> {
}
};
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
Ok(quote! {
impl ::lua_typed::Typed for #name {
impl #impl_generics ::lua_typed::Typed for #name #ty_generics #where_clause {
#type_name_fn
#test
#definitions
}
})
}

View File

@@ -156,16 +156,17 @@ impl Typed for MacAddress {
}
impl<A: Typed, B: Typed> !Typed for (A, B) {}
impl !Typed for () {}
impl<T: Typed> Typed for Option<T> {
fn type_name() -> String {
format!("{}?", <T as Typed>::type_name())
format!("({})?", <T as Typed>::type_name())
}
}
impl<T: Typed> Typed for Vec<T> {
fn type_name() -> String {
format!("{}[]", <T as Typed>::type_name())
format!("({})[]", <T as Typed>::type_name())
}
}

View File

@@ -8,7 +8,7 @@ pub struct B {
#[derive(Typed)]
pub struct A {
#[serde(flatten)]
#[typed(flatten)]
pub b: B,
pub cool: u32,
}

View File

@@ -3,7 +3,7 @@ use std::collections::HashMap;
use lua_typed::Typed;
#[derive(Typed)]
#[serde(rename_all = "snake_case", tag = "action")]
#[typed(rename_all = "snake_case", tag = "action")]
pub enum ActionType {
Broadcast { extras: HashMap<String, String> },
// View,
@@ -16,14 +16,14 @@ fn action_type() {
---@class ActionType
---@field action
---| "broadcast"
---@field extras table<string, string>?
---@field extras (table<string, string>)?
local ActionType
"#);
}
#[derive(Typed)]
#[repr(u8)]
#[serde(rename_all = "snake_case")]
#[typed(rename_all = "snake_case")]
pub enum Priority {
Min = 1,
Low,
@@ -46,7 +46,7 @@ fn priority() {
#[derive(Typed)]
pub struct Action {
#[serde(flatten)]
#[typed(flatten)]
pub action: ActionType,
pub label: String,
pub clear: Option<bool>,
@@ -58,9 +58,9 @@ fn action() {
---@class Action
---@field action
---| "broadcast"
---@field extras table<string, string>?
---@field extras (table<string, string>)?
---@field label string
---@field clear boolean?
---@field clear (boolean)?
local Action
"#);
}
@@ -69,10 +69,10 @@ fn action() {
pub struct Notification {
pub title: String,
pub message: Option<String>,
#[serde(default)]
#[typed(default)]
pub tags: Vec<String>,
pub priority: Option<Priority>,
#[serde(default)]
#[typed(default)]
pub actions: Vec<Action>,
}
@@ -81,10 +81,10 @@ fn notification() {
insta::assert_snapshot!(<Notification as Typed>::generate_full().unwrap(), @r"
---@class Notification
---@field title string
---@field message string?
---@field tags string[]?
---@field priority Priority?
---@field actions Action[]?
---@field message (string)?
---@field tags ((string)[])?
---@field priority (Priority)?
---@field actions ((Action)[])?
local Notification
");
}

View File

@@ -1,7 +1,7 @@
use lua_typed::Typed;
#[derive(Typed)]
#[serde(rename_all = "invalid/case")]
#[typed(rename_all = "invalid/case")]
pub enum Test {
HelloWorld,
}

View File

@@ -1,5 +1,5 @@
error: Typed does not support this type of rename
--> tests/ui/invalid_rename.rs:4:22
|
4 | #[serde(rename_all = "invalid/case")]
4 | #[typed(rename_all = "invalid/case")]
| ^^^^^^^^^^^^^^

View File

@@ -1,7 +1,7 @@
use lua_typed::Typed;
#[derive(Typed)]
#[serde(tag = "tag")]
#[typed(tag = "tag")]
pub enum Test {
A,
B(String),