diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cad8f1..9b2d8b58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +* migrate parser to the latest version. Reduce stack overflow issues, add support for compound assignments using floor division and leading symbols in union and intersection types ([#219](https://github.com/seaofvoices/darklua/pull/219)) + ## 0.13.1 * fix `remove_unused_variable` rule ([#192](https://github.com/seaofvoices/darklua/pull/192)) diff --git a/Cargo.lock b/Cargo.lock index ad9e24dd..b377a77b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,12 +93,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" - [[package]] name = "bitflags" version = "1.3.2" @@ -120,6 +114,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "cfg_aliases 0.2.1", +] + [[package]] name = "brownstone" version = "3.0.0" @@ -193,6 +196,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "ciborium" version = "0.2.2" @@ -298,12 +307,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "cpufeatures" version = "0.2.12" @@ -450,15 +453,23 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.17" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ - "convert_case", "proc-macro2", "quote", - "rustc_version", - "syn 1.0.109", + "syn 2.0.64", + "unicode-xid", ] [[package]] @@ -573,12 +584,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "fsevent-sys" version = "4.1.0" @@ -590,19 +595,17 @@ dependencies = [ [[package]] name = "full_moon" -version = "0.19.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ef4f8ad0689d3a86bb483650422d72e6f79a37fdc83ed5426cafe96b776ce1" +checksum = "dcd269eb6078dde1b26db6368cb0b062045976d4d858d85b28f4f59ba49aab4e" dependencies = [ "bytecount", "cfg-if 1.0.0", "derive_more", "full_moon_derive", - "logos", "paste", "serde", "smol_str", - "stacker", ] [[package]] @@ -885,29 +888,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "logos" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" -dependencies = [ - "logos-derive", -] - -[[package]] -name = "logos-derive" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" -dependencies = [ - "beef", - "fnv", - "proc-macro2", - "quote", - "regex-syntax 0.6.29", - "syn 1.0.109", -] - [[package]] name = "loom" version = "0.7.2" @@ -962,7 +942,7 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.5.0", "cfg-if 1.0.0", - "cfg_aliases", + "cfg_aliases 0.1.1", "libc", ] @@ -1222,15 +1202,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "psm" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" -dependencies = [ - "cc", -] - [[package]] name = "quote" version = "1.0.36" @@ -1353,15 +1324,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.38.34" @@ -1402,12 +1364,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" - [[package]] name = "serde" version = "1.0.202" @@ -1504,10 +1460,11 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smol_str" -version = "0.1.24" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9" +checksum = "66eaf762c5af19db3108300515c8aa7a50efc90ff745f4c62288052ebf9fdd25" dependencies = [ + "borsh", "serde", ] @@ -1517,19 +1474,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "stacker" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" -dependencies = [ - "cc", - "cfg-if 1.0.0", - "libc", - "psm", - "winapi", -] - [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index 025450b0..873071ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,9 +23,7 @@ name = "darklua" path = "src/bin.rs" [features] -default = ["stacker"] tracing = ["dep:tracing"] -stacker = ["full_moon/stacker"] [dependencies] anstyle = "1.0.6" @@ -33,7 +31,7 @@ clap = { version = "4.5.3", features = ["derive"] } durationfmt = "0.1.1" elsa = "1.10.0" env_logger = "0.11.3" -full_moon = { version = "0.19.0", features = ["roblox"] } +full_moon = { version = "1.0.0", features = ["roblox"] } json5 = "0.4.1" log = "0.4.21" pathdiff = "0.2.1" @@ -84,3 +82,6 @@ harness = false [[bench]] name = "parse_bench" harness = false + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage)'] } diff --git a/src/ast_converter.rs b/src/ast_converter.rs index 74a6f1d3..7e63dbc7 100644 --- a/src/ast_converter.rs +++ b/src/ast_converter.rs @@ -2,6 +2,7 @@ use std::{fmt, str::FromStr}; use full_moon::{ ast, + node::Node, tokenizer::{self, InterpolatedStringKind, Symbol, TokenType}, }; @@ -118,6 +119,11 @@ impl<'a> AstConverter<'a> { .ok_or(ConvertError::InternalStack { kind: "Type" }) } + #[inline] + fn pop_types(&mut self, n: usize) -> Result, ConvertError> { + std::iter::repeat_with(|| self.pop_type()).take(n).collect() + } + #[inline] fn pop_variadic_type_pack(&mut self) -> Result { self.variadic_type_packs @@ -397,10 +403,12 @@ impl<'a> AstConverter<'a> { let mut value_segment = ValueSegment::new(expression); if self.hold_token_data { + let literal_end = self.convert_token_end_position(literal)?; + let mut opening_brace = Token::new_with_line( - literal.end_position().bytes().saturating_sub(1), - literal.end_position().bytes(), - literal.end_position().line(), + literal_end.0.saturating_sub(1), + literal_end.0, + literal_end.1, ); for trivia_token in literal.trailing_trivia() { @@ -413,11 +421,13 @@ impl<'a> AstConverter<'a> { .map(|next_segment| &next_segment.literal) .unwrap_or(interpolated_string.last_string()); - let start_position = next_literal.start_position().bytes(); + let next_literal_position = + self.convert_token_position(next_literal)?; + let closing_brace = Token::new_with_line( - start_position, - start_position + 1, - next_literal.start_position().line(), + next_literal_position.0, + next_literal_position.0.saturating_add(1), + next_literal_position.2, ); value_segment.set_tokens(ValueSegmentTokens { @@ -448,17 +458,17 @@ impl<'a> AstConverter<'a> { let (opening_tick, closing_tick) = match first.token_type() { TokenType::InterpolatedString { literal: _, kind } => match kind { InterpolatedStringKind::Begin | InterpolatedStringKind::Simple => { - let start_position = first.start_position().bytes(); + let first_position = self.convert_token_position(first)?; let mut start_token = Token::new_with_line( - start_position, - start_position + 1, - first.start_position().line(), + first_position.0, + first_position.0.saturating_add(1), + first_position.2, ); - let end_position = last.end_position().bytes(); + let last_position = self.convert_token_end_position(last)?; let mut end_token = Token::new_with_line( - end_position.saturating_sub(1), - end_position, - last.end_position().line(), + last_position.0.saturating_sub(1), + last_position.0, + last_position.1, ); for trivia_token in first.leading_trivia() { @@ -632,7 +642,7 @@ impl<'a> AstConverter<'a> { for parameter in generics.generics() { match parameter.parameter() { - ast::types::GenericParameterInfo::Name(token) => { + ast::luau::GenericParameterInfo::Name(token) => { let name = self.convert_token_to_identifier(token)?; if let Some(default_type) = parameter @@ -660,16 +670,16 @@ impl<'a> AstConverter<'a> { .push(self.convert_token_to_identifier(token)?); } } - ast::types::GenericParameterInfo::Variadic { name, ellipse } => { + ast::luau::GenericParameterInfo::Variadic { name, ellipsis } => { let mut generic_pack = GenericTypePack::new( self.convert_token_to_identifier(name)?, ); if self.hold_token_data { - generic_pack.set_token(self.convert_token(ellipse)?); + generic_pack.set_token(self.convert_token(ellipsis)?); } - use ast::types::TypeInfo; + use ast::luau::TypeInfo; if let Some(default_type) = parameter .default_type() @@ -981,7 +991,7 @@ impl<'a> AstConverter<'a> { self.statements.push(if_statement.into()); } ConvertWork::MakeFunctionReturnType { type_info } => { - use ast::types::TypeInfo; + use ast::luau::TypeInfo; let return_type = if is_variadic_type(type_info).is_some() { self.pop_variadic_type_pack()?.into() @@ -995,11 +1005,11 @@ impl<'a> AstConverter<'a> { self.function_return_types.push(return_type); } - ConvertWork::MakeVariadicTypePack { ellipse } => { + ConvertWork::MakeVariadicTypePack { ellipsis } => { let mut variadic_type_pack = VariadicTypePack::new(self.pop_type()?); if self.hold_token_data { - variadic_type_pack.set_token(self.convert_token(ellipse)?); + variadic_type_pack.set_token(self.convert_token(ellipsis)?); } self.variadic_type_packs.push(variadic_type_pack); @@ -1028,26 +1038,46 @@ impl<'a> AstConverter<'a> { self.types.push(optional_type.into()); } - ConvertWork::MakeIntersectionType { operator } => { - let left_type = self.pop_type()?; - let right_type = self.pop_type()?; + ConvertWork::MakeIntersectionType { + length, + leading_token, + separators, + } => { + let types = self.pop_types(length)?; - let mut intersection_type = IntersectionType::new(left_type, right_type); + let mut intersection_type = IntersectionType::from(types); if self.hold_token_data { - intersection_type.set_token(self.convert_token(operator)?); + intersection_type.set_tokens(IntersectionTypeTokens { + leading_token: leading_token + .map(|token| self.convert_token(token)) + .transpose()?, + separators: self.extract_tokens_from_punctuation(separators)?, + }); + } else if leading_token.is_some() { + intersection_type.put_leading_token(); } self.types.push(intersection_type.into()); } - ConvertWork::MakeUnionType { operator } => { - let left_type = self.pop_type()?; - let right_type = self.pop_type()?; + ConvertWork::MakeUnionType { + length, + leading_token, + separators, + } => { + let types = self.pop_types(length)?; - let mut union_type = UnionType::new(left_type, right_type); + let mut union_type = UnionType::from(types); if self.hold_token_data { - union_type.set_token(self.convert_token(operator)?); + union_type.set_tokens(UnionTypeTokens { + leading_token: leading_token + .map(|token| self.convert_token(token)) + .transpose()?, + separators: self.extract_tokens_from_punctuation(separators)?, + }); + } else if leading_token.is_some() { + union_type.put_leading_token(); } self.types.push(union_type.into()); @@ -1056,7 +1086,7 @@ impl<'a> AstConverter<'a> { let mut table_type = TableType::default(); for field in fields { - use ast::types::TypeFieldKey; + use ast::luau::TypeFieldKey; match field.key() { TypeFieldKey::Name(property_name) => { @@ -1138,7 +1168,7 @@ impl<'a> AstConverter<'a> { let mut function_type = FunctionType::new(self.pop_function_return_type()?); for argument in arguments { - use ast::types::TypeInfo; + use ast::luau::TypeInfo; if is_variadic_type(argument.type_info()).is_some() { function_type.set_variadic_type(self.pop_variadic_type_pack()?); @@ -1210,7 +1240,7 @@ impl<'a> AstConverter<'a> { }); } ConvertWork::MakeTypeParameters { arrows, generics } => { - use ast::types::TypeInfo; + use ast::luau::TypeInfo; let mut parameters = generics .iter() @@ -1294,7 +1324,7 @@ impl<'a> AstConverter<'a> { self.types.push(parenthese_type.into()); } ConvertWork::MakeTypePack { types, parentheses } => { - use ast::types::TypeInfo; + use ast::luau::TypeInfo; let mut type_pack = TypePack::default(); @@ -1346,13 +1376,13 @@ impl<'a> AstConverter<'a> { fn convert_generic_type_parameters( &mut self, - generics: &ast::types::GenericDeclaration, + generics: &ast::luau::GenericDeclaration, ) -> Result { let mut type_variables = Vec::new(); let mut generic_type_packs = Vec::new(); for parameter in generics.generics() { match parameter.parameter() { - ast::types::GenericParameterInfo::Name(name) => { + ast::luau::GenericParameterInfo::Name(name) => { if !generic_type_packs.is_empty() { return Err(ConvertError::GenericDeclaration { generics: generics.to_string(), @@ -1360,12 +1390,12 @@ impl<'a> AstConverter<'a> { } type_variables.push(self.convert_token_to_identifier(name)?); } - ast::types::GenericParameterInfo::Variadic { name, ellipse } => { + ast::luau::GenericParameterInfo::Variadic { name, ellipsis } => { let mut generic_pack = GenericTypePack::new(self.convert_token_to_identifier(name)?); if self.hold_token_data { - generic_pack.set_token(self.convert_token(ellipse)?); + generic_pack.set_token(self.convert_token(ellipsis)?); } generic_type_packs.push(generic_pack); @@ -1549,7 +1579,7 @@ impl<'a> AstConverter<'a> { fn convert_type_declaration( &mut self, - type_declaration: &'a ast::types::TypeDeclaration, + type_declaration: &'a ast::luau::TypeDeclaration, export_token: Option<&'a tokenizer::TokenReference>, ) { self.work_stack @@ -1564,8 +1594,8 @@ impl<'a> AstConverter<'a> { if let Some(default_type) = parameter.default_type() { match (parameter.parameter(), default_type) { ( - ast::types::GenericParameterInfo::Variadic { .. }, - ast::types::TypeInfo::Tuple { parentheses, types }, + ast::luau::GenericParameterInfo::Variadic { .. }, + ast::luau::TypeInfo::Tuple { parentheses, types }, ) => { self.push_type_pack_work(types, parentheses); } @@ -1798,7 +1828,8 @@ impl<'a> AstConverter<'a> { self.push_work(type_assertion.cast_to()); self.push_work(expression.as_ref()); } - ast::Expression::Function((token, body)) => { + ast::Expression::Function(function) => { + let (token, body) = function.as_ref(); self.work_stack .push(ConvertWork::MakeFunctionExpression { body, token }); @@ -1842,7 +1873,7 @@ impl<'a> AstConverter<'a> { Symbol::True => Expression::True(token), Symbol::False => Expression::False(token), Symbol::Nil => Expression::Nil(token), - Symbol::Ellipse => Expression::VariableArguments(token), + Symbol::Ellipsis => Expression::VariableArguments(token), _ => { return Err(ConvertError::Expression { expression: expression.to_string(), @@ -1917,12 +1948,12 @@ impl<'a> AstConverter<'a> { } } - fn push_function_return_type(&mut self, return_type: &'a ast::types::TypeInfo) { + fn push_function_return_type(&mut self, return_type: &'a ast::luau::TypeInfo) { self.push_work(ConvertWork::MakeFunctionReturnType { type_info: return_type, }); match return_type { - ast::types::TypeInfo::Tuple { types, parentheses } => { + ast::luau::TypeInfo::Tuple { types, parentheses } => { self.push_type_pack_work(types, parentheses); } _ => { @@ -1933,7 +1964,7 @@ impl<'a> AstConverter<'a> { fn push_type_pack_work( &mut self, - types: &'a ast::punctuated::Punctuated, + types: &'a ast::punctuated::Punctuated, parentheses: &'a ast::span::ContainedSpan, ) { self.work_stack @@ -1952,12 +1983,16 @@ impl<'a> AstConverter<'a> { #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))] fn convert_type_info( &mut self, - type_info: &'a ast::types::TypeInfo, + type_info: &'a ast::luau::TypeInfo, ) -> Result<(), ConvertError> { - use ast::types::TypeInfo; + use ast::luau::TypeInfo; match type_info { - TypeInfo::Array { braces, type_info } => { + TypeInfo::Array { + braces, + type_info, + access: _, + } => { self.work_stack.push(ConvertWork::MakeArrayType { braces }); self.push_work(type_info.as_ref()); @@ -2019,9 +2054,6 @@ impl<'a> AstConverter<'a> { arrow, return_type, } => { - let (override_return_type, push_right_expression) = - self.patch_return_type_tuple(return_type); - self.work_stack.push(ConvertWork::MakeFunctionType { generics, parentheses, @@ -2029,7 +2061,7 @@ impl<'a> AstConverter<'a> { arrow, }); - self.push_function_return_type(override_return_type); + self.push_function_return_type(return_type); let mut has_variadic_type = false; @@ -2045,10 +2077,6 @@ impl<'a> AstConverter<'a> { } self.push_maybe_variadic_type(argument_type); } - - for right in push_right_expression { - self.push_work(right); - } } TypeInfo::Generic { base, @@ -2057,41 +2085,44 @@ impl<'a> AstConverter<'a> { } => { self.push_generic_type_work(base, arrows, generics, None); } - TypeInfo::GenericPack { name, ellipse } => { + TypeInfo::GenericPack { name, ellipsis } => { let mut generic_pack = GenericTypePack::new(self.convert_token_to_identifier(name)?); if self.hold_token_data { - generic_pack.set_token(self.convert_token(ellipse)?); + generic_pack.set_token(self.convert_token(ellipsis)?); } self.generic_type_packs.push(generic_pack); } - TypeInfo::Intersection { - left, - ampersand, - right, - } => { + TypeInfo::Intersection(intersection) => { self.work_stack.push(ConvertWork::MakeIntersectionType { - operator: ampersand, + leading_token: intersection.leading(), + separators: intersection.types(), + length: intersection.types().len(), }); - self.push_work(left.as_ref()); - self.push_work(right.as_ref()); + for type_info in intersection.types() { + self.push_work(type_info); + } } - TypeInfo::Union { left, pipe, right } => { - self.work_stack - .push(ConvertWork::MakeUnionType { operator: pipe }); + TypeInfo::Union(union) => { + self.work_stack.push(ConvertWork::MakeUnionType { + leading_token: union.leading(), + separators: union.types(), + length: union.types().len(), + }); - self.push_work(left.as_ref()); - self.push_work(right.as_ref()); + for type_info in union.types() { + self.push_work(type_info); + } } TypeInfo::Module { module, punctuation, type_info, } => match type_info.as_ref() { - ast::types::IndexedTypeInfo::Basic(name) => { + ast::luau::IndexedTypeInfo::Basic(name) => { let mut type_field = TypeField::new( self.convert_token_to_identifier(module)?, TypeName::new(self.convert_token_to_identifier(name)?), @@ -2104,7 +2135,7 @@ impl<'a> AstConverter<'a> { self.work_stack .push(ConvertWork::PushType(type_field.into())); } - ast::types::IndexedTypeInfo::Generic { + ast::luau::IndexedTypeInfo::Generic { base, arrows, generics, @@ -2136,7 +2167,7 @@ impl<'a> AstConverter<'a> { .push(ConvertWork::MakeTableType { braces, fields }); for field in fields { - use ast::types::TypeFieldKey; + use ast::luau::TypeFieldKey; match field.key() { TypeFieldKey::Name(_) => {} @@ -2198,10 +2229,10 @@ impl<'a> AstConverter<'a> { Ok(()) } - fn push_maybe_variadic_type(&mut self, type_info: &'a ast::types::TypeInfo) { - if let Some(ellipse) = is_variadic_type(type_info) { + fn push_maybe_variadic_type(&mut self, type_info: &'a ast::luau::TypeInfo) { + if let Some(ellipsis) = is_variadic_type(type_info) { self.work_stack - .push(ConvertWork::MakeVariadicTypePack { ellipse }); + .push(ConvertWork::MakeVariadicTypePack { ellipsis }); } self.push_work(type_info); } @@ -2210,7 +2241,7 @@ impl<'a> AstConverter<'a> { &mut self, base: &'a tokenizer::TokenReference, arrows: &'a ast::span::ContainedSpan, - generics: &'a ast::punctuated::Punctuated, + generics: &'a ast::punctuated::Punctuated, module: Option<(&'a tokenizer::TokenReference, &'a tokenizer::TokenReference)>, ) { self.work_stack @@ -2221,7 +2252,7 @@ impl<'a> AstConverter<'a> { for parameter_type in generics { match parameter_type { - ast::types::TypeInfo::Tuple { parentheses, types } => { + ast::luau::TypeInfo::Tuple { parentheses, types } => { self.push_type_pack_work(types, parentheses); } _ => { @@ -2282,13 +2313,44 @@ impl<'a> AstConverter<'a> { Ok(()) } + fn convert_token_position( + &self, + token: &tokenizer::TokenReference, + ) -> Result<(usize, usize, usize), ConvertError> { + let start = token + .start_position() + .ok_or_else(|| ConvertError::TokenPositionNotFound { + token: token.to_string(), + })?; + Ok(( + start.bytes(), + token + .end_position() + .ok_or_else(|| ConvertError::TokenPositionNotFound { + token: token.to_string(), + })? + .bytes(), + start.line(), + )) + } + + fn convert_token_end_position( + &self, + token: &tokenizer::TokenReference, + ) -> Result<(usize, usize), ConvertError> { + let end_position = + token + .end_position() + .ok_or_else(|| ConvertError::TokenPositionNotFound { + token: token.to_string(), + })?; + Ok((end_position.bytes(), end_position.line())) + } + #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))] fn convert_token(&self, token: &tokenizer::TokenReference) -> Result { - let mut new_token = Token::new_with_line( - token.start_position().bytes(), - token.end_position().bytes(), - token.start_position().line(), - ); + let position = self.convert_token_position(token)?; + let mut new_token = Token::new_with_line(position.0, position.1, position.2); for trivia_token in token.leading_trivia() { new_token.push_leading_trivia(self.convert_trivia(trivia_token)?); @@ -2335,7 +2397,7 @@ impl<'a> AstConverter<'a> { fn convert_typed_identifier( &mut self, identifier: &tokenizer::TokenReference, - type_specifier: Option<&ast::types::TypeSpecifier>, + type_specifier: Option<&ast::luau::TypeSpecifier>, ) -> Result { let identifier = self.convert_token_to_identifier(identifier)?; @@ -2390,7 +2452,7 @@ impl<'a> AstConverter<'a> { for (param, type_specifier) in body.parameters().iter().zip(body.type_specifiers()) { match param { - ast::Parameter::Ellipse(token) => { + ast::Parameter::Ellipsis(token) => { if builder.is_variadic() { return Err(ConvertError::FunctionParameters { parameters: body.parameters().to_string(), @@ -2398,7 +2460,7 @@ impl<'a> AstConverter<'a> { } else { if let Some(type_specifier) = type_specifier { builder.set_variadic_type( - if let ast::types::TypeInfo::GenericPack { .. } = + if let ast::luau::TypeInfo::GenericPack { .. } = type_specifier.type_info() { self.pop_generic_type_pack()?.into() @@ -2547,19 +2609,17 @@ impl<'a> AstConverter<'a> { #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))] fn convert_compound_op( &self, - operator: &ast::types::CompoundOp, + operator: &ast::luau::CompoundOp, ) -> Result { Ok(match operator { - ast::types::CompoundOp::PlusEqual(_) => CompoundOperator::Plus, - ast::types::CompoundOp::MinusEqual(_) => CompoundOperator::Minus, - ast::types::CompoundOp::StarEqual(_) => CompoundOperator::Asterisk, - ast::types::CompoundOp::SlashEqual(_) => CompoundOperator::Slash, - // todo: once full-moon fixes this issue and the change is in a new release - // https://github.com/Kampfkarren/full-moon/issues/292 - // ast::types::CompoundOp::DoubleSlashEqual(_) => CompoundOperator::DoubleSlash, - ast::types::CompoundOp::PercentEqual(_) => CompoundOperator::Percent, - ast::types::CompoundOp::CaretEqual(_) => CompoundOperator::Caret, - ast::types::CompoundOp::TwoDotsEqual(_) => CompoundOperator::Concat, + ast::luau::CompoundOp::PlusEqual(_) => CompoundOperator::Plus, + ast::luau::CompoundOp::MinusEqual(_) => CompoundOperator::Minus, + ast::luau::CompoundOp::StarEqual(_) => CompoundOperator::Asterisk, + ast::luau::CompoundOp::SlashEqual(_) => CompoundOperator::Slash, + ast::luau::CompoundOp::DoubleSlashEqual(_) => CompoundOperator::DoubleSlash, + ast::luau::CompoundOp::PercentEqual(_) => CompoundOperator::Percent, + ast::luau::CompoundOp::CaretEqual(_) => CompoundOperator::Caret, + ast::luau::CompoundOp::TwoDotsEqual(_) => CompoundOperator::Concat, _ => { return Err(ConvertError::CompoundOperator { operator: operator.to_string(), @@ -2637,10 +2697,11 @@ impl<'a> AstConverter<'a> { .expect("unable to convert interpolated string segment"); if self.hold_token_data { + let position = self.convert_token_position(token)?; let segment_token = Token::new_with_line( - token.start_position().bytes() + 1, - token.end_position().bytes().saturating_sub(1), - token.start_position().line(), + position.0.saturating_add(1), + position.1.saturating_sub(1), + position.2, ); // no trivia since it is grabbing a substring of the token segment.set_token(segment_token); @@ -2654,121 +2715,61 @@ impl<'a> AstConverter<'a> { _ => unreachable!(), } } - - fn patch_return_type_tuple( - &mut self, - r#type: &'a ast::types::TypeInfo, - ) -> (&'a ast::types::TypeInfo, Vec<&'a ast::types::TypeInfo>) { - use ast::types::TypeInfo; - let mut current = r#type; - let mut additional_types = Vec::new(); - - loop { - match current { - TypeInfo::Tuple { types, .. } => { - if types.len() == 1 { - break (r#type, additional_types); - } else { - break (current, additional_types); - } - } - TypeInfo::Optional { - base, - question_mark, - } => match base.as_ref() { - TypeInfo::Tuple { types, .. } if types.len() != 1 => { - self.work_stack - .push(ConvertWork::MakeOptionalType { question_mark }); - - current = base; - } - TypeInfo::GenericPack { .. } => { - self.work_stack - .push(ConvertWork::MakeOptionalType { question_mark }); - - break (base, additional_types); - } - _ => break (current, additional_types), - }, - TypeInfo::Intersection { - left, - right, - ampersand, - } => match left.as_ref() { - TypeInfo::Tuple { types, .. } if types.len() != 1 => { - self.work_stack.push(ConvertWork::MakeIntersectionType { - operator: ampersand, - }); - additional_types.push(right.as_ref()); - - break (left, additional_types); - } - TypeInfo::GenericPack { .. } => { - // if we get a generic pack here then we are - // not making a function type - self.work_stack.push(ConvertWork::MakeIntersectionType { - operator: ampersand, - }); - additional_types.push(right.as_ref()); - - break (left, additional_types); - } - _ => break (current, additional_types), - }, - TypeInfo::Union { left, right, pipe } => match left.as_ref() { - TypeInfo::Tuple { types, .. } if types.len() != 1 => { - self.work_stack - .push(ConvertWork::MakeUnionType { operator: pipe }); - additional_types.push(right.as_ref()); - - break (left, additional_types); - } - TypeInfo::GenericPack { .. } => { - // if we get a generic pack here then we are - // not making a function type - self.work_stack - .push(ConvertWork::MakeUnionType { operator: pipe }); - additional_types.push(right.as_ref()); - - break (left, additional_types); - } - _ => break (current, additional_types), - }, - _ => break (current, additional_types), - } - } - } } -fn is_argument_variadic(mut r#type: &ast::types::TypeInfo) -> bool { - use ast::types::TypeInfo; +fn is_argument_variadic(mut r#type: &ast::luau::TypeInfo) -> bool { + use ast::luau::TypeInfo; loop { match r#type { TypeInfo::GenericPack { .. } | TypeInfo::Variadic { .. } | TypeInfo::VariadicPack { .. } => break true, - TypeInfo::Intersection { left, .. } - | TypeInfo::Union { left, .. } - | TypeInfo::Optional { base: left, .. } => { - r#type = left; + TypeInfo::Optional { base, .. } => { + r#type = base; + } + TypeInfo::Intersection(intersection) => { + r#type = intersection + .types() + .first() + .expect("intersection should have at least one type") + .value(); + } + TypeInfo::Union(union_type) => { + r#type = union_type + .types() + .first() + .expect("union should have at least one type") + .value(); } _ => break false, } } } -fn is_variadic_type(mut r#type: &ast::types::TypeInfo) -> Option<&tokenizer::TokenReference> { - use ast::types::TypeInfo; +fn is_variadic_type(mut r#type: &ast::luau::TypeInfo) -> Option<&tokenizer::TokenReference> { + use ast::luau::TypeInfo; loop { match r#type { - TypeInfo::Variadic { ellipse, .. } | TypeInfo::VariadicPack { ellipse, .. } => { - break Some(ellipse) + TypeInfo::Variadic { ellipsis, .. } | TypeInfo::VariadicPack { ellipsis, .. } => { + break Some(ellipsis) } - TypeInfo::Intersection { left, .. } - | TypeInfo::Union { left, .. } - | TypeInfo::Optional { base: left, .. } => { + TypeInfo::Optional { base: left, .. } => { r#type = left; } + TypeInfo::Intersection(intersection) => { + r#type = intersection + .types() + .first() + .expect("at least one type") + .value(); + } + TypeInfo::Union(union_type) => { + r#type = union_type + .types() + .first() + .expect("at least one type") + .value(); + } _ => break None, } } @@ -2782,7 +2783,7 @@ enum ConvertWork<'a> { Expression(&'a ast::Expression), Prefix(&'a ast::Prefix), Arguments(&'a ast::FunctionArgs), - TypeInfo(&'a ast::types::TypeInfo), + TypeInfo(&'a ast::luau::TypeInfo), PushExpression(Expression), PushVariable(Variable), PushType(Type), @@ -2805,7 +2806,7 @@ enum ConvertWork<'a> { contained_span: &'a ast::span::ContainedSpan, }, MakeIfExpression { - if_expression: &'a ast::types::IfExpression, + if_expression: &'a ast::luau::IfExpression, }, MakeFunctionExpression { body: &'a ast::FunctionBody, @@ -2833,7 +2834,7 @@ enum ConvertWork<'a> { call: &'a ast::FunctionCall, }, MakeTypeDeclarationStatement { - type_declaration: &'a ast::types::TypeDeclaration, + type_declaration: &'a ast::luau::TypeDeclaration, export_token: Option<&'a tokenizer::TokenReference>, }, MakePrefixFromExpression { @@ -2849,7 +2850,7 @@ enum ConvertWork<'a> { statement: &'a ast::Assignment, }, MakeCompoundAssignStatement { - statement: &'a ast::types::CompoundAssignment, + statement: &'a ast::luau::CompoundAssignment, }, MakeIfStatement { statement: &'a ast::If, @@ -2871,13 +2872,13 @@ enum ConvertWork<'a> { variable: &'a ast::VarExpression, }, MakeInterpolatedString { - interpolated_string: &'a ast::types::InterpolatedString, + interpolated_string: &'a ast::luau::InterpolatedString, }, MakeFunctionReturnType { - type_info: &'a ast::types::TypeInfo, + type_info: &'a ast::luau::TypeInfo, }, MakeVariadicTypePack { - ellipse: &'a tokenizer::TokenReference, + ellipsis: &'a tokenizer::TokenReference, }, MakeArrayType { braces: &'a ast::span::ContainedSpan, @@ -2886,23 +2887,27 @@ enum ConvertWork<'a> { question_mark: &'a tokenizer::TokenReference, }, MakeUnionType { - operator: &'a tokenizer::TokenReference, + length: usize, + leading_token: Option<&'a tokenizer::TokenReference>, + separators: &'a ast::punctuated::Punctuated, }, MakeIntersectionType { - operator: &'a tokenizer::TokenReference, + length: usize, + leading_token: Option<&'a tokenizer::TokenReference>, + separators: &'a ast::punctuated::Punctuated, }, MakeTableType { braces: &'a ast::span::ContainedSpan, - fields: &'a ast::punctuated::Punctuated, + fields: &'a ast::punctuated::Punctuated, }, MakeExpressionType { typeof_token: &'a tokenizer::TokenReference, parentheses: &'a ast::span::ContainedSpan, }, MakeFunctionType { - generics: &'a Option, + generics: &'a Option, parentheses: &'a ast::span::ContainedSpan, - arguments: &'a ast::punctuated::Punctuated, + arguments: &'a ast::punctuated::Punctuated, arrow: &'a tokenizer::TokenReference, }, MakeGenericType { @@ -2911,17 +2916,17 @@ enum ConvertWork<'a> { }, MakeTypeParameters { arrows: &'a ast::span::ContainedSpan, - generics: &'a ast::punctuated::Punctuated, + generics: &'a ast::punctuated::Punctuated, }, MakeTypeCast { - type_assertion: &'a ast::types::TypeAssertion, + type_assertion: &'a ast::luau::TypeAssertion, }, MakeParentheseType { parentheses: &'a ast::span::ContainedSpan, }, MakeTypePack { parentheses: &'a ast::span::ContainedSpan, - types: &'a ast::punctuated::Punctuated, + types: &'a ast::punctuated::Punctuated, }, } @@ -2961,8 +2966,8 @@ impl<'a> From<&'a ast::FunctionArgs> for ConvertWork<'a> { } } -impl<'a> From<&'a ast::types::TypeInfo> for ConvertWork<'a> { - fn from(type_info: &'a ast::types::TypeInfo) -> Self { +impl<'a> From<&'a ast::luau::TypeInfo> for ConvertWork<'a> { + fn from(type_info: &'a ast::luau::TypeInfo) -> Self { ConvertWork::TypeInfo(type_info) } } @@ -3035,6 +3040,9 @@ pub(crate) enum ConvertError { }, UnexpectedTrivia(tokenizer::TokenKind), ExpectedFunctionName, + TokenPositionNotFound { + token: String, + }, InternalStack { kind: &'static str, }, @@ -3081,7 +3089,14 @@ impl fmt::Display for ConvertError { ); } ConvertError::ExpectedFunctionName => { - return write!(f, "unable to convert empty function name",); + return write!(f, "unable to convert empty function name"); + } + ConvertError::TokenPositionNotFound { token } => { + return write!( + f, + "unable to convert token '{}' because its position is missing", + token + ); } ConvertError::InternalStack { kind } => { return write!( @@ -3137,18 +3152,16 @@ fn get_unary_operator_token( } fn get_compound_operator_token( - operator: &ast::types::CompoundOp, + operator: &ast::luau::CompoundOp, ) -> Result<&tokenizer::TokenReference, ConvertError> { - use ast::types::CompoundOp; + use ast::luau::CompoundOp; match operator { CompoundOp::PlusEqual(token) | CompoundOp::MinusEqual(token) | CompoundOp::StarEqual(token) | CompoundOp::SlashEqual(token) - // todo: once full-moon fixes this issue and the change is in a new release - // https://github.com/Kampfkarren/full-moon/issues/292 - // | CompoundOp::DoubleSlashEqual(token) + | CompoundOp::DoubleSlashEqual(token) | CompoundOp::PercentEqual(token) | CompoundOp::CaretEqual(token) | CompoundOp::TwoDotsEqual(token) => Ok(token), diff --git a/src/generator/dense.rs b/src/generator/dense.rs index 4a21187a..571429de 100644 --- a/src/generator/dense.rs +++ b/src/generator/dense.rs @@ -1018,7 +1018,24 @@ impl LuaGenerator for DenseLuaGenerator { } nodes::TableEntryType::Indexer(indexer) => { self.push_char('['); - self.write_type(indexer.get_key_type()); + + let key_type = indexer.get_key_type(); + + let need_parentheses = matches!( + key_type, + nodes::Type::Optional(_) + | nodes::Type::Intersection(_) + | nodes::Type::Union(_) + ); + + if need_parentheses { + self.push_char('('); + self.write_type(key_type); + self.push_char(')'); + } else { + self.write_type(key_type); + } + self.push_char(']'); self.push_char(':'); self.write_type(indexer.get_value_type()); @@ -1085,38 +1102,46 @@ impl LuaGenerator for DenseLuaGenerator { } fn write_intersection_type(&mut self, intersection: &nodes::IntersectionType) { - let left = intersection.get_left(); - if nodes::IntersectionType::left_needs_parentheses(left) { - self.write_type_in_parentheses(left); - } else { - self.write_type(left); - } + let length = intersection.len(); + let last_index = length.saturating_sub(1); + for (i, r#type) in intersection.iter_types().enumerate() { + if i != 0 || intersection.has_leading_token() { + self.push_char('&'); + } - self.push_char('&'); + let need_parentheses = if i == last_index { + nodes::IntersectionType::last_needs_parentheses(r#type) + } else { + nodes::IntersectionType::intermediate_needs_parentheses(r#type) + }; - let right = intersection.get_right(); - if nodes::IntersectionType::right_needs_parentheses(right) { - self.write_type_in_parentheses(right); - } else { - self.write_type(right); + if need_parentheses { + self.write_type_in_parentheses(r#type); + } else { + self.write_type(r#type); + } } } fn write_union_type(&mut self, union: &nodes::UnionType) { - let left = union.get_left(); - if nodes::UnionType::left_needs_parentheses(left) { - self.write_type_in_parentheses(left); - } else { - self.write_type(left); - } + let length = union.len(); + let last_index = length.saturating_sub(1); + for (i, r#type) in union.iter_types().enumerate() { + if i != 0 || union.has_leading_token() { + self.push_char('|'); + } - self.push_char('|'); + let need_parentheses = if i == last_index { + nodes::UnionType::last_needs_parentheses(r#type) + } else { + nodes::UnionType::intermediate_needs_parentheses(r#type) + }; - let right = union.get_right(); - if nodes::UnionType::right_needs_parentheses(right) { - self.write_type_in_parentheses(right); - } else { - self.write_type(right); + if need_parentheses { + self.write_type_in_parentheses(r#type); + } else { + self.write_type(r#type); + } } } diff --git a/src/generator/mod.rs b/src/generator/mod.rs index 15086027..5157b693 100644 --- a/src/generator/mod.rs +++ b/src/generator/mod.rs @@ -842,6 +842,20 @@ mod $mod_name { .with_new_branch(false, Block::default()), )); + snapshot_node!($mod_name, $generator, intersection_type, write_intersection_type => ( + single_type => IntersectionType::from(vec![Type::from(true)]), + two_types => IntersectionType::from(vec![Type::from(true), Type::from(false)]), + two_types_with_leading_token => IntersectionType::from(vec![Type::from(true), Type::from(false)]) + .with_leading_token(), + )); + + snapshot_node!($mod_name, $generator, union_type, write_union_type => ( + single_type => UnionType::from(vec![Type::from(true)]), + two_types => UnionType::from(vec![Type::from(true), Type::from(false)]), + two_types_with_leading_token => UnionType::from(vec![Type::from(true), Type::from(false)]) + .with_leading_token(), + )); + snapshot_node!($mod_name, $generator, local_assign, write_statement => ( foo_unassigned => LocalAssignStatement::from_variable("foo"), foo_typed_unassigned => LocalAssignStatement::from_variable( diff --git a/src/generator/readable.rs b/src/generator/readable.rs index 664d474e..5dd4c7b5 100644 --- a/src/generator/readable.rs +++ b/src/generator/readable.rs @@ -1260,7 +1260,24 @@ impl LuaGenerator for ReadableLuaGenerator { } nodes::TableEntryType::Indexer(indexer) => { self.push_char('['); - self.write_type(indexer.get_key_type()); + + let key_type = indexer.get_key_type(); + + let need_parentheses = matches!( + key_type, + nodes::Type::Optional(_) + | nodes::Type::Intersection(_) + | nodes::Type::Union(_) + ); + + if need_parentheses { + self.push_char('('); + self.write_type(key_type); + self.push_char(')'); + } else { + self.write_type(key_type); + } + self.push_char(']'); self.push_char(':'); self.push_char(' '); @@ -1331,38 +1348,59 @@ impl LuaGenerator for ReadableLuaGenerator { } fn write_intersection_type(&mut self, intersection: &nodes::IntersectionType) { - let left = intersection.get_left(); - if nodes::IntersectionType::left_needs_parentheses(left) { - self.write_type_in_parentheses(left); - } else { - self.write_type(left); + if intersection.has_leading_token() { + self.push_char('&'); } - self.push_char('&'); + let length = intersection.len(); + let last_index = length.saturating_sub(1); - let right = intersection.get_right(); - if nodes::IntersectionType::right_needs_parentheses(right) { - self.write_type_in_parentheses(right); - } else { - self.write_type(right); + for (i, r#type) in intersection.iter_types().enumerate() { + if i != 0 { + self.push_char('&'); + } + + let need_parentheses = if i == last_index { + nodes::IntersectionType::last_needs_parentheses(r#type) + } else { + nodes::IntersectionType::intermediate_needs_parentheses(r#type) + }; + + if need_parentheses { + self.write_type_in_parentheses(r#type); + } else { + self.write_type(r#type); + } } } fn write_union_type(&mut self, union: &nodes::UnionType) { - let left = union.get_left(); - if nodes::UnionType::left_needs_parentheses(left) { - self.write_type_in_parentheses(left); - } else { - self.write_type(left); + let length = union.len(); + let last_index = length.saturating_sub(1); + + if union.has_leading_token() { + self.push_char('|'); + self.push_space(); } - self.push_char('|'); + for (i, r#type) in union.iter_types().enumerate() { + if i != 0 { + self.push_space(); + self.push_char('|'); + self.push_space(); + } - let right = union.get_right(); - if nodes::UnionType::right_needs_parentheses(right) { - self.write_type_in_parentheses(right); - } else { - self.write_type(right); + let need_parentheses = if i == last_index { + nodes::UnionType::last_needs_parentheses(r#type) + } else { + nodes::UnionType::intermediate_needs_parentheses(r#type) + }; + + if need_parentheses { + self.write_type_in_parentheses(r#type); + } else { + self.write_type(r#type); + } } } diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__intersection_type__dense_intersection_type_single_type.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__intersection_type__dense_intersection_type_single_type.snap new file mode 100644 index 00000000..7e49956e --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__intersection_type__dense_intersection_type_single_type.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +&true diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__intersection_type__dense_intersection_type_two_types.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__intersection_type__dense_intersection_type_two_types.snap new file mode 100644 index 00000000..1723fff9 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__intersection_type__dense_intersection_type_two_types.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +true&false diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__intersection_type__dense_intersection_type_two_types_with_leading_token.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__intersection_type__dense_intersection_type_two_types_with_leading_token.snap new file mode 100644 index 00000000..e642ad0b --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__intersection_type__dense_intersection_type_two_types_with_leading_token.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +&true&false diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__union_type__dense_union_type_single_type.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__union_type__dense_union_type_single_type.snap new file mode 100644 index 00000000..b5359d4d --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__union_type__dense_union_type_single_type.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +|true diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__union_type__dense_union_type_two_types.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__union_type__dense_union_type_two_types.snap new file mode 100644 index 00000000..194a9e60 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__union_type__dense_union_type_two_types.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +true|false diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__union_type__dense_union_type_two_types_with_leading_token.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__union_type__dense_union_type_two_types_with_leading_token.snap new file mode 100644 index 00000000..bf023036 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__union_type__dense_union_type_two_types_with_leading_token.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +|true|false diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__function_statement__readable_function_statement_empty_with_variadic_pack_return_type.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__function_statement__readable_function_statement_empty_with_variadic_pack_return_type.snap index f2db83fa..b80e269e 100644 --- a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__function_statement__readable_function_statement_empty_with_variadic_pack_return_type.snap +++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__function_statement__readable_function_statement_empty_with_variadic_pack_return_type.snap @@ -2,4 +2,4 @@ source: src/generator/mod.rs expression: generator.into_string() --- -function fn(): ...(true|nil) end +function fn(): ...(true | nil) end diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__intersection_type__readable_intersection_type_single_type.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__intersection_type__readable_intersection_type_single_type.snap new file mode 100644 index 00000000..7e49956e --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__intersection_type__readable_intersection_type_single_type.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +&true diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__intersection_type__readable_intersection_type_two_types.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__intersection_type__readable_intersection_type_two_types.snap new file mode 100644 index 00000000..1723fff9 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__intersection_type__readable_intersection_type_two_types.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +true&false diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__intersection_type__readable_intersection_type_two_types_with_leading_token.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__intersection_type__readable_intersection_type_two_types_with_leading_token.snap new file mode 100644 index 00000000..e642ad0b --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__intersection_type__readable_intersection_type_two_types_with_leading_token.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +&true&false diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__union_type__readable_union_type_single_type.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__union_type__readable_union_type_single_type.snap new file mode 100644 index 00000000..130c2f01 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__union_type__readable_union_type_single_type.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +| true diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__union_type__readable_union_type_two_types.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__union_type__readable_union_type_two_types.snap new file mode 100644 index 00000000..fc3c6486 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__union_type__readable_union_type_two_types.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +true | false diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__union_type__readable_union_type_two_types_with_leading_token.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__union_type__readable_union_type_two_types_with_leading_token.snap new file mode 100644 index 00000000..6499fe5b --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__union_type__readable_union_type_two_types_with_leading_token.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +| true | false diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__intersection_type__token_based_intersection_type_single_type.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__intersection_type__token_based_intersection_type_single_type.snap new file mode 100644 index 00000000..7e49956e --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__intersection_type__token_based_intersection_type_single_type.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +&true diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__intersection_type__token_based_intersection_type_two_types.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__intersection_type__token_based_intersection_type_two_types.snap new file mode 100644 index 00000000..1723fff9 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__intersection_type__token_based_intersection_type_two_types.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +true&false diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__intersection_type__token_based_intersection_type_two_types_with_leading_token.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__intersection_type__token_based_intersection_type_two_types_with_leading_token.snap new file mode 100644 index 00000000..e642ad0b --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__intersection_type__token_based_intersection_type_two_types_with_leading_token.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +&true&false diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__union_type__token_based_union_type_single_type.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__union_type__token_based_union_type_single_type.snap new file mode 100644 index 00000000..b5359d4d --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__union_type__token_based_union_type_single_type.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +|true diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__union_type__token_based_union_type_two_types.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__union_type__token_based_union_type_two_types.snap new file mode 100644 index 00000000..194a9e60 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__union_type__token_based_union_type_two_types.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +true|false diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__union_type__token_based_union_type_two_types_with_leading_token.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__union_type__token_based_union_type_two_types_with_leading_token.snap new file mode 100644 index 00000000..bf023036 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__union_type__token_based_union_type_two_types_with_leading_token.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +|true|false diff --git a/src/generator/token_based.rs b/src/generator/token_based.rs index bc555245..3d6b86d2 100644 --- a/src/generator/token_based.rs +++ b/src/generator/token_based.rs @@ -841,7 +841,22 @@ impl<'a> TokenBasedLuaGenerator<'a> { tokens: &TableIndexTypeTokens, ) { self.write_token(&tokens.opening_bracket); - self.write_type(indexer_type.get_key_type()); + + let key_type = indexer_type.get_key_type(); + + let need_parentheses = matches!( + key_type, + Type::Optional(_) | Type::Intersection(_) | Type::Union(_) + ); + + if need_parentheses { + self.write_symbol("("); + self.write_type(key_type); + self.write_symbol(")"); + } else { + self.write_type(key_type); + } + self.write_token(&tokens.closing_bracket); self.write_token(&tokens.colon); self.write_type(indexer_type.get_value_type()); @@ -1025,48 +1040,66 @@ impl<'a> TokenBasedLuaGenerator<'a> { fn write_intersection_type_with_token( &mut self, intersection: &IntersectionType, - token: &Token, + tokens: &IntersectionTypeTokens, ) { - let left = intersection.get_left(); - if IntersectionType::left_needs_parentheses(left) { - self.write_symbol("("); - self.write_type(left); - self.write_symbol(")"); - } else { - self.write_type(left); - } + let length = intersection.len(); + let last_index = length.saturating_sub(1); - self.write_token(token); + for (i, r#type) in intersection.iter_types().enumerate() { + if i == 0 { + if let Some(leading) = &tokens.leading_token { + self.write_token(leading); + } + } else if let Some(token) = tokens.separators.get(i.saturating_sub(1)) { + self.write_token(token); + } else { + self.write_symbol("&"); + } - let right = intersection.get_right(); - if IntersectionType::right_needs_parentheses(right) { - self.write_symbol("("); - self.write_type(right); - self.write_symbol(")"); - } else { - self.write_type(right); + let need_parentheses = if i == last_index { + IntersectionType::last_needs_parentheses(r#type) + } else { + IntersectionType::intermediate_needs_parentheses(r#type) + }; + + if need_parentheses { + self.write_symbol("("); + self.write_type(r#type); + self.write_symbol(")"); + } else { + self.write_type(r#type); + } } } - fn write_union_type_with_token(&mut self, union: &UnionType, token: &Token) { - let left = union.get_left(); - if UnionType::left_needs_parentheses(left) { - self.write_symbol("("); - self.write_type(left); - self.write_symbol(")"); - } else { - self.write_type(left); - } + fn write_union_type_with_token(&mut self, union: &UnionType, tokens: &UnionTypeTokens) { + let length = union.len(); + let last_index = length.saturating_sub(1); - self.write_token(token); + for (i, r#type) in union.iter_types().enumerate() { + if i == 0 { + if let Some(leading) = &tokens.leading_token { + self.write_token(leading); + } + } else if let Some(token) = tokens.separators.get(i.saturating_sub(1)) { + self.write_token(token); + } else { + self.write_symbol("|"); + } - let right = union.get_right(); - if UnionType::right_needs_parentheses(right) { - self.write_symbol("("); - self.write_type(right); - self.write_symbol(")"); - } else { - self.write_type(right); + let need_parentheses = if i == last_index { + UnionType::last_needs_parentheses(r#type) + } else { + UnionType::intermediate_needs_parentheses(r#type) + }; + + if need_parentheses { + self.write_symbol("("); + self.write_type(r#type); + self.write_symbol(")"); + } else { + self.write_type(r#type); + } } } @@ -1502,12 +1535,23 @@ impl<'a> TokenBasedLuaGenerator<'a> { Token::from_content("?") } - fn generate_intersection_type_token(&self, _intersection: &IntersectionType) -> Token { - Token::from_content("&") + fn generate_intersection_type_token( + &self, + intersection: &IntersectionType, + ) -> IntersectionTypeTokens { + IntersectionTypeTokens { + leading_token: intersection + .has_leading_token() + .then(|| Token::from_content("&")), + separators: intersect_with_token(Token::from_content("&"), intersection.len()), + } } - fn generate_union_type_token(&self, _union: &UnionType) -> Token { - Token::from_content("|") + fn generate_union_type_token(&self, union: &UnionType) -> UnionTypeTokens { + UnionTypeTokens { + leading_token: union.has_leading_token().then(|| Token::from_content("|")), + separators: intersect_with_token(Token::from_content("|"), union.len()), + } } fn generate_type_pack_tokens(&self, type_pack: &TypePack) -> TypePackTokens { diff --git a/src/nodes/expressions/binary.rs b/src/nodes/expressions/binary.rs index 659beeb2..dca8d7b3 100644 --- a/src/nodes/expressions/binary.rs +++ b/src/nodes/expressions/binary.rs @@ -74,8 +74,12 @@ fn ends_with_type_cast_to_type_name_without_type_parameters(expression: &Express } } } - Type::Intersection(intersection) => current_type = intersection.get_right(), - Type::Union(union) => current_type = union.get_right(), + Type::Intersection(intersection) => { + current_type = intersection.last_type(); + } + Type::Union(union_type) => { + current_type = union_type.last_type(); + } Type::True(_) | Type::False(_) | Type::Nil(_) diff --git a/src/nodes/types/function.rs b/src/nodes/types/function.rs index 8a19c9d8..3ab85327 100644 --- a/src/nodes/types/function.rs +++ b/src/nodes/types/function.rs @@ -81,7 +81,12 @@ pub enum FunctionReturnType { impl> From for FunctionReturnType { fn from(r#type: T) -> Self { - Self::Type(Box::new(r#type.into())) + match r#type.into() { + Type::Parenthese(parenthese) => { + Self::TypePack(TypePack::default().with_type(parenthese.into_inner_type())) + } + other => Self::Type(Box::new(other)), + } } } diff --git a/src/nodes/types/intersection.rs b/src/nodes/types/intersection.rs index 6828fa7d..1ff55756 100644 --- a/src/nodes/types/intersection.rs +++ b/src/nodes/types/intersection.rs @@ -1,68 +1,143 @@ +use std::iter; + use crate::nodes::Token; use super::Type; #[derive(Clone, Debug, PartialEq, Eq)] pub struct IntersectionType { - left_type: Box, - right_type: Box, - token: Option, + types: Vec, + leading_operator: bool, + tokens: Option, } impl IntersectionType { pub fn new(left_type: impl Into, right_type: impl Into) -> Self { Self { - left_type: Box::new(left_type.into()), - right_type: Box::new(right_type.into()), - token: None, + types: vec![left_type.into(), right_type.into()], + leading_operator: false, + tokens: None, } } - pub fn with_token(mut self, token: Token) -> Self { - self.token = Some(token); + pub fn with_type(mut self, r#type: impl Into) -> Self { + self.types.push(r#type.into()); self } + pub fn with_tokens(mut self, tokens: IntersectionTypeTokens) -> Self { + self.set_tokens(tokens); + self + } + + #[inline] + pub fn set_tokens(&mut self, tokens: IntersectionTypeTokens) { + if tokens.leading_token.is_some() { + self.leading_operator = true; + } + self.tokens = Some(tokens); + } + #[inline] - pub fn set_token(&mut self, token: Token) { - self.token = Some(token); + pub fn get_token(&self) -> Option<&IntersectionTypeTokens> { + self.tokens.as_ref() } #[inline] - pub fn get_token(&self) -> Option<&Token> { - self.token.as_ref() + pub(crate) fn len(&self) -> usize { + self.types.len() } #[inline] - pub fn mutate_left(&mut self) -> &mut Type { - &mut self.left_type + pub fn iter_types(&self) -> impl Iterator { + self.types.iter() } #[inline] - pub fn mutate_right(&mut self) -> &mut Type { - &mut self.right_type + pub fn iter_mut_types(&mut self) -> impl Iterator { + self.types.iter_mut() } #[inline] - pub fn get_left(&self) -> &Type { - &self.left_type + pub fn first_type(&self) -> &Type { + self.types.first().unwrap() } #[inline] - pub fn get_right(&self) -> &Type { - &self.right_type + pub fn last_type(&self) -> &Type { + self.types.last().unwrap() + } + + pub fn has_leading_token(&self) -> bool { + self.leading_operator + || self.types.len() < 2 + || self + .tokens + .as_ref() + .map(|tokens| tokens.leading_token.is_some()) + .unwrap_or_default() } - pub fn left_needs_parentheses(r#type: &Type) -> bool { + pub fn with_leading_token(mut self) -> Self { + self.put_leading_token(); + self + } + + pub fn put_leading_token(&mut self) { + self.leading_operator = true; + } + + pub fn remove_leading_token(&mut self) { + self.leading_operator = false; + if let Some(tokens) = &mut self.tokens { + tokens.leading_token.take(); + } + } + + pub fn intermediate_needs_parentheses(r#type: &Type) -> bool { matches!( r#type, Type::Optional(_) | Type::Union(_) | Type::Function(_) | Type::Intersection(_) ) } - pub fn right_needs_parentheses(r#type: &Type) -> bool { - matches!(r#type, Type::Optional(_) | Type::Union(_)) + pub fn last_needs_parentheses(r#type: &Type) -> bool { + matches!( + r#type, + Type::Optional(_) | Type::Union(_) | Type::Function(_) | Type::Intersection(_) + ) + } + + super::impl_token_fns!(iter = [tokens]); +} + +impl From> for IntersectionType { + fn from(types: Vec) -> Self { + assert!(!types.is_empty(), "union types cannot be empty"); + Self { + types, + leading_operator: false, + tokens: None, + } } +} + +impl iter::FromIterator for IntersectionType { + fn from_iter>(iter: I) -> Self { + Self { + types: iter.into_iter().collect(), + leading_operator: false, + tokens: None, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct IntersectionTypeTokens { + pub leading_token: Option, + pub separators: Vec, +} - super::impl_token_fns!(iter = [token]); +impl IntersectionTypeTokens { + super::impl_token_fns!(iter = [leading_token, separators]); } diff --git a/src/nodes/types/optional.rs b/src/nodes/types/optional.rs index 16a2d1c8..22853684 100644 --- a/src/nodes/types/optional.rs +++ b/src/nodes/types/optional.rs @@ -42,13 +42,9 @@ impl OptionalType { } pub fn needs_parentheses(r#type: &Type) -> bool { - // todo: parentheses are not needed for nested optional types but - // a bug in full-moon forces darklua to put parentheses around - // optional types too - // https://github.com/Kampfkarren/full-moon/issues/290 matches!( r#type, - Type::Intersection(_) | Type::Union(_) | Type::Optional(_) | Type::Function(_) + Type::Intersection(_) | Type::Union(_) | Type::Function(_) ) } diff --git a/src/nodes/types/parenthese.rs b/src/nodes/types/parenthese.rs index 9b4a995e..e95c9e83 100644 --- a/src/nodes/types/parenthese.rs +++ b/src/nodes/types/parenthese.rs @@ -21,6 +21,11 @@ impl ParentheseType { &self.inner_type } + #[inline] + pub fn into_inner_type(self) -> Type { + *self.inner_type + } + #[inline] pub fn mutate_inner_type(&mut self) -> &mut Type { &mut self.inner_type diff --git a/src/nodes/types/type_name.rs b/src/nodes/types/type_name.rs index 240a8e14..81a3d76b 100644 --- a/src/nodes/types/type_name.rs +++ b/src/nodes/types/type_name.rs @@ -174,7 +174,12 @@ pub enum TypeParameter { impl> From for TypeParameter { fn from(value: T) -> Self { - Self::Type(value.into()) + match value.into() { + Type::Parenthese(parenthese) => { + Self::TypePack(TypePack::default().with_type(parenthese.into_inner_type())) + } + other => Self::Type(other), + } } } diff --git a/src/nodes/types/union.rs b/src/nodes/types/union.rs index b0c6c6fa..07716219 100644 --- a/src/nodes/types/union.rs +++ b/src/nodes/types/union.rs @@ -1,68 +1,138 @@ +use std::iter; + use crate::nodes::Token; use super::Type; #[derive(Clone, Debug, PartialEq, Eq)] pub struct UnionType { - left_type: Box, - right_type: Box, - token: Option, + types: Vec, + leading_operator: bool, + tokens: Option, } impl UnionType { pub fn new(left_type: impl Into, right_type: impl Into) -> Self { Self { - left_type: Box::new(left_type.into()), - right_type: Box::new(right_type.into()), - token: None, + types: vec![left_type.into(), right_type.into()], + leading_operator: false, + tokens: None, } } - pub fn with_token(mut self, token: Token) -> Self { - self.token = Some(token); + pub fn with_tokens(mut self, tokens: UnionTypeTokens) -> Self { + self.set_tokens(tokens); self } #[inline] - pub fn set_token(&mut self, token: Token) { - self.token = Some(token); + pub fn set_tokens(&mut self, tokens: UnionTypeTokens) { + if tokens.leading_token.is_some() { + self.leading_operator = true; + } + self.tokens = Some(tokens); + } + + #[inline] + pub fn get_token(&self) -> Option<&UnionTypeTokens> { + self.tokens.as_ref() } #[inline] - pub fn get_token(&self) -> Option<&Token> { - self.token.as_ref() + pub(crate) fn len(&self) -> usize { + self.types.len() } #[inline] - pub fn mutate_left(&mut self) -> &mut Type { - &mut self.left_type + pub fn iter_types(&self) -> impl Iterator { + self.types.iter() } #[inline] - pub fn mutate_right(&mut self) -> &mut Type { - &mut self.right_type + pub fn first_type(&self) -> &Type { + self.types.first().unwrap() } #[inline] - pub fn get_left(&self) -> &Type { - &self.left_type + pub fn last_type(&self) -> &Type { + self.types.last().unwrap() } #[inline] - pub fn get_right(&self) -> &Type { - &self.right_type + pub fn iter_mut_types(&mut self) -> impl Iterator { + self.types.iter_mut() } - pub fn left_needs_parentheses(r#type: &Type) -> bool { + pub fn has_leading_token(&self) -> bool { + self.leading_operator + || self.types.len() < 2 + || self + .tokens + .as_ref() + .map(|tokens| tokens.leading_token.is_some()) + .unwrap_or_default() + } + + pub fn with_leading_token(mut self) -> Self { + self.put_leading_token(); + self + } + + pub fn put_leading_token(&mut self) { + self.leading_operator = true; + } + + pub fn remove_leading_token(&mut self) { + self.leading_operator = false; + if let Some(tokens) = &mut self.tokens { + tokens.leading_token.take(); + } + } + + pub fn intermediate_needs_parentheses(r#type: &Type) -> bool { matches!( r#type, Type::Optional(_) | Type::Intersection(_) | Type::Function(_) | Type::Union(_) ) } - pub fn right_needs_parentheses(r#type: &Type) -> bool { - matches!(r#type, Type::Intersection(_)) + pub fn last_needs_parentheses(r#type: &Type) -> bool { + matches!( + r#type, + Type::Intersection(_) | Type::Function(_) | Type::Union(_) + ) + } + + super::impl_token_fns!(iter = [tokens]); +} + +impl From> for UnionType { + fn from(types: Vec) -> Self { + assert!(!types.is_empty(), "union types cannot be empty"); + Self { + types, + leading_operator: false, + tokens: None, + } + } +} + +impl iter::FromIterator for UnionType { + fn from_iter>(iter: I) -> Self { + Self { + types: iter.into_iter().collect(), + leading_operator: false, + tokens: None, + } } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct UnionTypeTokens { + pub leading_token: Option, + pub separators: Vec, +} - super::impl_token_fns!(iter = [token]); +impl UnionTypeTokens { + super::impl_token_fns!(iter = [leading_token, separators]); } diff --git a/src/parser.rs b/src/parser.rs index e07aa0d0..b932c78a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -50,7 +50,7 @@ impl Parser { #[derive(Clone, Debug)] enum ParserErrorKind { - Parsing(full_moon::Error), + Parsing(Vec), Converting(ConvertError), } @@ -60,7 +60,7 @@ pub struct ParserError { } impl ParserError { - fn parsing(err: full_moon::Error) -> Self { + fn parsing(err: Vec) -> Self { Self { kind: ParserErrorKind::Parsing(err).into(), } @@ -76,7 +76,12 @@ impl ParserError { impl fmt::Display for ParserError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &*self.kind { - ParserErrorKind::Parsing(err) => write!(f, "{}", err), + ParserErrorKind::Parsing(errors) => { + for err in errors { + writeln!(f, "{}", err)?; + } + Ok(()) + } ParserErrorKind::Converting(err) => write!(f, "{}", err), } } @@ -498,13 +503,11 @@ mod test { Variable::new("var"), Expression::identifier("amount"), ), - // todo: once full-moon fixes this issue and the change is in a new release - // https://github.com/Kampfkarren/full-moon/issues/292 - // compound_floor_division("var //= divider") => CompoundAssignStatement::new( - // CompoundOperator::Plus, - // Variable::new("var"), - // Expression::identifier("divider"), - // ), + compound_floor_division("var //= divider") => CompoundAssignStatement::new( + CompoundOperator::DoubleSlash, + Variable::new("var"), + Expression::identifier("divider"), + ), ); mod parse_with_tokens { @@ -989,13 +992,19 @@ mod test { UnionType::new( StringType::from_value("").with_token(token_at_first_line(19, 21)), Type::True(Some(token_at_first_line(22, 26))) - ).with_token(token_at_first_line(21, 22)) + ).with_tokens(UnionTypeTokens { + leading_token: None, + separators: vec![token_at_first_line(21, 22)], + }) ) .with_tokens(ParentheseTypeTokens { left_parenthese: token_at_first_line(18, 19), right_parenthese: token_at_first_line(26, 27), }) - ).with_token(token_at_first_line(17, 18)) + ).with_tokens(IntersectionTypeTokens { + leading_token: None, + separators: vec![token_at_first_line(17, 18)], + }) ).with_token(spaced_token(11, 13)) ).with_tokens(ReturnTokens { r#return: spaced_token(0, 6), @@ -1028,7 +1037,10 @@ mod test { commas: vec![token_at_first_line(23, 24)], }), TypeName::new(create_identifier("T", 41, 0)), - ).with_token(token_at_first_line(40, 41)) + ).with_tokens(IntersectionTypeTokens { + leading_token: None, + separators: vec![token_at_first_line(40, 41)], + }) ).with_token(spaced_token(11, 13)) ).with_tokens(ReturnTokens { r#return: spaced_token(0, 6), @@ -1932,6 +1944,24 @@ mod test { variable_commas: Vec::new(), value_commas: Vec::new(), }), + local_assignment_intersection_typed_with_no_values("local var : &string") => LocalAssignStatement::from_variable( + create_identifier("var", 6, 1) + .with_type( + IntersectionType::from(vec![ + TypeName::new(create_identifier("string", 13, 0)).into(), + ]) + .with_tokens(IntersectionTypeTokens { + leading_token: Some(token_at_first_line(12, 13)), + separators: Vec::new(), + }) + ) + .with_colon_token(spaced_token(10, 11)), + ).with_tokens(LocalAssignTokens { + local: spaced_token(0, 5), + equal: None, + variable_commas: Vec::new(), + value_commas: Vec::new(), + }), multiple_local_assignment_with_no_values("local foo, bar") => LocalAssignStatement::from_variable( create_identifier("foo", 6, 0) ) @@ -2132,7 +2162,10 @@ mod test { TypeName::new(create_identifier("boolean", 9, 1)), Type::Nil(Some(token_at_first_line(19, 22))) ) - .with_token(spaced_token(17, 18)) + .with_tokens(UnionTypeTokens { + leading_token: None, + separators: vec![spaced_token(17, 18)] + }) ).with_tokens(TypeDeclarationTokens { r#type: spaced_token(0, 4), equal: spaced_token(7, 8), @@ -2144,7 +2177,10 @@ mod test { TypeName::new(create_identifier("U", 9, 1)), TypeName::new(create_identifier("V", 13, 0)), ) - .with_token(spaced_token(11, 12)) + .with_tokens(IntersectionTypeTokens { + leading_token: None, + separators: vec![spaced_token(11, 12)] + }) ).with_tokens(TypeDeclarationTokens { r#type: spaced_token(0, 4), equal: spaced_token(7, 8), @@ -2154,13 +2190,17 @@ mod test { create_identifier("T", 5, 1), IntersectionType::new( TypeName::new(create_identifier("U", 9, 1)), - IntersectionType::new( - TypeName::new(create_identifier("V", 13, 1)), - TypeName::new(create_identifier("W", 17, 0)), - ) - .with_token(spaced_token(15, 16)) + TypeName::new(create_identifier("V", 13, 1)), + ).with_type( + TypeName::new(create_identifier("W", 17, 0)), ) - .with_token(spaced_token(11, 12)) + .with_tokens(IntersectionTypeTokens { + leading_token: None, + separators: vec![ + spaced_token(11, 12), + spaced_token(15, 16), + ] + }) ).with_tokens(TypeDeclarationTokens { r#type: spaced_token(0, 4), equal: spaced_token(7, 8), @@ -2442,7 +2482,10 @@ mod test { commas: Vec::new(), }), TypeName::new(create_identifier("string", 20, 0)) - ).with_token(spaced_token(18, 19)) + ).with_tokens(IntersectionTypeTokens { + leading_token: None, + separators: vec![spaced_token(18, 19)] + }) ).with_tokens(TypeDeclarationTokens { r#type: spaced_token(0, 4), equal: spaced_token(7, 8), @@ -2465,7 +2508,10 @@ mod test { commas: Vec::new(), }), TypeName::new(create_identifier("string", 20, 0)) - ).with_token(spaced_token(18, 19)) + ).with_tokens(UnionTypeTokens { + leading_token: None, + separators: vec![spaced_token(18, 19)] + }) ).with_tokens(TypeDeclarationTokens { r#type: spaced_token(0, 4), equal: spaced_token(7, 8), @@ -2490,11 +2536,16 @@ mod test { FunctionType::new( IntersectionType::new( TypeName::new(create_identifier("A", 15, 1)), - IntersectionType::new( - TypeName::new(create_identifier("B", 19, 1)), - TypeName::new(create_identifier("C", 23, 0)), - ).with_token(spaced_token(21, 22)) - ).with_token(spaced_token(17, 18)) + TypeName::new(create_identifier("B", 19, 1)), + ) + .with_type(TypeName::new(create_identifier("C", 23, 0))) + .with_tokens(IntersectionTypeTokens { + leading_token: None, + separators: vec![ + spaced_token(17, 18), + spaced_token(21, 22), + ] + }), ) .with_tokens(FunctionTypeTokens { opening_parenthese: token_at_first_line(9, 10), @@ -2602,7 +2653,10 @@ mod test { IntersectionType::new( TypeName::new(create_identifier("string", 15, 1)), TypeName::new(create_identifier("T", 24, 0)), - ).with_token(spaced_token(22, 23)) + ).with_tokens(IntersectionTypeTokens { + leading_token: None, + separators: vec![spaced_token(22, 23)] + }) ) .with_tokens(FunctionTypeTokens { opening_parenthese: token_at_first_line(9, 10), @@ -2621,7 +2675,10 @@ mod test { UnionType::new( TypeName::new(create_identifier("string", 15, 1)), TypeName::new(create_identifier("T", 24, 0)), - ).with_token(spaced_token(22, 23)) + ).with_tokens(UnionTypeTokens { + leading_token: None, + separators: vec![spaced_token(22, 23)] + }) ) .with_tokens(FunctionTypeTokens { opening_parenthese: token_at_first_line(9, 10), @@ -2641,7 +2698,10 @@ mod test { IntersectionType::new( TypeName::new(create_identifier("string", 18, 1)), TypeName::new(create_identifier("T", 27, 0)), - ).with_token(spaced_token(25, 26)) + ).with_tokens(IntersectionTypeTokens { + leading_token: None, + separators: vec![spaced_token(25, 26)] + }) ) .with_token(token_at_first_line(15, 18)) ) @@ -2663,7 +2723,10 @@ mod test { UnionType::new( TypeName::new(create_identifier("string", 18, 1)), TypeName::new(create_identifier("boolean", 27, 0)), - ).with_token(spaced_token(25, 26)) + ).with_tokens(UnionTypeTokens { + leading_token: None, + separators: vec![spaced_token(25, 26)] + }) ) .with_token(token_at_first_line(15, 18)) ) @@ -2854,7 +2917,10 @@ mod test { IntersectionType::new( TypeName::new(create_identifier("U", 18, 1)), TypeName::new(create_identifier("V", 22, 0)), - ).with_token(spaced_token(20, 21)) + ).with_tokens(IntersectionTypeTokens { + leading_token: None, + separators: vec![spaced_token(20, 21)] + }) ) .with_generic_parameters( GenericParametersWithDefaults::from_type_variable( @@ -2929,7 +2995,10 @@ mod test { UnionType::new( TypeName::new(create_identifier("A", 21, 1)), TypeName::new(create_identifier("B", 25, 0)), - ).with_token(spaced_token(23, 24)) + ).with_tokens(UnionTypeTokens { + leading_token: None, + separators: vec![spaced_token(23, 24)] + }) ) .with_generic_parameters( GenericParametersWithDefaults::from_type_variable( @@ -3220,29 +3289,28 @@ mod test { equal: spaced_token(7, 8), export: None, }), - // todo: once https://github.com/Kampfkarren/full-moon/issues/285 is fixed, re-enable this test - // type_declaration_to_generic_type_with_variadic_string_literal_type_pack("type T = Fn<...'ok'>") => TypeDeclarationStatement::new( - // create_identifier("T", 5, 1), - // TypeName::new(create_identifier("Fn", 9, 0)) - // .with_type_parameters( - // TypeParameters::new( - // VariadicTypePack::new( - // StringType::from_value("ok").with_token(token_at_first_line(15, 19)) - // ) - // .with_token(token_at_first_line(12, 15)) - // ) - // .with_tokens(TypeParametersTokens { - // opening_list: token_at_first_line(11, 12), - // closing_list: token_at_first_line(19, 20), - // commas: Vec::new(), - // }) - // ) - // ) - // .with_tokens(TypeDeclarationTokens { - // r#type: spaced_token(0, 4), - // equal: spaced_token(7, 8), - // export: None, - // }), + type_declaration_to_generic_type_with_variadic_string_literal_type_pack("type T = Fn<...'ok'>") => TypeDeclarationStatement::new( + create_identifier("T", 5, 1), + TypeName::new(create_identifier("Fn", 9, 0)) + .with_type_parameters( + TypeParameters::new( + VariadicTypePack::new( + StringType::from_value("ok").with_token(token_at_first_line(15, 19)) + ) + .with_token(token_at_first_line(12, 15)) + ) + .with_tokens(TypeParametersTokens { + opening_list: token_at_first_line(11, 12), + closing_list: token_at_first_line(19, 20), + commas: Vec::new(), + }) + ) + ) + .with_tokens(TypeDeclarationTokens { + r#type: spaced_token(0, 4), + equal: spaced_token(7, 8), + export: None, + }), type_declaration_to_generic_type_in_namespace("type T = M.Array") => TypeDeclarationStatement::new( create_identifier("T", 5, 1), TypeField::new( diff --git a/src/process/evaluator/mod.rs b/src/process/evaluator/mod.rs index c87ba95f..3db16417 100644 --- a/src/process/evaluator/mod.rs +++ b/src/process/evaluator/mod.rs @@ -664,12 +664,12 @@ mod test { BinaryOperator::Slash, Expression::from(1.0), Expression::from(0.0) - ) => LuaValue::Number(std::f64::INFINITY), + ) => LuaValue::Number(f64::INFINITY), zero_divided_by_zero( BinaryOperator::Slash, Expression::from(0.0), Expression::from(0.0) - ) => LuaValue::Number(std::f64::NAN), + ) => LuaValue::Number(f64::NAN), twelve_floor_division_by_four( BinaryOperator::DoubleSlash, Expression::from(12.0), @@ -684,17 +684,17 @@ mod test { BinaryOperator::DoubleSlash, Expression::from(1.0), Expression::from(0.0) - ) => LuaValue::Number(std::f64::INFINITY), + ) => LuaValue::Number(f64::INFINITY), minus_one_floor_division_by_zero( BinaryOperator::DoubleSlash, Expression::from(-1.0), Expression::from(0.0) - ) => LuaValue::Number(std::f64::NEG_INFINITY), + ) => LuaValue::Number(f64::NEG_INFINITY), zero_floor_division_by_zero( BinaryOperator::DoubleSlash, Expression::from(0.0), Expression::from(0.0) - ) => LuaValue::Number(std::f64::NAN), + ) => LuaValue::Number(f64::NAN), five_mod_two( BinaryOperator::Percent, Expression::from(5.0), diff --git a/src/process/visitors.rs b/src/process/visitors.rs index a6f69792..a831c231 100644 --- a/src/process/visitors.rs +++ b/src/process/visitors.rs @@ -476,14 +476,16 @@ pub trait NodeVisitor { Type::Intersection(intersection) => { processor.process_intersection_type(intersection); - Self::visit_type(intersection.mutate_left(), processor); - Self::visit_type(intersection.mutate_right(), processor); + for r#type in intersection.iter_mut_types() { + Self::visit_type(r#type, processor); + } } Type::Union(union) => { processor.process_union_type(union); - Self::visit_type(union.mutate_left(), processor); - Self::visit_type(union.mutate_right(), processor); + for r#type in union.iter_mut_types() { + Self::visit_type(r#type, processor); + } } Type::String(string) => { processor.process_string_type(string); diff --git a/src/rules/bundle/path_require_mode/module_definitions.rs b/src/rules/bundle/path_require_mode/module_definitions.rs index 8c529e4a..b8fc815a 100644 --- a/src/rules/bundle/path_require_mode/module_definitions.rs +++ b/src/rules/bundle/path_require_mode/module_definitions.rs @@ -374,8 +374,8 @@ fn last_type_token(r#type: &Type) -> Option<&Token> { } }, Type::Optional(optional) => optional.get_token(), - Type::Intersection(intersection) => last_type_token(intersection.get_right()), - Type::Union(union) => last_type_token(union.get_right()), + Type::Intersection(intersection) => last_type_token(intersection.last_type()), + Type::Union(union_type) => last_type_token(union_type.last_type()), } } diff --git a/tests/ast_fuzzer/fuzzer_work.rs b/tests/ast_fuzzer/fuzzer_work.rs index 67c161a5..e3517eda 100644 --- a/tests/ast_fuzzer/fuzzer_work.rs +++ b/tests/ast_fuzzer/fuzzer_work.rs @@ -91,8 +91,14 @@ pub enum AstFuzzerWork { MakeParenthesePrefix, MakeIndexPrefix, MakeCallPrefix, - MakeIntersectionType, - MakeUnionType, + MakeIntersectionType { + has_leading_token: bool, + length: usize, + }, + MakeUnionType { + has_leading_token: bool, + length: usize, + }, MakeOptionalType, MakeParentheseType, MakeArrayType, diff --git a/tests/ast_fuzzer/mod.rs b/tests/ast_fuzzer/mod.rs index d73da747..b1fe19fb 100644 --- a/tests/ast_fuzzer/mod.rs +++ b/tests/ast_fuzzer/mod.rs @@ -8,7 +8,7 @@ use fuzzer_work::*; use rand::seq::SliceRandom; use random::RandomAst; -use std::iter; +use std::iter::{self, FromIterator}; use darklua_core::nodes::*; @@ -333,11 +333,10 @@ impl AstFuzzer { ) } TypeParameterWithDefaultKind::GenericPackWithVariadicPack => { - let r#type = self.pop_type(); GenericParametersWithDefaults::from_generic_type_pack_with_default( GenericTypePackWithDefault::new( GenericTypePack::new(self.random.identifier()), - self.variadic_type_pack(r#type), + self.pop_variadic_type_pack(), ), ) } @@ -375,11 +374,10 @@ impl AstFuzzer { ); } TypeParameterWithDefaultKind::GenericPackWithVariadicPack => { - let r#type = self.pop_type(); parameter_list.push_generic_type_pack_with_default( GenericTypePackWithDefault::new( GenericTypePack::new(identifier), - self.variadic_type_pack(r#type), + self.pop_variadic_type_pack(), ), ); } @@ -706,16 +704,29 @@ impl AstFuzzer { }; match self.random.full_range(start, bound) { 0 => { - self.push_work(AstFuzzerWork::MakeIntersectionType); - self.budget.try_take_types(2); - self.fuzz_nested_type(depth); - self.fuzz_nested_type(depth); + let length = self + .budget + .try_take_types(self.random.intersection_type_length()) + .max(1); + self.push_work(AstFuzzerWork::MakeIntersectionType { + has_leading_token: length == 1 + || self.random.leading_intersection_or_union_operator(), + length, + }); + self.fuzz_multiple_nested_type(depth, length); } 1 => { - self.push_work(AstFuzzerWork::MakeUnionType); + let length = self + .budget + .try_take_types(self.random.union_type_length()) + .max(1); + self.push_work(AstFuzzerWork::MakeUnionType { + has_leading_token: length == 1 + || self.random.leading_intersection_or_union_operator(), + length, + }); self.budget.try_take_types(2); - self.fuzz_nested_type(depth); - self.fuzz_nested_type(depth); + self.fuzz_multiple_nested_type(depth, length); } 2 => { @@ -806,10 +817,7 @@ impl AstFuzzer { } 8 => { if self.random.has_type_parameters() && self.budget.has_types() { - // todo: once full-moon supports empty type parameter lists - // the max(1) can be removed - // https://github.com/Kampfkarren/full-moon/issues/275 - let type_parameters = self.random.type_parameters().max(1); + let type_parameters = self.random.type_parameters(); self.push_work(AstFuzzerWork::MakeTypeField { type_parameters }); self.push_repeated_work( @@ -868,8 +876,15 @@ impl AstFuzzer { .collect(); if has_indexer { + let mut key_type = self.pop_type(); + if matches!( + key_type, + Type::Optional(_) | Type::Union(_) | Type::Intersection(_) + ) { + key_type = ParentheseType::new(key_type).into(); + } table_properties - .push(TableIndexerType::new(self.pop_type(), self.pop_type()).into()); + .push(TableIndexerType::new(key_type, self.pop_type()).into()); } table_properties.shuffle(&mut rand::thread_rng()); @@ -1316,35 +1331,59 @@ impl AstFuzzer { let call = self.pop_call(); self.prefixes.push(call.into()); } - AstFuzzerWork::MakeIntersectionType => { - let mut left_type = self.pop_type(); - let mut right_type = self.pop_type(); - - if IntersectionType::left_needs_parentheses(&left_type) { - left_type = left_type.in_parentheses(); - } + AstFuzzerWork::MakeIntersectionType { + has_leading_token, + length, + } => { + let mut intersection = IntersectionType::from_iter( + self.pop_types(length) + .into_iter() + .enumerate() + .map(|(i, inner_type)| { + let needs_parentheses = if i == length.saturating_sub(1) { + IntersectionType::last_needs_parentheses(&inner_type) + } else { + IntersectionType::intermediate_needs_parentheses(&inner_type) + }; + if needs_parentheses { + inner_type.in_parentheses() + } else { + inner_type + } + }), + ); - if IntersectionType::right_needs_parentheses(&right_type) { - right_type = right_type.in_parentheses(); + if has_leading_token { + intersection.put_leading_token(); } - self.types - .push(IntersectionType::new(left_type, right_type).into()); + self.types.push(intersection.into()); } - AstFuzzerWork::MakeUnionType => { - let mut left_type = self.pop_type(); - let mut right_type = self.pop_type(); - - if UnionType::left_needs_parentheses(&left_type) { - left_type = left_type.in_parentheses(); - } + AstFuzzerWork::MakeUnionType { + has_leading_token, + length, + } => { + let mut union_type = + UnionType::from_iter(self.pop_types(length).into_iter().enumerate().map( + |(i, inner_type)| { + let needs_parentheses = if i == length.saturating_sub(1) { + UnionType::last_needs_parentheses(&inner_type) + } else { + UnionType::intermediate_needs_parentheses(&inner_type) + }; + if needs_parentheses { + inner_type.in_parentheses() + } else { + inner_type + } + }, + )); - if UnionType::right_needs_parentheses(&right_type) { - right_type = right_type.in_parentheses(); + if has_leading_token { + union_type.put_leading_token(); } - self.types - .push(UnionType::new(left_type, right_type).into()); + self.types.push(union_type.into()); } AstFuzzerWork::MakeOptionalType => { let r#type = self.pop_type(); @@ -1382,8 +1421,7 @@ impl AstFuzzer { self.function_return_types.push(type_pack.into()); } AstFuzzerWork::MakeReturnFunctionVariadicPack => { - let r#type = self.pop_type(); - let variadic_type = self.variadic_type_pack(r#type); + let variadic_type = self.pop_variadic_type_pack(); self.function_return_types.push(variadic_type.into()); } AstFuzzerWork::MakeTypePack { @@ -1399,8 +1437,7 @@ impl AstFuzzer { type_pack.set_variadic_type(generic_pack); } VariadicArgumentTypeKind::VariadicPack => { - let r#type = self.pop_type(); - type_pack.set_variadic_type(self.variadic_type_pack(r#type)); + type_pack.set_variadic_type(self.pop_variadic_type_pack()); } } @@ -1440,8 +1477,7 @@ impl AstFuzzer { function_type.set_variadic_type(generic_pack); } VariadicArgumentTypeKind::VariadicPack => { - let r#type = self.pop_type(); - function_type.set_variadic_type(self.variadic_type_pack(r#type)); + function_type.set_variadic_type(self.pop_variadic_type_pack()); } } @@ -1485,10 +1521,7 @@ impl AstFuzzer { } } TypeParameterKind::TypePack => self.pop_type_pack().into(), - TypeParameterKind::VariadicTypePack => { - let r#type = self.pop_type(); - self.variadic_type_pack(r#type).into() - } + TypeParameterKind::VariadicTypePack => self.pop_variadic_type_pack().into(), TypeParameterKind::GenericTypePack => { GenericTypePack::new(self.random.identifier()).into() } @@ -1518,6 +1551,15 @@ impl AstFuzzer { } } + fn pop_variadic_type_pack(&mut self) -> VariadicTypePack { + // fix: once full-moon supports leading operators for union and + // intersection type, simply replace with `self.pop_type()` + // https://github.com/Kampfkarren/full-moon/issues/311 + VariadicTypePack::new(wrap_in_parenthese_if_leading_union_or_intersection( + self.pop_type(), + )) + } + fn generate_function_generics(&mut self) -> Option { let generic_types_count = self.random.function_generic_types(); if generic_types_count > 0 { @@ -1613,7 +1655,17 @@ impl AstFuzzer { } fn pop_type_parameter(&mut self) -> TypeParameter { - self.type_parameters.pop().expect("expected type parameter") + // fix: once full-moon supports leading operators for union and + // intersection type, simply replace with `self.type_parameters.pop()` + // https://github.com/Kampfkarren/full-moon/issues/311 + let parameter = self.type_parameters.pop().expect("expected type parameter"); + + match parameter { + TypeParameter::Type(r#type) => { + wrap_in_parenthese_if_leading_union_or_intersection(r#type).into() + } + parameter => parameter, + } } fn pop_type_parameters(&mut self, n: usize) -> Vec { @@ -1647,9 +1699,20 @@ impl AstFuzzer { } fn pop_return_type(&mut self) -> FunctionReturnType { - self.function_return_types + let return_type = self + .function_return_types .pop() - .expect("expected function return type") + .expect("expected function return type"); + + // fix: once full-moon supports leading operators for union and + // intersection type, simply replace with `self.function_return_types.pop()` + // https://github.com/Kampfkarren/full-moon/issues/311 + match return_type { + FunctionReturnType::Type(r#type) => FunctionReturnType::from( + wrap_in_parenthese_if_leading_union_or_intersection(*r#type), + ), + return_type => return_type, + } } fn pop_type_pack(&mut self) -> TypePack { @@ -1702,21 +1765,24 @@ impl AstFuzzer { amount, ); } +} - #[inline] - fn variadic_type_pack(&self, r#type: Type) -> VariadicTypePack { - // todo: full-moons has a bug where it does not allow variadic type packs - // in some situations. Remove this function once this issue is fixed - // https://github.com/Kampfkarren/full-moon/issues/285 - match &r#type { - Type::Name(name) => { - if name.has_type_parameters() { - VariadicTypePack::new(TypeName::new(name.get_type_name().clone())) - } else { - VariadicTypePack::new(r#type) - } +fn wrap_in_parenthese_if_leading_union_or_intersection(r#type: Type) -> Type { + match r#type { + Type::Intersection(intersection_type) => { + if intersection_type.has_leading_token() { + Type::from(intersection_type).in_parentheses() + } else { + intersection_type.into() + } + } + Type::Union(union_type) => { + if union_type.has_leading_token() { + Type::from(union_type).in_parentheses() + } else { + union_type.into() } - _ => VariadicTypePack::new(TypeName::new(self.random.identifier())), } + r#type => r#type, } } diff --git a/tests/ast_fuzzer/random.rs b/tests/ast_fuzzer/random.rs index c09cf278..78004c85 100644 --- a/tests/ast_fuzzer/random.rs +++ b/tests/ast_fuzzer/random.rs @@ -24,6 +24,10 @@ pub struct RandomAst { interpolated_string_segments_mean: f64, interpolated_string_segments_std_def: f64, interpolated_segment_is_expression_prob: f64, + intersection_type_length_mean: f64, + intersection_type_length_std_dev: f64, + union_type_length_mean: f64, + union_type_length_std_dev: f64, } impl Default for RandomAst { @@ -49,6 +53,10 @@ impl Default for RandomAst { interpolated_string_segments_mean: 1.5, interpolated_string_segments_std_def: 2.5, interpolated_segment_is_expression_prob: 0.5, + intersection_type_length_mean: 2.0, + intersection_type_length_std_dev: 0.5, + union_type_length_mean: 2.0, + union_type_length_std_dev: 0.5, } } } @@ -150,6 +158,17 @@ impl RandomAst { normal_sample(self.return_length_mean, self.return_length_std_dev) } + pub fn intersection_type_length(&self) -> usize { + normal_sample( + self.intersection_type_length_mean, + self.intersection_type_length_std_dev, + ) + } + + pub fn union_type_length(&self) -> usize { + normal_sample(self.union_type_length_mean, self.union_type_length_std_dev) + } + pub fn numeric_for_step(&self) -> bool { thread_rng().gen_bool(self.numeric_for_step_prob) } @@ -217,9 +236,7 @@ impl RandomAst { 1 => CompoundOperator::Minus, 2 => CompoundOperator::Asterisk, 3 => CompoundOperator::Slash, - // todo: once full-moon fixes this issue and the change is in a new release - // https://github.com/Kampfkarren/full-moon/issues/292 - // 4 => CompoundOperator::DoubleSlash, + 4 => CompoundOperator::DoubleSlash, 5 => CompoundOperator::Percent, 6 => CompoundOperator::Caret, _ => CompoundOperator::Concat, @@ -297,6 +314,10 @@ impl RandomAst { pub fn function_variadic_type_is_generic_pack(&self) -> bool { thread_rng().gen_bool(0.2) } + + pub fn leading_intersection_or_union_operator(&self) -> bool { + thread_rng().gen_bool(0.4) + } } #[inline] diff --git a/tests/bundle.rs b/tests/bundle.rs index cf25c6d5..27dde32c 100644 --- a/tests/bundle.rs +++ b/tests/bundle.rs @@ -528,13 +528,11 @@ data: }); result - .map_err(|err| { + .inspect_err(|_err| { std::fs::write("fuzz_bundle_failure.repro.lua", block_file).unwrap(); let out = resources.get("out.lua").unwrap(); std::fs::write("fuzz_bundle_failure.lua", out).unwrap(); - - err }) .unwrap(); }) diff --git a/tests/fuzz_generator.rs b/tests/fuzz_generator.rs index ed456d00..49a1dfca 100644 --- a/tests/fuzz_generator.rs +++ b/tests/fuzz_generator.rs @@ -280,21 +280,19 @@ macro_rules! generate_fuzz_tests { }); } - // todo: re-enable these tests once full-moon parser re-write has merged - // because full-moon throws stack overflow errors - // #[test] - // fn fuzz_medium_block() { - // run_for_minimum_time(|| { - // fuzz_test_block!(FuzzBudget::new(100, 200), generator()); - // }); - // } - - // #[test] - // fn fuzz_large_block() { - // run_for_minimum_time(|| { - // fuzz_test_block!(FuzzBudget::new(200, 200), generator()); - // }); - // } + #[test] + fn fuzz_medium_block() { + run_for_minimum_time(|| { + fuzz_test_block!(FuzzBudget::new(100, 200), generator()); + }); + } + + #[test] + fn fuzz_large_block() { + run_for_minimum_time(|| { + fuzz_test_block!(FuzzBudget::new(200, 200), generator()); + }); + } $( $extra )* } diff --git a/tests/snapshots/bundle__without_rules__require_lua_file_with_parser_error.snap b/tests/snapshots/bundle__without_rules__require_lua_file_with_parser_error.snap index f8001923..8caddc88 100644 --- a/tests/snapshots/bundle__without_rules__require_lua_file_with_parser_error.snap +++ b/tests/snapshots/bundle__without_rules__require_lua_file_with_parser_error.snap @@ -3,5 +3,5 @@ source: tests/bundle.rs expression: "error_display.join(\"\\n\")" --- error processing `src/main.lua` (bundler): -unable to parse `src/value.lua`: error occurred while creating ast: unexpected token `returnone`. (starting from line 1, character 1 and ending on line 1, character 10) -additional information: leftover token +unable to parse `src/value.lua`: error occurred while creating ast: unexpected token ``. (starting from line 1, character 10 and ending on line 1, character 10) +additional information: unexpected expression when looking for a statement