diff --git a/crates/luaify/src/generate.rs b/crates/luaify/src/generate.rs index bdf14c2..da0c3ae 100644 --- a/crates/luaify/src/generate.rs +++ b/crates/luaify/src/generate.rs @@ -916,7 +916,7 @@ fn generate_pat_typed(f: &mut Formatter, typed: &PatType, cx: PatContext) -> Res assert_no_attrs!(typed); match *typed.ty { Type::Infer(_) => generate_pat(f, &typed.pat, cx), - ref ty => syn_error!(ty, "cannot have type"), + ref ty => syn_error!(ty, "cannot specify type"), } } diff --git a/crates/luaify/src/transform.rs b/crates/luaify/src/transform.rs index 8c6047d..3acd10e 100644 --- a/crates/luaify/src/transform.rs +++ b/crates/luaify/src/transform.rs @@ -1,6 +1,4 @@ -use crate::utils::{LuaType, expr_ident, pat_ident, syn_error, wrap_expr_block}; -use quote::format_ident; -use std::mem; +use crate::utils::syn_error; use syn::{spanned::*, visit_mut::*, *}; pub fn transform(expr: &mut Expr) -> Result<()> { @@ -27,27 +25,6 @@ impl Visitor { } impl VisitMut for Visitor { - fn visit_expr_closure_mut(&mut self, clo: &mut ExprClosure) { - match self.transform_expr_closure(clo) { - res @ Err(_) => self.result = res, - _ => visit_expr_closure_mut(self, clo), - } - } - - fn visit_item_fn_mut(&mut self, func: &mut ItemFn) { - match self.transform_function(func) { - res @ Err(_) => self.result = res, - _ => visit_item_fn_mut(self, func), - } - } - - fn visit_expr_mut(&mut self, expr: &mut Expr) { - match self.transform_expr(expr) { - res @ Err(_) => self.result = res, - _ => visit_expr_mut(self, expr), - } - } - fn visit_expr_unary_mut(&mut self, un: &mut ExprUnary) { match self.transform_unary(un) { res @ Err(_) => self.result = res, @@ -64,147 +41,9 @@ impl VisitMut for Visitor { } impl Visitor { - fn transform_expr_closure(&mut self, clo: &mut ExprClosure) -> Result<()> { - // - // transforms a closure expression with input type annotations by removing the annotations - // and inserting `as` casts at the start. - // - // before: - // |a: string, b: number| { ... } - // after: - // |a, b| { a as string; b as number; ... } - // - let mut checks: Vec = vec![]; - for input in clo.inputs.iter_mut() { - match input { - Pat::Ident(_) => {} - Pat::Type(typed) => { - let ident = pat_ident(&typed.pat)?; - let ty = mem::replace(&mut typed.ty, parse_quote!(_)); - match (&*ty).try_into()? { - LuaType::Any => {} - _ => checks.push(parse_quote! { #ident as #ty; }), - } - } - _ => {} - } - } - - if !checks.is_empty() { - let mut body = wrap_expr_block(&clo.body); - body.stmts.splice(..0, checks); - clo.body = Box::new(parse_quote! { #body }); - } - - Ok(()) - } - - fn transform_function(&mut self, func: &mut ItemFn) -> Result<()> { - // - // transforms a function item with input type annotations by removing the annotations - // and inserting `as` casts at the start. - // - // before: - // fn my_func(self: table, a: string) { ... } - // after: - // fn my_func(self: _, a: _) { self as table; a as string; ... } - // - let mut checks: Vec = vec![]; - for input in func.sig.inputs.iter_mut() { - if let Some((ident, ty)) = match input { - FnArg::Receiver(recv) if recv.colon_token.is_some() => { - let ty = mem::replace(&mut recv.ty, parse_quote!(_)); - recv.colon_token = None; - Some((Ident::new("self", recv.self_token.span()), ty)) - } - FnArg::Typed(typed) => { - let ident = pat_ident(&typed.pat)?; - let ty = mem::replace(&mut typed.ty, parse_quote!(_)); - Some((ident, ty)) - } - _ => None, - } { - match (&*ty).try_into()? { - LuaType::Any => {} - _ => checks.push(parse_quote! { #ident as #ty; }), - } - }; - } - - func.block.stmts.splice(..0, checks); - Ok(()) - } - - fn transform_expr(&mut self, expr: &mut Expr) -> Result<()> { - self.transform_expr_cast(expr)?; - Ok(()) - } - - fn transform_expr_cast(&mut self, expr: &mut Expr) -> Result<()> { - // - // transforms an `as` cast expression into a block expression containing a runtime - // lua type check. - // - // before: - // var as string - // after: - // { if type(var) != "string" { error(...) } } - // - if let Expr::Cast(cast) = expr { - let arg = (*cast.expr).clone(); - let mut prelude: Option = None; - let ty: LuaType = (&*cast.ty).try_into()?; - let ty_str = format!("{ty}"); - let (ident, msg) = match expr_ident(&arg) { - Ok(ident) => (ident.clone(), format!("{ty} expected in '{ident}', got ")), - Err(_) => { - let ident = Ident::new("_", arg.span()); - prelude = Some(parse_quote! { let #ident = #arg; }); - (ident, format!("{ty} expected, got ")) - } - }; - - let tmp = format_ident!("__{ident}"); - let span = cast.span(); - *expr = match ty { - LuaType::Any => parse_quote_spanned!(span => {}), - LuaType::Nil => parse_quote_spanned!(span => { - #prelude - assert(#ident == (), concat!(#msg, r#type(#ident))); - }), - LuaType::Number => parse_quote_spanned!(span => { - #prelude - let #tmp = #ident; - #ident = tonumber(#ident); - assert(#ident != (), concat!(#msg, r#type(#tmp))); - }), - LuaType::Integer => parse_quote_spanned!(span => { - #prelude - let #tmp = #ident; - #ident = tonumber(#ident); - assert(#ident != () && math::floor(#ident) == #ident, concat!(#msg, r#type(#tmp))); - }), - LuaType::String => parse_quote_spanned!(span => { - #prelude - if r#type(#ident) == "number" { - #ident = tostring(#ident); - } else { - assert(r#type(#ident) == "string", concat!(#msg, r#type(#ident))); - } - }), - _ => parse_quote_spanned!(span => { - #prelude - assert(r#type(#ident) == #ty_str, concat!(#msg, r#type(#ident))); - }), - } - } - - Ok(()) - } - fn transform_unary(&mut self, un: &mut ExprUnary) -> Result<()> { // - // separates a nested negation unary operator with parentheses, because double hyphen + // separates a nested negation unary operator with parentheses, because the double hyphen // `--` indicates a comment in lua. // // before: @@ -216,7 +55,7 @@ impl Visitor { && let Expr::Unary(ref inner) = *un.expr && let UnOp::Neg(_) = inner.op { - un.expr = Box::new(parse_quote!((#inner))); + un.expr = Box::new(parse_quote_spanned!(inner.span() => (#inner))); } Ok(()) diff --git a/crates/luaify/src/utils.rs b/crates/luaify/src/utils.rs index d452d4d..3cf6b68 100644 --- a/crates/luaify/src/utils.rs +++ b/crates/luaify/src/utils.rs @@ -1,5 +1,4 @@ -use std::fmt; -use syn::{ext::*, spanned::*, *}; +use syn::*; macro_rules! syn_error { ($src:expr, $($fmt:expr),+) => {{ @@ -24,90 +23,3 @@ pub fn wrap_expr_block(expr: &Expr) -> Block { expr => parse_quote!({ #expr }), } } - -pub fn expr_ident(expr: &Expr) -> Result<&Ident> { - match expr { - Expr::Path(path) => path.path.require_ident(), - _ => syn_error!(expr, "expected ident"), - } -} - -pub fn pat_ident(pat: &Pat) -> Result { - Ok(match pat { - Pat::Ident(ident) => match ident.subpat { - Some((_, ref subpat)) => syn_error!(subpat, "unexpected subpattern"), - None => ident.ident.clone(), - }, - Pat::Wild(wild) => Ident::new("_", wild.span()), - _ => syn_error!(pat, "expected ident"), - }) -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum LuaType { - Any, - Nil, - Boolean, - Lightuserdata, - Number, - Integer, - String, - Table, - Function, - Userdata, - Thread, - Cdata, -} - -impl fmt::Display for LuaType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - LuaType::Any => write!(f, "any"), - LuaType::Nil => write!(f, "nil"), - LuaType::Boolean => write!(f, "boolean"), - LuaType::Lightuserdata => write!(f, "lightuserdata"), - LuaType::Number => write!(f, "number"), - LuaType::Integer => write!(f, "integer"), - LuaType::String => write!(f, "string"), - LuaType::Table => write!(f, "table"), - LuaType::Function => write!(f, "function"), - LuaType::Userdata => write!(f, "userdata"), - LuaType::Thread => write!(f, "thread"), - LuaType::Cdata => write!(f, "cdata"), - } - } -} - -impl TryFrom<&Ident> for LuaType { - type Error = Error; - - fn try_from(value: &Ident) -> Result { - Ok(match format!("{}", value.unraw()).as_str() { - "any" => Self::Any, - "nil" => Self::Nil, - "boolean" => Self::Boolean, - "lightuserdata" => Self::Lightuserdata, - "number" => Self::Number, - "integer" => Self::Integer, - "string" => Self::String, - "table" => Self::Table, - "function" => Self::Function, - "userdata" => Self::Userdata, - "thread" => Self::Thread, - "cdata" => Self::Cdata, - _ => syn_error!(value, "invalid lua type"), - }) - } -} - -impl TryFrom<&Type> for LuaType { - type Error = Error; - - fn try_from(value: &Type) -> Result { - match value { - Type::Infer(_) => Ok(Self::Any), - Type::Path(path) if path.qself.is_none() => path.path.require_ident()?.try_into(), - _ => syn_error!(value, "invalid lua type"), - } - } -} diff --git a/crates/luaify/tests/test.rs b/crates/luaify/tests/test.rs index 78e77ac..70836c0 100644 --- a/crates/luaify/tests/test.rs +++ b/crates/luaify/tests/test.rs @@ -77,13 +77,6 @@ fn local_fn() { }), r#"function(a,b)local function inner(c,d)end;return inner;end"# ); - assert_eq!( - luaify!(|| { - fn check(self: string, arg: number) {} - inner - }), - r#"function()local function check(self,arg)do if type(self)=="number"then self=tostring(self);else assert(type(self)=="string","string expected in \'self\', got "..type(self));end;end;do local __arg=arg;arg=tonumber(arg);assert(arg~=nil,"number expected in \'arg\', got "..type(__arg));end;end;return inner;end"# - ); } #[test] @@ -208,37 +201,6 @@ fn loops() { ); } -#[test] -fn type_checks() { - assert_eq!(luaify!(|s| {}), r#"function(s)end"#); - assert_eq!( - luaify!(|s: table| {}), - r#"function(s)do assert(type(s)=="table","table expected in \'s\', got "..type(s));end;end"# - ); - assert_eq!( - luaify!(|s| { s as string }), - r#"function(s)do if type(s)=="number"then s=tostring(s);else assert(type(s)=="string","string expected in \'s\', got "..type(s));end;end;end"# - ); - assert_eq!( - luaify!(|s| { s as number }), - r#"function(s)do local __s=s;s=tonumber(s);assert(s~=nil,"number expected in \'s\', got "..type(__s));end;end"# - ); - assert_eq!( - luaify!(|s| { s as nil }), - r#"function(s)do assert(s==nil,"nil expected in \'s\', got "..type(s));end;end"# - ); - assert_eq!(luaify!(|s| { s as any }), r#"function(s)do end;end"#); - - assert_eq!( - luaify!(|s| { - let (ok, res) = coroutine::r#yield(thread); - ok as boolean; - res as nil; - }), - r#"function(s)local ok,res=coroutine.yield(thread);do assert(type(ok)=="boolean","boolean expected in \'ok\', got "..type(ok));end;do assert(res==nil,"nil expected in \'res\', got "..type(res));end;end"# - ); -} - #[test] fn concat() { assert_eq!(luaify!(concat!(a)), r#"a"#);