Compare commits
8 Commits
21bc70cbcd
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
3d29c9dd14
|
|||
|
08f5c4533a
|
|||
|
d5d6fc1638
|
|||
|
d30a01aada
|
|||
|
513b36afdb
|
|||
|
396938a089
|
|||
|
d2de4b5830
|
|||
|
fe958b863a
|
49
Cargo.lock
generated
49
Cargo.lock
generated
@@ -2,6 +2,15 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.11"
|
||||
@@ -47,6 +56,16 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "eui48"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "887418ac5e8d57c2e66e04bdc2fe15f9a5407be20b54a82c86bd0e368b709701"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
@@ -105,6 +124,7 @@ checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
name = "lua_typed"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"eui48",
|
||||
"insta",
|
||||
"lua_typed_macro",
|
||||
"trybuild",
|
||||
@@ -151,6 +171,35 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
|
||||
@@ -7,6 +7,7 @@ edition = "2024"
|
||||
members = ["lua_typed_macro"]
|
||||
|
||||
[dependencies]
|
||||
eui48 = { version = "1.1.0", features = ["serde"], default-features = false }
|
||||
lua_typed_macro = { path = "./lua_typed_macro/" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -6,11 +6,11 @@ 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 {
|
||||
name: syn::Ident,
|
||||
name: LitStr,
|
||||
ty: syn::Type,
|
||||
case: Option<Case<'static>>,
|
||||
optional: bool,
|
||||
@@ -19,7 +19,7 @@ struct StructField {
|
||||
|
||||
impl ToTokens for StructField {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let mut name = self.name.to_string();
|
||||
let mut name = self.name.value();
|
||||
|
||||
if let Some(case) = self.case {
|
||||
name = name.to_case(case);
|
||||
@@ -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())
|
||||
@@ -283,23 +283,21 @@ fn parse_fields(
|
||||
for field in input {
|
||||
let mut default = false;
|
||||
let mut flatten = false;
|
||||
let name = field.ident.expect("We already checked that ident is some");
|
||||
let mut as_name = LitStr::new(&name.to_string(), name.span());
|
||||
|
||||
for attr in &field.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("default") {
|
||||
if meta.path.is_ident("as") {
|
||||
let value = meta.value()?;
|
||||
as_name = value.parse()?;
|
||||
} else 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>()?;
|
||||
}
|
||||
return Err(syn::Error::new(meta.path.span(), "Unknown attribute"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -310,7 +308,7 @@ fn parse_fields(
|
||||
}
|
||||
|
||||
fields.push(StructField {
|
||||
name: field.ident.expect("We already checked that ident is some"),
|
||||
name: as_name,
|
||||
ty: field.ty,
|
||||
case,
|
||||
optional: default,
|
||||
@@ -321,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);
|
||||
|
||||
@@ -335,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()?;
|
||||
|
||||
@@ -358,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(())
|
||||
@@ -370,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() {
|
||||
@@ -394,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()
|
||||
@@ -414,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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
32
src/lib.rs
32
src/lib.rs
@@ -1,5 +1,10 @@
|
||||
#![feature(negative_impls)]
|
||||
use eui48::MacAddress;
|
||||
pub use lua_typed_macro::Typed;
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{Ipv4Addr, SocketAddr},
|
||||
};
|
||||
|
||||
pub trait Typed {
|
||||
fn type_name() -> String;
|
||||
@@ -132,15 +137,36 @@ impl Typed for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl Typed for SocketAddr {
|
||||
fn type_name() -> String {
|
||||
"string".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Typed for Ipv4Addr {
|
||||
fn type_name() -> String {
|
||||
"string".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Typed for MacAddress {
|
||||
fn type_name() -> String {
|
||||
"string".into()
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ pub struct B {
|
||||
|
||||
#[derive(Typed)]
|
||||
pub struct A {
|
||||
#[serde(flatten)]
|
||||
#[typed(flatten)]
|
||||
pub b: B,
|
||||
pub cool: u32,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use lua_typed::Typed;
|
||||
|
||||
#[derive(Typed)]
|
||||
#[serde(rename_all = "invalid/case")]
|
||||
#[typed(rename_all = "invalid/case")]
|
||||
pub enum Test {
|
||||
HelloWorld,
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use lua_typed::Typed;
|
||||
|
||||
#[derive(Typed)]
|
||||
#[serde(tag = "tag")]
|
||||
#[typed(tag = "tag")]
|
||||
pub enum Test {
|
||||
A,
|
||||
B(String),
|
||||
|
||||
Reference in New Issue
Block a user