diff --git a/src/lexer.rs b/src/lexer.rs index 72cc410..6d22e1c 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -1,162 +1,25 @@ +mod token; +pub use token::*; + use std::ops::Range; use chumsky::{ prelude::{any, choice, end, filter, just, take_until, Simple}, - text::{ident, keyword, newline, TextParser}, + recursive::recursive, + text::{ident, keyword, newline, whitespace, TextParser}, Parser, }; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Kind { - Dot, - Colon, - Local, -} - -impl Kind { - pub fn as_char(&self) -> char { - match self { - Self::Dot => '.', - Self::Colon => ':', - Self::Local => '#', - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Scope { - Public, - Private, - Protected, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum TagType { - /// ```lua - /// ---@toc - /// ``` - Toc(String), - /// ```lua - /// ---@mod [desc] - /// ``` - Module(String, Option), - /// ```lua - /// ---@divider - /// ``` - Divider(char), - /// ```lua - /// function one.two() end - /// one.two = function() end - /// ``` - Func { - prefix: Option, - name: String, - kind: Kind, - }, - /// ```lua - /// one = 1 - /// one.two = 12 - /// ``` - Expr { - prefix: Option, - name: String, - kind: Kind, - }, - /// ```lua - /// ---@export - /// or - /// return \eof - /// ``` - Export(String), - /// ```lua - /// ---@brief [[ - /// ``` - BriefStart, - /// ```lua - /// ---@brief ]] - /// ``` - BriefEnd, - /// ```lua - /// ---@param [description] - /// ``` - Param { - name: String, - ty: String, - desc: Option, - }, - /// ```lua - /// ---@return [ [comment] | [name] #] - /// ``` - Return { - ty: String, - name: Option, - desc: Option, - }, - /// ```lua - /// ---@class - /// ``` - Class(String), - /// ```lua - /// ---@field [public|private|protected] [description] - /// ``` - Field { - scope: Scope, - name: String, - ty: String, - desc: Option, - }, - /// ```lua - /// -- Simple Alias - /// ---@alias - /// - /// -- Enum alias - /// ---@alias - /// ``` - Alias(String, Option), - /// ```lua - /// ---| '' [# description] - /// ``` - Variant(String, Option), - /// ```lua - /// ---@type [desc] - /// ``` - Type(String, Option), - /// ```lua - /// ---@tag - /// ``` - Tag(String), - /// ```lua - /// ---@see - /// ``` - See(String), - /// ```lua - /// ---@usage `` - /// ``` - Usage(String), - /// ```lua - /// ---@usage [[ - /// ``` - UsageStart, - /// ```lua - /// ---@usage ]] - /// ``` - UsageEnd, - /// ```lua - /// ---TEXT - /// ``` - Comment(String), - /// Text nodes which are not needed - Skip, -} - type Spanned = (TagType, Range); +const C: [char; 3] = ['.', '_', '-']; + #[derive(Debug)] pub struct Lexer; impl Lexer { /// Parse emmylua/lua files into rust token - pub fn parse(src: &str) -> Result, Vec>> { + pub fn init() -> impl Parser, Error = Simple> { let triple = just("---"); let space = just(' ').repeated().at_least(1); let till_eol = take_until(newline()); @@ -164,17 +27,6 @@ impl Lexer { let comment = till_eol.map(|(x, _)| x.iter().collect()); let desc = space.ignore_then(comment).or_not(); - // Source: https://github.com/sumneko/lua-language-server/wiki/Annotations#documenting-types - // A TYPE could be - // - primary = string|number|boolean - // - fn = func(...):string - // - enum = "one"|"two"|"three" - // - or: primary (| primary)+ - // - optional = primary? - // - table = table - // - array = primary[] - let ty = filter(|x: &char| !x.is_whitespace()).repeated().collect(); - let scope = choice(( keyword("public").to(Scope::Public), keyword("protected").to(Scope::Protected), @@ -196,14 +48,14 @@ impl Lexer { ))) .ignored(); + let union_literal = just('\'') + .ignore_then(filter(|c| c != &'\'').repeated()) + .then_ignore(just('\'')) + .collect(); + let variant = just('|') .then_ignore(space) - .ignore_then( - just('\'') - .ignore_then(filter(|c| c != &'\'').repeated()) - .then_ignore(just('\'')) - .collect(), - ) + .ignore_then(union_literal) .then( space .ignore_then(just('#').ignore_then(space).ignore_then(comment)) @@ -211,6 +63,115 @@ impl Lexer { ) .map(|(t, d)| TagType::Variant(t, d)); + let optional = just('?').or_not().map(|c| match c { + Some(_) => TypeVal::Opt as fn(_, _) -> _, + None => TypeVal::Req as fn(_, _) -> _, + }); + + let name = filter(|x: &char| x.is_alphanumeric() || C.contains(x)) + .repeated() + .collect(); + + let ty = recursive(|inner| { + let comma = just(',').padded(); + let colon = just(':').padded(); + + let any = just("any").to(Ty::Any); + let unknown = just("unknown").to(Ty::Unknown); + let nil = just("nil").to(Ty::Nil); + let boolean = just("boolean").to(Ty::Boolean); + let string = just("string").to(Ty::String); + let num = just("number").to(Ty::Number); + let int = just("integer").to(Ty::Integer); + let function = just("function").to(Ty::Function); + let thread = just("thread").to(Ty::Thread); + let userdata = just("userdata").to(Ty::Userdata); + let lightuserdata = just("lightuserdata").to(Ty::Lightuserdata); + + #[inline] + fn array_union( + p: impl Parser>, + inner: impl Parser>, + ) -> impl Parser> { + p.then(just("[]").repeated()) + .foldl(|arr, _| Ty::Array(Box::new(arr))) + // NOTE: Not the way I wanted i.e., Ty::Union(Vec) it to be, but it's better than nothing + .then(just('|').padded().ignore_then(inner).repeated()) + .foldl(|x, y| Ty::Union(Box::new(x), Box::new(y))) + } + + let list_like = ident() + .padded() + .then(optional) + .then( + colon + .ignore_then(inner.clone()) + .or_not() + // NOTE: if param type is missing then LLS treats it as `any` + .map(|x| x.unwrap_or(Ty::Any)), + ) + .map(|((n, attr), t)| attr(n, t)) + .separated_by(comma) + .allow_trailing(); + + let fun = just("fun") + .ignore_then( + list_like + .clone() + .delimited_by(just('(').then(whitespace()), whitespace().then(just(')'))), + ) + .then( + colon + .ignore_then(inner.clone().separated_by(comma)) + .or_not(), + ) + .map(|(param, ret)| Ty::Fun(param, ret)); + + let table = just("table") + .ignore_then( + just('<') + .ignore_then(inner.clone().map(Box::new)) + .then_ignore(comma) + .then(inner.clone().map(Box::new)) + .then_ignore(just('>')) + .or_not(), + ) + .map(Ty::Table); + + let dict = list_like + .delimited_by(just('{').then(whitespace()), whitespace().then(just('}'))) + .map(Ty::Dict); + + let ty_name = name.map(Ty::Ref); + + let parens = inner + .clone() + .delimited_by(just('(').padded(), just(')').padded()); + + // Union of string literals: '"g@"'|'"g@$"' + let string_literal = union_literal.map(Ty::Ref); + + choice(( + array_union(any, inner.clone()), + array_union(unknown, inner.clone()), + array_union(nil, inner.clone()), + array_union(boolean, inner.clone()), + array_union(string, inner.clone()), + array_union(num, inner.clone()), + array_union(int, inner.clone()), + array_union(function, inner.clone()), + array_union(thread, inner.clone()), + array_union(userdata, inner.clone()), + array_union(lightuserdata, inner.clone()), + array_union(fun, inner.clone()), + array_union(table, inner.clone()), + array_union(dict, inner.clone()), + array_union(parens, inner.clone()), + array_union(string_literal, inner.clone()), + array_union(ty_name, inner), + )) + }); + let tag = just('@').ignore_then(choice(( private.to(TagType::Skip), just("toc") @@ -219,7 +180,7 @@ impl Lexer { .map(TagType::Toc), just("mod") .then_ignore(space) - .ignore_then(ty) + .ignore_then(name) .then(desc) .map(|(name, desc)| TagType::Module(name, desc)), just("divider") @@ -232,14 +193,14 @@ impl Lexer { ))), just("param") .ignore_then(space) - .ignore_then(ty) // I am using `ty` here because param can have `?` + .ignore_then(ident().then(optional)) .then_ignore(space) - .then(ty) + .then(ty.clone()) .then(desc) - .map(|((name, ty), desc)| TagType::Param { name, ty, desc }), + .map(|(((name, opt), ty), desc)| TagType::Param(opt(name, ty), desc)), just("return") .ignore_then(space) - .ignore_then(ty) + .ignore_then(ty.clone()) .then(choice(( newline().to((None, None)), space.ignore_then(choice(( @@ -250,14 +211,14 @@ impl Lexer { .map(|(ty, (name, desc))| TagType::Return { ty, name, desc }), just("class") .ignore_then(space) - .ignore_then(ident()) + .ignore_then(name) .map(TagType::Class), just("field") .ignore_then(space.ignore_then(scope).or_not()) .then_ignore(space) .then(ident()) .then_ignore(space) - .then(ty) + .then(ty.clone()) .then(desc) .map(|(((scope, name), ty), desc)| TagType::Field { scope: scope.unwrap_or(Scope::Public), @@ -267,8 +228,8 @@ impl Lexer { }), just("alias") .ignore_then(space) - .ignore_then(ident()) - .then(space.ignore_then(ty).or_not()) + .ignore_then(name) + .then(space.ignore_then(ty.clone()).or_not()) .map(|(name, ty)| TagType::Alias(name, ty)), just("type") .ignore_then(space) @@ -350,6 +311,5 @@ impl Lexer { .padded() .map_with_span(|t, r| (t, r)) .repeated() - .parse(src) } } diff --git a/src/lexer/token.rs b/src/lexer/token.rs new file mode 100644 index 0000000..c56df42 --- /dev/null +++ b/src/lexer/token.rs @@ -0,0 +1,229 @@ +use std::fmt::Display; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TagType { + /// ```lua + /// ---@toc + /// ``` + Toc(String), + /// ```lua + /// ---@mod [desc] + /// ``` + Module(String, Option), + /// ```lua + /// ---@divider + /// ``` + Divider(char), + /// ```lua + /// function one.two() end + /// one.two = function() end + /// ``` + Func { + prefix: Option, + name: String, + kind: Kind, + }, + /// ```lua + /// one = 1 + /// one.two = 12 + /// ``` + Expr { + prefix: Option, + name: String, + kind: Kind, + }, + /// ```lua + /// ---@export + /// or + /// return \eof + /// ``` + Export(String), + /// ```lua + /// ---@brief [[ + /// ``` + BriefStart, + /// ```lua + /// ---@brief ]] + /// ``` + BriefEnd, + /// ```lua + /// ---@param [description] + /// ``` + Param(TypeVal, Option), + /// ```lua + /// ---@return [ [comment] | [name] #] + /// ``` + Return { + ty: Ty, + name: Option, + desc: Option, + }, + /// ```lua + /// ---@class + /// ``` + Class(String), + /// ```lua + /// ---@field [public|private|protected] [description] + /// ``` + Field { + scope: Scope, + name: String, + ty: Ty, + desc: Option, + }, + /// ```lua + /// -- Simple Alias + /// ---@alias + /// + /// -- Enum alias + /// ---@alias + /// ``` + Alias(String, Option), + /// ```lua + /// ---| '' [# description] + /// ``` + Variant(String, Option), + /// ```lua + /// ---@type [desc] + /// ``` + Type(Ty, Option), + /// ```lua + /// ---@tag + /// ``` + Tag(String), + /// ```lua + /// ---@see + /// ``` + See(String), + /// ```lua + /// ---@usage `` + /// ``` + Usage(String), + /// ```lua + /// ---@usage [[ + /// ``` + UsageStart, + /// ```lua + /// ---@usage ]] + /// ``` + UsageEnd, + /// ```lua + /// ---TEXT + /// ``` + Comment(String), + /// Text nodes which are not needed + Skip, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Kind { + Dot, + Colon, + Local, +} + +impl Kind { + pub fn as_char(&self) -> char { + match self { + Self::Dot => '.', + Self::Colon => ':', + Self::Local => '#', + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Scope { + Public, + Private, + Protected, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TypeVal { + Req(String, Ty), + Opt(String, Ty), +} + +impl Display for TypeVal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Req(n, t) => write!(f, "{n}:{t}"), + Self::Opt(n, t) => write!(f, "{n}?:{t}"), + } + } +} + +// Source: https://github.com/sumneko/lua-language-server/wiki/Annotations#documenting-types +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Ty { + Nil, + Any, + Unknown, + Boolean, + String, + Number, + Integer, + Function, + Thread, + Userdata, + Lightuserdata, + Ref(String), + Array(Box), + Table(Option<(Box, Box)>), + Fun(Vec, Option>), + Dict(Vec), + Union(Box, Box), +} + +impl Display for Ty { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn list_like(args: &[TypeVal]) -> String { + args.iter() + .map(|t| t.to_string()) + .collect::>() + .join(",") + } + + match self { + Self::Nil => f.write_str("nil"), + Self::Any => f.write_str("any"), + Self::Unknown => f.write_str("unknown"), + Self::Boolean => f.write_str("boolean"), + Self::String => f.write_str("string"), + Self::Number => f.write_str("number"), + Self::Integer => f.write_str("integer"), + Self::Function => f.write_str("function"), + Self::Thread => f.write_str("thread"), + Self::Userdata => f.write_str("userdata"), + Self::Lightuserdata => f.write_str("lightuserdata"), + Self::Ref(id) => f.write_str(id), + Self::Array(ty) => write!(f, "{ty}[]"), + Self::Table(kv) => match kv { + Some((k, v)) => write!(f, "table<{k},{v}>"), + None => f.write_str("table"), + }, + Self::Fun(args, ret) => { + f.write_str("fun(")?; + f.write_str(&list_like(args))?; + f.write_str(")")?; + if let Some(ret) = ret { + f.write_str(":")?; + f.write_str( + &ret.iter() + .map(|r| r.to_string()) + .collect::>() + .join(","), + )?; + } + Ok(()) + } + Self::Dict(kv) => { + f.write_str("{")?; + f.write_str(&list_like(kv))?; + f.write_str("}") + } + Self::Union(rhs, lhs) => write!(f, "{rhs}|{lhs}"), + } + } +} diff --git a/src/parser/node.rs b/src/parser/node.rs index afe1c54..ec6875f 100644 --- a/src/parser/node.rs +++ b/src/parser/node.rs @@ -65,7 +65,7 @@ impl Node { /// assert!(!nodes.is_empty()); /// ``` pub fn new(src: &str) -> Result, Vec>> { - let tokens = Lexer::parse(src).unwrap(); + let tokens = Lexer::init().parse(src).unwrap(); let stream = Stream::from_iter(src.len()..src.len() + 1, tokens.into_iter()); Node::parse().repeated().flatten().parse(stream) diff --git a/src/parser/tags/alias.rs b/src/parser/tags/alias.rs index 3139c74..cd18ec4 100644 --- a/src/parser/tags/alias.rs +++ b/src/parser/tags/alias.rs @@ -1,13 +1,13 @@ use chumsky::{prelude::choice, select, Parser}; use crate::{ - lexer::TagType, + lexer::{TagType, Ty}, parser::{impl_parse, Prefix}, }; #[derive(Debug, Clone)] pub enum AliasKind { - Type(String), + Type(Ty), Enum(Vec<(String, Option)>), } diff --git a/src/parser/tags/class.rs b/src/parser/tags/class.rs index 90a298f..c8bc3cb 100644 --- a/src/parser/tags/class.rs +++ b/src/parser/tags/class.rs @@ -1,7 +1,7 @@ use chumsky::{select, Parser}; use crate::{ - lexer::{Scope, TagType}, + lexer::{Scope, TagType, Ty}, parser::{impl_parse, Prefix, See}, }; @@ -9,7 +9,7 @@ use crate::{ pub struct Field { pub scope: Scope, pub name: String, - pub ty: String, + pub ty: Ty, pub desc: Vec, } diff --git a/src/parser/tags/func.rs b/src/parser/tags/func.rs index c172024..0128661 100644 --- a/src/parser/tags/func.rs +++ b/src/parser/tags/func.rs @@ -1,38 +1,35 @@ use chumsky::{select, Parser}; use crate::{ - lexer::{Kind, TagType}, + lexer::{Kind, TagType, Ty, TypeVal}, parser::{impl_parse, Prefix, See}, }; use super::Usage; #[derive(Debug, Clone)] -pub struct Param { - pub name: String, - pub ty: String, - pub desc: Vec, -} +pub struct Param(pub TypeVal, pub Vec); impl_parse!(Param, { - select! { TagType::Param { name, ty, desc } => (name, ty, desc) } - .then(select! { TagType::Comment(x) => x }.repeated()) - .map(|((name, ty, desc), extra)| { - let desc = match desc { - Some(d) => Vec::from([d]) - .into_iter() - .chain(extra.into_iter()) - .collect(), - None => extra, - }; - - Self { name, ty, desc } - }) + select! { + TagType::Param(typeval, desc) => (typeval, desc) + } + .then(select! { TagType::Comment(x) => x }.repeated()) + .map(|((typeval, desc), extra)| { + let desc = match desc { + Some(d) => Vec::from([d]) + .into_iter() + .chain(extra.into_iter()) + .collect(), + None => extra, + }; + Self(typeval, desc) + }) }); #[derive(Debug, Clone)] pub struct Return { - pub ty: String, + pub ty: Ty, pub name: Option, pub desc: Vec, } diff --git a/src/parser/tags/type.rs b/src/parser/tags/type.rs index 5823cc4..d6f7d06 100644 --- a/src/parser/tags/type.rs +++ b/src/parser/tags/type.rs @@ -1,7 +1,7 @@ use chumsky::{select, Parser}; use crate::{ - lexer::{Kind, TagType}, + lexer::{Kind, TagType, Ty}, parser::{impl_parse, Prefix, See}, }; @@ -13,7 +13,7 @@ pub struct Type { pub name: String, pub desc: (Vec, Option), pub kind: Kind, - pub ty: String, + pub ty: Ty, pub see: See, pub usage: Option, } diff --git a/src/vimdoc/alias.rs b/src/vimdoc/alias.rs index bed9cb6..3ee03a8 100644 --- a/src/vimdoc/alias.rs +++ b/src/vimdoc/alias.rs @@ -31,6 +31,7 @@ impl Display for AliasDoc<'_> { match &kind { AliasKind::Type(ty) => { description!(f, "Type: ~")?; + let ty = ty.to_string(); writeln!(f, "{:>w$}", ty, w = 8 + ty.len())?; } AliasKind::Enum(variants) => { diff --git a/src/vimdoc/func.rs b/src/vimdoc/func.rs index 4dfb89b..0263f05 100644 --- a/src/vimdoc/func.rs +++ b/src/vimdoc/func.rs @@ -1,6 +1,9 @@ use std::fmt::Display; -use crate::parser::Func; +use crate::{ + lexer::{Ty, TypeVal}, + parser::{Func, Param}, +}; use super::{description, header, see::SeeDoc, usage::UsageDoc, Table}; @@ -20,30 +23,40 @@ impl Display for FuncDoc<'_> { usage, } = self.0; + fn is_opt(typeval: &'_ TypeVal) -> (String, &Ty) { + match typeval { + TypeVal::Opt(k, v) => (format!("{{{k}?}}"), v), + TypeVal::Req(k, v) => (format!("{{{k}}}"), v), + } + } + let name_with_param = if !params.is_empty() { let args = params .iter() - .map(|x| format!("{{{}}}", x.name)) + .map(|Param(typeval, _)| is_opt(typeval).0) .collect::>() .join(", "); - format!("{}({})", name, args) + format!( + "{}{}{name}({args})", + prefix.left.as_deref().unwrap_or_default(), + kind.as_char() + ) } else { - format!("{}()", name) + format!( + "{}{}{name}()", + prefix.left.as_deref().unwrap_or_default(), + kind.as_char() + ) }; header!( f, + name_with_param, &format!( - "{}{}{name_with_param}", - prefix.left.as_deref().unwrap_or_default(), - kind.as_char() - ), - &format!( - "{}{}{}", + "{}{}{name}", prefix.right.as_deref().unwrap_or_default(), kind.as_char(), - name ) )?; @@ -58,12 +71,9 @@ impl Display for FuncDoc<'_> { let mut table = Table::new(); - for param in params { - table.add_row([ - format!("{{{}}}", param.name), - format!("({})", param.ty), - param.desc.join("\n"), - ]); + for Param(typeval, desc) in params { + let (name, ty) = is_opt(typeval); + table.add_row([name, format!("({ty})"), desc.join("\n")]); } writeln!(f, "{table}")?; diff --git a/tests/basic.rs b/tests/basic.rs index ad54665..873fca9 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -194,9 +194,9 @@ fn functions() { --- ---NOTE: This will be part of the public API ---@param this number First number - ---@param that number + ---@param that? number function U.sum(this, that) - print(this + that) + print(this + that or 0) end ---Subtract second from the first integer @@ -235,14 +235,14 @@ fn functions() { assert_eq!( VimDoc::from_emmy(&lemmy, ()).to_string(), "\ -U.sum({this}, {that}) *U.sum* +U.sum({this}, {that?}) *U.sum* Add two integer and print it NOTE: This will be part of the public API Parameters: ~ - {this} (number) First number - {that} (number) + {this} (number) First number + {that?} (number) U.sub({this}, {that}) *U.sub* diff --git a/tests/types.rs b/tests/types.rs new file mode 100644 index 0000000..42a491c --- /dev/null +++ b/tests/types.rs @@ -0,0 +1,230 @@ +use chumsky::Parser; +use lemmy_help::lexer::{Lexer, Ty, TypeVal}; + +macro_rules! b { + ($t:expr) => { + Box::new($t) + }; +} + +#[test] +fn types() { + let type_parse = Lexer::init(); + + macro_rules! check { + ($s:expr, $ty:expr) => { + assert_eq!( + type_parse + .parse(concat!("---@type ", $s)) + .unwrap() + .into_iter() + .next() + .unwrap() + .0, + lemmy_help::lexer::TagType::Type($ty, None) + ); + }; + } + + check!("nil", Ty::Nil); + check!("any", Ty::Any); + check!("unknown", Ty::Unknown); + check!("boolean", Ty::Boolean); + check!("string", Ty::String); + check!("number", Ty::Number); + check!("integer", Ty::Integer); + check!("function", Ty::Function); + check!("thread", Ty::Thread); + check!("userdata", Ty::Userdata); + check!("lightuserdata", Ty::Lightuserdata); + check!("Any-Thing.El_se", Ty::Ref("Any-Thing.El_se".into())); + + check!( + "(string|number|table)[]", + Ty::Array(b!(Ty::Union( + b!(Ty::String), + b!(Ty::Union( + b!(Ty::Number), + b!(Ty::Table(Some(( + b!(Ty::String), + b!(Ty::Array(b!(Ty::String))) + )))) + )) + ))) + ); + + check!( + "table[]", + Ty::Array(b!(Ty::Table(Some(( + b!(Ty::String), + b!(Ty::Dict(vec![ + TypeVal::Req("get".into(), Ty::String), + TypeVal::Req("set".into(), Ty::String), + ])) + ))))) + ); + + check!( + "table", + Ty::Table(Some(( + b!(Ty::String), + b!(Ty::Fun( + vec![TypeVal::Req("a".into(), Ty::String)], + Some(vec![Ty::String]) + )) + ))) + ); + + check!( + "table>", + Ty::Table(Some(( + b!(Ty::Fun(vec![], None)), + b!(Ty::Table(Some((b!(Ty::String), b!(Ty::Number))))) + ))) + ); + + check!( + "table[]", + Ty::Array(b!(Ty::Table(Some(( + b!(Ty::String), + b!(Ty::Union( + b!(Ty::String), + b!(Ty::Union(b!(Ty::Array(b!(Ty::String))), b!(Ty::Boolean))) + )) + ))))) + ); + + check!( + "fun( + a: string, b: string|number|boolean, c: number[][], d?: SomeClass + ): number, string|string[]", + Ty::Fun( + vec![ + TypeVal::Req("a".into(), Ty::String), + TypeVal::Req( + "b".into(), + Ty::Union( + b!(Ty::String), + b!(Ty::Union(b!(Ty::Number), b!(Ty::Boolean))) + ) + ), + TypeVal::Req("c".into(), Ty::Array(b!(Ty::Array(b!(Ty::Number))))), + TypeVal::Opt("d".into(), Ty::Ref("SomeClass".into())), + ], + Some(vec![ + Ty::Number, + Ty::Union(b!(Ty::String), b!(Ty::Array(b!(Ty::String)))) + ]) + ) + ); + + // "fun(a: string, b: (string|table)[]|boolean, c: string[], d: number[][]): string|string[]", + + check!( + "fun( + a: string, + b?: string, + c: function, + d: fun(z: string), + e: string|string[]|table|fun(y: string[]|{ get: function }|string): string + ): table", + Ty::Fun( + vec![ + TypeVal::Req("a".into(), Ty::String), + TypeVal::Opt("b".into(), Ty::String), + TypeVal::Req("c".into(), Ty::Function), + TypeVal::Req( + "d".into(), + Ty::Fun(vec![TypeVal::Req("z".into(), Ty::String)], None) + ), + TypeVal::Req( + "e".into(), + Ty::Union( + b!(Ty::String), + b!(Ty::Union( + b!(Ty::Array(b!(Ty::String))), + b!(Ty::Union( + b!(Ty::Table(Some((b!(Ty::String), b!(Ty::String))))), + b!(Ty::Fun( + vec![TypeVal::Req( + "y".into(), + Ty::Union( + b!(Ty::Array(b!(Ty::String))), + b!(Ty::Union( + b!(Ty::Dict(vec![TypeVal::Req( + "get".into(), + Ty::Function + )])), + b!(Ty::String) + )) + ) + ),], + Some(vec![Ty::String]) + )) + )) + )) + ) + ) + ], + Some(vec![Ty::Table(Some((b!(Ty::String), b!(Ty::String))))]) + ) + ); + + check!( + "{ + inner: string, + get: fun(a: unknown), + set: fun(a: unknown), + __proto__?: { _?: unknown } + }", + Ty::Dict(vec![ + TypeVal::Req("inner".into(), Ty::String), + TypeVal::Req( + "get".into(), + Ty::Fun(vec![TypeVal::Req("a".into(), Ty::Unknown)], None,) + ), + TypeVal::Req( + "set".into(), + Ty::Fun(vec![TypeVal::Req("a".into(), Ty::Unknown)], None) + ), + TypeVal::Opt( + "__proto__".into(), + Ty::Dict(vec![TypeVal::Opt("_".into(), Ty::Unknown)]) + ) + ]) + ); + + check!( + r#"'"g@"'|string[]|'"g@$"'|number"#, + Ty::Union( + b!(Ty::Ref(r#""g@""#.into())), + b!(Ty::Union( + b!(Ty::Array(b!(Ty::String))), + b!(Ty::Union(b!(Ty::Ref(r#""g@$""#.into())), b!(Ty::Number))) + )) + ) + ); + + check!( + "any|any|string|(string|number)[]|fun(a: string)|table|userdata[]", + Ty::Union( + b!(Ty::Any), + b!(Ty::Union( + b!(Ty::Any), + b!(Ty::Union( + b!(Ty::String), + b!(Ty::Union( + b!(Ty::Array(b!(Ty::Union(b!(Ty::String), b!(Ty::Number))))), + b!(Ty::Union( + b!(Ty::Fun(vec![TypeVal::Req("a".into(), Ty::String)], None)), + b!(Ty::Union( + b!(Ty::Table(Some((b!(Ty::String), b!(Ty::Number))))), + b!(Ty::Array(b!(Ty::Userdata))) + )) + )) + )) + )) + )) + ) + ); +}