diff --git a/CHANGELOG.md b/CHANGELOG.md index 906e29bc..1cf85df1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* add support for type functions ([#333](https://github.com/seaofvoices/darklua/pull/333)) * add support for property modifiers in table types (like `read` in a type like `{ read name: string }`) ([#332](https://github.com/seaofvoices/darklua/pull/332)) ## 0.17.3 diff --git a/src/ast_converter.rs b/src/ast_converter.rs index 9a1ae8fe..cc4ce89b 100644 --- a/src/ast_converter.rs +++ b/src/ast_converter.rs @@ -852,6 +852,36 @@ impl<'a> AstConverter<'a> { .into(), ); } + ConvertWork::MakeTypeFunctionStatement { + statement, + export_token, + } => { + let builder = self.convert_function_body_attributes( + statement.function_body(), + self.convert_token(statement.function_token())?, + )?; + + let mut name = Identifier::new(statement.function_name().token().to_string()); + let mut type_token = None; + let mut export = None; + + if self.hold_token_data { + name.set_token(self.convert_token(statement.function_name())?); + type_token = Some(self.convert_token(statement.type_token())?); + export = export_token + .map(|token| self.convert_token(token)) + .transpose()?; + } + + let mut type_function_statement = + builder.into_type_function_statement(name, type_token, export); + + if export_token.is_some() { + type_function_statement.set_exported(); + } + + self.statements.push(type_function_statement.into()); + } ConvertWork::MakeLocalAssignStatement { statement } => { let variables = statement .names() @@ -1547,6 +1577,22 @@ impl<'a> AstConverter<'a> { }); self.push_function_body_work(local_function.body()); } + ast::Stmt::TypeFunction(type_function) => { + self.work_stack + .push(ConvertWork::MakeTypeFunctionStatement { + statement: type_function, + export_token: None, + }); + self.push_function_body_work(type_function.function_body()); + } + ast::Stmt::ExportedTypeFunction(type_function) => { + self.work_stack + .push(ConvertWork::MakeTypeFunctionStatement { + statement: type_function.type_function(), + export_token: Some(type_function.export_token()), + }); + self.push_function_body_work(type_function.type_function().function_body()); + } ast::Stmt::NumericFor(numeric_for) => { self.work_stack.push(ConvertWork::MakeNumericForStatement { statement: numeric_for, @@ -2866,6 +2912,10 @@ enum ConvertWork<'a> { type_declaration: &'a ast::luau::TypeDeclaration, export_token: Option<&'a tokenizer::TokenReference>, }, + MakeTypeFunctionStatement { + statement: &'a ast::luau::TypeFunction, + export_token: Option<&'a tokenizer::TokenReference>, + }, MakePrefixFromExpression { prefix: &'a ast::Prefix, }, diff --git a/src/generator/dense.rs b/src/generator/dense.rs index f0d48ef4..972455d0 100644 --- a/src/generator/dense.rs +++ b/src/generator/dense.rs @@ -637,6 +637,42 @@ impl LuaGenerator for DenseLuaGenerator { self.write_type(statement.get_type()); } + fn write_type_function_statement(&mut self, function: &nodes::TypeFunctionStatement) { + if function.is_exported() { + self.push_str("export"); + } + self.push_str("type"); + self.push_str("function"); + + self.write_identifier(function.get_identifier()); + + if let Some(generics) = function.get_generic_parameters() { + self.write_function_generics(generics); + } + + self.push_char('('); + + let parameters = function.get_parameters(); + self.write_function_parameters( + parameters, + function.is_variadic(), + function.get_variadic_type(), + ); + self.push_char(')'); + + if let Some(return_type) = function.get_return_type() { + self.push_char(':'); + self.write_function_return_type(return_type); + } + + let block = function.get_block(); + + if !block.is_empty() { + self.write_block(block); + } + self.push_str("end"); + } + fn write_false_expression(&mut self, _token: &Option) { self.push_str("false"); } diff --git a/src/generator/mod.rs b/src/generator/mod.rs index 31383483..ca5b4f71 100644 --- a/src/generator/mod.rs +++ b/src/generator/mod.rs @@ -37,6 +37,7 @@ pub trait LuaGenerator { Repeat(statement) => self.write_repeat_statement(statement), While(statement) => self.write_while_statement(statement), TypeDeclaration(statement) => self.write_type_declaration_statement(statement), + TypeFunction(statement) => self.write_type_function_statement(statement), } } @@ -53,6 +54,7 @@ pub trait LuaGenerator { fn write_repeat_statement(&mut self, repeat: &nodes::RepeatStatement); fn write_while_statement(&mut self, while_statement: &nodes::WhileStatement); fn write_type_declaration_statement(&mut self, statement: &nodes::TypeDeclarationStatement); + fn write_type_function_statement(&mut self, statement: &nodes::TypeFunctionStatement); fn write_variable(&mut self, variable: &nodes::Variable) { use nodes::Variable::*; @@ -883,6 +885,18 @@ mod $mod_name { ), )); + snapshot_node!($mod_name, $generator, type_function, write_type_function_statement => ( + empty => TypeFunctionStatement::from_name("nothing", Block::default()), + empty_exported => TypeFunctionStatement::from_name("nothing", Block::default()) + .export(), + empty_with_parameter => TypeFunctionStatement::from_name("nothing", Block::default()) + .with_parameter("param"), + empty_with_parameters_and_return_type => TypeFunctionStatement::from_name("nothing", Block::default()) + .with_parameter("param") + .with_return_type(Type::from(true)), + )); + + snapshot_node!($mod_name, $generator, if_statement, write_statement => ( empty => IfStatement::create(false, Block::default()), empty_with_empty_else => IfStatement::create(false, Block::default()) diff --git a/src/generator/readable.rs b/src/generator/readable.rs index c7e9a6fc..8ae67c66 100644 --- a/src/generator/readable.rs +++ b/src/generator/readable.rs @@ -16,6 +16,7 @@ enum StatementType { Repeat, While, TypeDeclaration, + TypeFunction, Return, Break, Continue, @@ -38,6 +39,7 @@ impl From<&nodes::Statement> for StatementType { Repeat(_) => Self::Repeat, While(_) => Self::While, TypeDeclaration(_) => Self::TypeDeclaration, + TypeFunction(_) => Self::TypeFunction, } } } @@ -871,6 +873,45 @@ impl LuaGenerator for ReadableLuaGenerator { self.write_type(statement.get_type()); } + fn write_type_function_statement(&mut self, function: &nodes::TypeFunctionStatement) { + self.push_can_add_new_line(false); + if function.is_exported() { + self.push_str("export"); + } + self.push_str("type function "); + self.write_identifier(function.get_identifier()); + + if let Some(generics) = function.get_generic_parameters() { + self.write_function_generics(generics); + } + + self.raw_push_char('('); + + self.pop_can_add_new_line(); + + let parameters = function.get_parameters(); + self.write_function_parameters( + parameters, + function.is_variadic(), + function.get_variadic_type(), + ); + self.raw_push_char(')'); + + if let Some(return_type) = function.get_return_type() { + self.write_function_return_type_suffix(return_type); + } + + let block = function.get_block(); + + if block.is_empty() { + self.raw_push_str(" end"); + } else { + self.push_new_line(); + self.indent_and_write_block(block); + self.push_str("end"); + } + } + fn write_false_expression(&mut self, _token: &Option) { self.push_str("false"); } diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_function__dense_type_function_empty.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_function__dense_type_function_empty.snap new file mode 100644 index 00000000..c1e8f27a --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_function__dense_type_function_empty.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +type function nothing()end diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_function__dense_type_function_empty_exported.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_function__dense_type_function_empty_exported.snap new file mode 100644 index 00000000..c40377be --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_function__dense_type_function_empty_exported.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +export type function nothing()end diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_function__dense_type_function_empty_with_parameter.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_function__dense_type_function_empty_with_parameter.snap new file mode 100644 index 00000000..a4276097 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_function__dense_type_function_empty_with_parameter.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +type function nothing(param)end diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_function__dense_type_function_empty_with_parameters_and_return_type.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_function__dense_type_function_empty_with_parameters_and_return_type.snap new file mode 100644 index 00000000..e38445fe --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_function__dense_type_function_empty_with_parameters_and_return_type.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +type function nothing(param):true end diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_function__readable_type_function_empty.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_function__readable_type_function_empty.snap new file mode 100644 index 00000000..ffa1bd07 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_function__readable_type_function_empty.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +type function nothing() end diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_function__readable_type_function_empty_exported.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_function__readable_type_function_empty_exported.snap new file mode 100644 index 00000000..f0d79748 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_function__readable_type_function_empty_exported.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +export type function nothing() end diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_function__readable_type_function_empty_with_parameter.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_function__readable_type_function_empty_with_parameter.snap new file mode 100644 index 00000000..acb690fb --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_function__readable_type_function_empty_with_parameter.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +type function nothing(param) end diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_function__readable_type_function_empty_with_parameters_and_return_type.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_function__readable_type_function_empty_with_parameters_and_return_type.snap new file mode 100644 index 00000000..9f6cf33a --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_function__readable_type_function_empty_with_parameters_and_return_type.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +type function nothing(param): true end diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_function__token_based_type_function_empty.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_function__token_based_type_function_empty.snap new file mode 100644 index 00000000..c1e8f27a --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_function__token_based_type_function_empty.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +type function nothing()end diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_function__token_based_type_function_empty_exported.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_function__token_based_type_function_empty_exported.snap new file mode 100644 index 00000000..c40377be --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_function__token_based_type_function_empty_exported.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +export type function nothing()end diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_function__token_based_type_function_empty_with_parameter.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_function__token_based_type_function_empty_with_parameter.snap new file mode 100644 index 00000000..a4276097 --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_function__token_based_type_function_empty_with_parameter.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +type function nothing(param)end diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_function__token_based_type_function_empty_with_parameters_and_return_type.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_function__token_based_type_function_empty_with_parameters_and_return_type.snap new file mode 100644 index 00000000..e38445fe --- /dev/null +++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_function__token_based_type_function_empty_with_parameters_and_return_type.snap @@ -0,0 +1,5 @@ +--- +source: src/generator/mod.rs +expression: generator.into_string() +--- +type function nothing(param):true end diff --git a/src/generator/token_based.rs b/src/generator/token_based.rs index ecc8e325..96f2f932 100644 --- a/src/generator/token_based.rs +++ b/src/generator/token_based.rs @@ -687,6 +687,35 @@ impl<'a> TokenBasedLuaGenerator<'a> { self.write_type(statement.get_type()); } + fn write_type_function_with_tokens( + &mut self, + function: &TypeFunctionStatement, + tokens: &TypeFunctionStatementTokens, + ) { + if function.is_exported() { + if let Some(export_token) = &tokens.export { + self.write_token(export_token); + } else { + self.write_symbol("export"); + } + } + self.write_token(&tokens.r#type); + self.write_token(&tokens.function_body.function); + + self.write_identifier(function.get_identifier()); + + self.write_function_attributes( + tokens, + function.get_generic_parameters(), + function.parameters_count(), + function.iter_parameters(), + function.is_variadic(), + function.get_variadic_type(), + function.get_return_type(), + function.get_block(), + ); + } + fn write_generic_parameters_with_default_with_tokens( &mut self, generic_parameters: &GenericParametersWithDefaults, @@ -1397,10 +1426,37 @@ impl<'a> TokenBasedLuaGenerator<'a> { TypeDeclarationTokens { r#type: Token::from_content("type"), equal: Token::from_content("="), - export: if statement.is_exported() { - Some(Token::from_content("export")) - } else { - None + export: statement + .is_exported() + .then(|| Token::from_content("export")), + } + } + + fn generate_type_function_tokens( + &self, + statement: &TypeFunctionStatement, + ) -> TypeFunctionStatementTokens { + TypeFunctionStatementTokens { + r#type: Token::from_content("type"), + export: statement + .is_exported() + .then(|| Token::from_content("export")), + function_body: FunctionBodyTokens { + function: Token::from_content("function"), + opening_parenthese: Token::from_content("("), + closing_parenthese: Token::from_content(")"), + end: Token::from_content("end"), + parameter_commas: intersect_with_token( + comma_token(), + statement.parameters_count() + usize::from(statement.is_variadic()), + ), + variable_arguments: statement.is_variadic().then(|| Token::from_content("...")), + variable_arguments_colon: statement + .has_variadic_type() + .then(|| Token::from_content(":")), + return_type_colon: statement + .has_return_type() + .then(|| Token::from_content(":")), }, } } @@ -1896,6 +1952,17 @@ impl LuaGenerator for TokenBasedLuaGenerator<'_> { } } + fn write_type_function_statement(&mut self, statement: &TypeFunctionStatement) { + if let Some(tokens) = statement.get_tokens() { + self.write_type_function_with_tokens(statement, tokens); + } else { + self.write_type_function_with_tokens( + statement, + &self.generate_type_function_tokens(statement), + ); + } + } + fn write_false_expression(&mut self, token: &Option) { if let Some(token) = token { self.write_token(token); diff --git a/src/nodes/function_body.rs b/src/nodes/function_body.rs index e86d09c9..5a75bff7 100644 --- a/src/nodes/function_body.rs +++ b/src/nodes/function_body.rs @@ -1,7 +1,8 @@ use super::{ Block, FunctionExpression, FunctionName, FunctionReturnType, FunctionStatement, FunctionVariadicType, GenericParameters, Identifier, LocalFunctionStatement, - LocalFunctionTokens, Token, TypedIdentifier, + LocalFunctionTokens, Token, TypeFunctionStatement, TypeFunctionStatementTokens, + TypedIdentifier, }; pub(crate) struct FunctionBuilder { @@ -169,6 +170,59 @@ impl FunctionBuilder { statement } + pub(crate) fn into_type_function_statement( + self, + name: Identifier, + type_token: Option, + export_token: Option, + ) -> TypeFunctionStatement { + let mut statement = + TypeFunctionStatement::new(name, self.block, self.parameters, self.is_variadic); + + if let Some(variadic_type) = self.variadic_type { + statement.set_variadic_type(variadic_type); + } + + if let Some(return_type) = self.return_type { + statement.set_return_type(return_type); + } + + if let Some(generic_parameters) = self.generic_parameters { + statement.set_generic_parameters(generic_parameters); + } + + if let ( + Some(type_token), + Some(function), + Some(opening_parenthese), + Some(closing_parenthese), + Some(end), + ) = ( + type_token, + self.function, + self.opening_parenthese, + self.closing_parenthese, + self.end, + ) { + statement.set_tokens(TypeFunctionStatementTokens { + r#type: type_token, + export: export_token, + function_body: FunctionBodyTokens { + function, + opening_parenthese, + closing_parenthese, + end, + parameter_commas: self.parameter_commas, + variable_arguments: self.variable_arguments, + variable_arguments_colon: self.variable_arguments_colon, + return_type_colon: self.return_type_colon, + }, + }); + } + + statement + } + pub(crate) fn is_variadic(&self) -> bool { self.is_variadic } diff --git a/src/nodes/statements/mod.rs b/src/nodes/statements/mod.rs index dff67d0f..b4686873 100644 --- a/src/nodes/statements/mod.rs +++ b/src/nodes/statements/mod.rs @@ -10,6 +10,7 @@ mod local_function; mod numeric_for; mod repeat_statement; mod type_declaration; +mod type_function; mod while_statement; pub use assign::*; @@ -24,6 +25,7 @@ pub use local_function::*; pub use numeric_for::*; pub use repeat_statement::*; pub use type_declaration::*; +pub use type_function::*; pub use while_statement::*; use crate::nodes::FunctionCall; @@ -59,6 +61,8 @@ pub enum Statement { While(WhileStatement), /// A type declaration statement (e.g., `type T = string | number`) TypeDeclaration(TypeDeclarationStatement), + /// A type function declaration (e.g., `type function name() ... end`) + TypeFunction(Box), } impl Statement { @@ -78,6 +82,7 @@ impl Statement { Self::Repeat(repeat_stmt) => repeat_stmt.mutate_first_token(), Self::While(while_stmt) => while_stmt.mutate_first_token(), Self::TypeDeclaration(type_decl) => type_decl.mutate_first_token(), + Self::TypeFunction(type_function) => type_function.mutate_first_token(), } } @@ -98,6 +103,7 @@ impl Statement { Self::Repeat(repeat_stmt) => repeat_stmt.mutate_last_token(), Self::While(while_stmt) => while_stmt.mutate_last_token(), Self::TypeDeclaration(type_decl) => type_decl.mutate_last_token(), + Self::TypeFunction(type_function) => type_function.mutate_last_token(), } } } @@ -179,3 +185,9 @@ impl From for Statement { Statement::TypeDeclaration(type_declaration) } } + +impl From for Statement { + fn from(type_function: TypeFunctionStatement) -> Statement { + Statement::TypeFunction(Box::new(type_function)) + } +} diff --git a/src/nodes/statements/type_function.rs b/src/nodes/statements/type_function.rs new file mode 100644 index 00000000..75eb8219 --- /dev/null +++ b/src/nodes/statements/type_function.rs @@ -0,0 +1,322 @@ +use crate::nodes::{ + Block, FunctionBodyTokens, FunctionReturnType, FunctionVariadicType, GenericParameters, + Identifier, Token, TypedIdentifier, +}; + +/// Represents a type function statement. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TypeFunctionStatement { + identifier: Identifier, + block: Block, + parameters: Vec, + is_variadic: bool, + variadic_type: Option, + return_type: Option, + generic_parameters: Option, + exported: bool, + tokens: Option, +} + +impl TypeFunctionStatement { + /// Creates a new type function statement. + pub fn new( + identifier: impl Into, + block: Block, + parameters: Vec, + is_variadic: bool, + ) -> Self { + Self { + identifier: identifier.into(), + block, + parameters, + is_variadic, + variadic_type: None, + return_type: None, + generic_parameters: None, + exported: false, + tokens: None, + } + } + + /// Creates a new type function statement with a given name and block. + pub fn from_name(identifier: impl Into, block: impl Into) -> Self { + Self { + identifier: identifier.into(), + block: block.into(), + parameters: Vec::new(), + is_variadic: false, + variadic_type: None, + return_type: None, + generic_parameters: None, + exported: false, + tokens: None, + } + } + + /// Adds a parameter to this type function. + pub fn with_parameter(mut self, parameter: impl Into) -> Self { + self.parameters.push(parameter.into()); + self + } + + /// Marks this function as variadic. + pub fn variadic(mut self) -> Self { + self.is_variadic = true; + self + } + + /// Returns whether this type function is variadic. + pub fn is_variadic(&self) -> bool { + self.is_variadic + } + + /// Sets the variadic type for this function. + /// + /// If the function is not already variadic, this will make it variadic. + pub fn with_variadic_type(mut self, r#type: impl Into) -> Self { + self.is_variadic = true; + self.variadic_type = Some(r#type.into()); + self + } + + /// Sets the variadic type for this function. + /// + /// If the function is not already variadic, this will make it variadic. + pub fn set_variadic_type(&mut self, r#type: impl Into) { + self.is_variadic = true; + self.variadic_type = Some(r#type.into()); + } + + /// Returns the variadic type, if any. + pub fn get_variadic_type(&self) -> Option<&FunctionVariadicType> { + self.variadic_type.as_ref() + } + + /// Returns whether this function has a variadic type. + pub fn has_variadic_type(&self) -> bool { + self.variadic_type.is_some() + } + + /// Returns a mutable reference to the variadic type, if any. + pub fn mutate_variadic_type(&mut self) -> Option<&mut FunctionVariadicType> { + self.variadic_type.as_mut() + } + + /// Sets the return type for this function. + pub fn with_return_type(mut self, return_type: impl Into) -> Self { + self.return_type = Some(return_type.into()); + self + } + + /// Sets the return type for this function. + pub fn set_return_type(&mut self, return_type: impl Into) { + self.return_type = Some(return_type.into()); + } + + /// Returns the return type, if any. + pub fn get_return_type(&self) -> Option<&FunctionReturnType> { + self.return_type.as_ref() + } + + /// Returns whether this function has a return type. + pub fn has_return_type(&self) -> bool { + self.return_type.is_some() + } + + /// Returns a mutable reference to the return type, if any. + pub fn mutate_return_type(&mut self) -> Option<&mut FunctionReturnType> { + self.return_type.as_mut() + } + + /// Sets the generic parameters for this function. + pub fn with_generic_parameters(mut self, generic_parameters: GenericParameters) -> Self { + self.generic_parameters = Some(generic_parameters); + self + } + + /// Sets the generic parameters for this function. + pub fn set_generic_parameters(&mut self, generic_parameters: GenericParameters) { + self.generic_parameters = Some(generic_parameters); + } + + /// Returns the generic parameters, if any. + pub fn get_generic_parameters(&self) -> Option<&GenericParameters> { + self.generic_parameters.as_ref() + } + + /// Returns a mutable reference to the parameters. + pub fn mutate_parameters(&mut self) -> &mut Vec { + &mut self.parameters + } + + /// Returns a mutable reference to the block. + pub fn mutate_block(&mut self) -> &mut Block { + &mut self.block + } + + /// Returns a mutable reference to the identifier. + pub fn mutate_identifier(&mut self) -> &mut Identifier { + &mut self.identifier + } + + /// Returns the function's block. + pub fn get_block(&self) -> &Block { + &self.block + } + + /// Returns the function's parameters. + pub fn get_parameters(&self) -> &Vec { + &self.parameters + } + + /// Returns the number of parameters. + pub fn parameters_count(&self) -> usize { + self.parameters.len() + } + + /// Returns an iterator over the parameters. + pub fn iter_parameters(&self) -> impl Iterator { + self.parameters.iter() + } + + /// Returns a mutable iterator over the parameters. + pub fn iter_mut_parameters(&mut self) -> impl Iterator { + self.parameters.iter_mut() + } + + /// Returns whether this type function has a parameter with the given name. + pub fn has_parameter(&self, name: &str) -> bool { + self.parameters + .iter() + .any(|parameter| parameter.get_name() == name) + } + + /// Returns whether this type function has parameters. + pub fn has_parameters(&self) -> bool { + !self.parameters.is_empty() + } + + /// Returns the function's identifier. + pub fn get_identifier(&self) -> &Identifier { + &self.identifier + } + + /// Marks this type function statement as exported. + pub fn export(mut self) -> Self { + self.exported = true; + self + } + + /// Marks this type function statement as exported. + pub fn set_exported(&mut self) { + self.exported = true; + } + + /// Removes the exported status from this type function statement. + pub fn remove_exported(&mut self) { + self.exported = false; + if let Some(tokens) = self.tokens.as_mut() { + tokens.export.take(); + } + } + + /// Returns whether this type function is exported. + pub fn is_exported(&self) -> bool { + self.exported + } + + /// Sets the tokens for this type function statement. + pub fn with_tokens(mut self, tokens: TypeFunctionStatementTokens) -> Self { + self.tokens = Some(tokens); + self + } + + /// Sets the tokens for this type function statement. + pub fn set_tokens(&mut self, tokens: TypeFunctionStatementTokens) { + self.tokens = Some(tokens); + } + + /// Returns the tokens for this type function statement, if any. + pub fn get_tokens(&self) -> Option<&TypeFunctionStatementTokens> { + self.tokens.as_ref() + } + + /// Returns a mutable reference to the tokens, if any. + pub fn mutate_tokens(&mut self) -> Option<&mut TypeFunctionStatementTokens> { + self.tokens.as_mut() + } + + /// Returns a mutable reference to the first token for this statement, creating it if missing. + pub fn mutate_first_token(&mut self) -> &mut Token { + self.set_default_tokens(); + let tokens = self.tokens.as_mut().unwrap(); + if self.exported { + if tokens.export.is_none() { + tokens.export = Some(Token::from_content("export")); + } + tokens.export.as_mut().unwrap() + } else { + &mut tokens.r#type + } + } + + /// Returns a mutable reference to the last token for this statement, + /// creating it if missing. + pub fn mutate_last_token(&mut self) -> &mut Token { + self.set_default_tokens(); + &mut self.tokens.as_mut().unwrap().end + } + + fn set_default_tokens(&mut self) { + if self.tokens.is_none() { + self.tokens = Some(TypeFunctionStatementTokens { + r#type: Token::from_content("type"), + export: self.exported.then(|| Token::from_content("export")), + function_body: FunctionBodyTokens { + function: Token::from_content("function"), + opening_parenthese: Token::from_content("("), + closing_parenthese: Token::from_content(")"), + end: Token::from_content("end"), + parameter_commas: Vec::new(), + variable_arguments: None, + variable_arguments_colon: None, + return_type_colon: None, + }, + }); + } + } + + super::impl_token_fns!( + target = [identifier] + iter = [parameters, generic_parameters, tokens] + ); +} + +/// Tokens associated with a type function statement. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TypeFunctionStatementTokens { + pub r#type: Token, + pub function_body: FunctionBodyTokens, + pub export: Option, +} + +impl TypeFunctionStatementTokens { + super::impl_token_fns!( + target = [r#type, function_body] + iter = [export] + ); +} + +impl std::ops::Deref for TypeFunctionStatementTokens { + type Target = FunctionBodyTokens; + + fn deref(&self) -> &Self::Target { + &self.function_body + } +} + +impl std::ops::DerefMut for TypeFunctionStatementTokens { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.function_body + } +} diff --git a/src/parser.rs b/src/parser.rs index 0d75c15b..7e87db32 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -512,6 +512,46 @@ mod test { Variable::new("var"), Expression::identifier("divider"), ), + type_function("type function foo() end") => TypeFunctionStatement::from_name( + "foo", + Block::default(), + ), + type_function_with_2_typed_parameters("type function foo(a: number, b: string) end") + => TypeFunctionStatement::from_name( + Identifier::new("foo"), + Block::default(), + ) + .with_parameter(Identifier::new("a").with_type(TypeName::new("number"))) + .with_parameter(Identifier::new("b").with_type(TypeName::new("string"))), + empty_type_function_variadic("type function foo(...) end") + => TypeFunctionStatement::from_name("foo", Block::default()).variadic(), + empty_type_function_variadic_with_one_parameter("type function foo(a, ...) end") + => TypeFunctionStatement::from_name("foo", Block::default()) + .with_parameter("a") + .variadic(), + type_function_with_2_typed_parameters_and_variadic("type function foo(a: number, b: string, ...) end") + => TypeFunctionStatement::from_name("foo", Block::default()) + .with_parameter(Identifier::new("a").with_type(TypeName::new("number"))) + .with_parameter(Identifier::new("b").with_type(TypeName::new("string"))) + .variadic(), + type_function_variadic_and_return_type("type function foo(...): number end") + => TypeFunctionStatement::from_name("foo", Block::default()) + .variadic() + .with_return_type(TypeName::new("number")), + exported_type_function_optional("export type function optional(t) return types.unionof(t, types.singleton(nil)) end") + => TypeFunctionStatement::from_name( + "optional", + ReturnStatement::one( + FunctionCall::from_prefix(FieldExpression::new(Prefix::from_name("types"), "unionof")) + .with_argument(Expression::identifier("t")) + .with_argument( + FunctionCall::from_prefix(FieldExpression::new(Prefix::from_name("types"), "singleton")) + .with_argument(Expression::nil()) + ) + ) + ) + .with_parameter("t") + .export(), ); mod parse_with_tokens { @@ -3498,6 +3538,50 @@ mod test { equal: spaced_token(7, 8), export: None, }), + type_function_statement_empty("type function nothing() end") => TypeFunctionStatement::from_name( + create_identifier("nothing", 14, 0), + Block::default().with_tokens(BlockTokens { + semicolons: Vec::new(), + last_semicolon: None, + final_token: None, + }), + ).with_tokens(TypeFunctionStatementTokens { + r#type: spaced_token(0, 4), + function_body: FunctionBodyTokens { + function: spaced_token(5, 13), + opening_parenthese: token_at_first_line(21, 22), + closing_parenthese: spaced_token(22, 23), + end: token_at_first_line(24, 27), + parameter_commas: Vec::new(), + variable_arguments: None, + variable_arguments_colon: None, + return_type_colon: None, + }, + export: None, + }), + exported_type_function_statement_empty("export type function nothing() end") => TypeFunctionStatement::from_name( + create_identifier("nothing", 21, 0), + Block::default().with_tokens(BlockTokens { + semicolons: Vec::new(), + last_semicolon: None, + final_token: None, + }), + ) + .export() + .with_tokens(TypeFunctionStatementTokens { + r#type: spaced_token(7, 11), + function_body: FunctionBodyTokens { + function: spaced_token(12, 20), + opening_parenthese: token_at_first_line(28, 29), + closing_parenthese: spaced_token(29, 30), + end: token_at_first_line(31, 34), + parameter_commas: Vec::new(), + variable_arguments: None, + variable_arguments_colon: None, + return_type_colon: None, + }, + export: Some(spaced_token(0, 6)), + }), ); test_parse_block_with_tokens!( diff --git a/src/process/node_processor.rs b/src/process/node_processor.rs index 38c58fb3..3358b9cd 100644 --- a/src/process/node_processor.rs +++ b/src/process/node_processor.rs @@ -22,6 +22,7 @@ pub trait NodeProcessor { fn process_repeat_statement(&mut self, _: &mut RepeatStatement) {} fn process_while_statement(&mut self, _: &mut WhileStatement) {} fn process_type_declaration(&mut self, _: &mut TypeDeclarationStatement) {} + fn process_type_function(&mut self, _: &mut TypeFunctionStatement) {} fn process_variable(&mut self, _: &mut Variable) {} @@ -81,6 +82,7 @@ pub trait NodePostProcessor { fn process_after_repeat_statement(&mut self, _: &mut RepeatStatement) {} fn process_after_while_statement(&mut self, _: &mut WhileStatement) {} fn process_after_type_declaration(&mut self, _: &mut TypeDeclarationStatement) {} + fn process_after_type_function(&mut self, _: &mut TypeFunctionStatement) {} fn process_after_variable(&mut self, _: &mut Variable) {} diff --git a/src/process/post_visitor.rs b/src/process/post_visitor.rs index 0ad578e9..3160f978 100644 --- a/src/process/post_visitor.rs +++ b/src/process/post_visitor.rs @@ -41,6 +41,9 @@ pub trait NodePostVisitor { Statement::TypeDeclaration(statement) => { Self::visit_type_declaration(statement, processor) } + Statement::TypeFunction(statement) => { + Self::visit_type_function_statement(statement, processor) + } }; processor.process_after_statement(statement); } @@ -398,6 +401,28 @@ pub trait NodePostVisitor { processor.process_after_type_declaration(statement); } + fn visit_type_function_statement(statement: &mut TypeFunctionStatement, processor: &mut T) { + processor.process_type_function(statement); + processor.process_scope(statement.mutate_block(), None); + Self::visit_block(statement.mutate_block(), processor); + + for r#type in statement + .iter_mut_parameters() + .filter_map(TypedIdentifier::mutate_type) + { + Self::visit_type(r#type, processor); + } + + if let Some(variadic_type) = statement.mutate_variadic_type() { + Self::visit_function_variadic_type(variadic_type, processor); + } + + if let Some(return_type) = statement.mutate_return_type() { + Self::visit_function_return_type(return_type, processor); + } + processor.process_after_type_function(statement); + } + fn visit_variable(variable: &mut Variable, processor: &mut T) { processor.process_variable(variable); diff --git a/src/process/visitors.rs b/src/process/visitors.rs index 82a83155..3e47b048 100644 --- a/src/process/visitors.rs +++ b/src/process/visitors.rs @@ -38,6 +38,7 @@ pub trait NodeVisitor { Statement::TypeDeclaration(statement) => { Self::visit_type_declaration(statement, processor) } + Statement::TypeFunction(statement) => Self::visit_type_function(statement, processor), }; } @@ -371,6 +372,27 @@ pub trait NodeVisitor { Self::visit_type(statement.mutate_type(), processor); } + fn visit_type_function(statement: &mut TypeFunctionStatement, processor: &mut T) { + processor.process_type_function(statement); + processor.process_scope(statement.mutate_block(), None); + Self::visit_block(statement.mutate_block(), processor); + + for r#type in statement + .iter_mut_parameters() + .filter_map(TypedIdentifier::mutate_type) + { + Self::visit_type(r#type, processor); + } + + if let Some(variadic_type) = statement.mutate_variadic_type() { + Self::visit_function_variadic_type(variadic_type, processor); + } + + if let Some(return_type) = statement.mutate_return_type() { + Self::visit_function_return_type(return_type, processor); + } + } + fn visit_variable(variable: &mut Variable, processor: &mut T) { processor.process_variable(variable); diff --git a/src/rules/bundle/rename_type_declaration.rs b/src/rules/bundle/rename_type_declaration.rs index 4891ca49..7d5dda20 100644 --- a/src/rules/bundle/rename_type_declaration.rs +++ b/src/rules/bundle/rename_type_declaration.rs @@ -100,6 +100,22 @@ impl RenameTypeDeclarationProcessor { declaration.mutate_name().set_name(new_name); } + fn rename_type_function(&mut self, function: &mut TypeFunctionStatement) { + let original_name = function.get_identifier().get_name().to_owned(); + + let new_name = self.generate_unique_type(&original_name); + + if function.is_exported() { + function.remove_exported(); + self.exported_types + .insert(original_name.clone(), new_name.clone()); + } + + self.renamed_types.insert(original_name, new_name.clone()); + + function.mutate_identifier().set_name(new_name); + } + pub(crate) fn get_type_lines(&self) -> isize { self.type_lines } @@ -140,8 +156,14 @@ impl NodeProcessor for RenameTypeDeclarationProcessor { } for statement in block.iter_mut_statements() { - if let Statement::TypeDeclaration(declaration) = statement { - self.rename_type_declaration(declaration); + match statement { + Statement::TypeDeclaration(declaration) => { + self.rename_type_declaration(declaration); + } + Statement::TypeFunction(function) => { + self.rename_type_function(function); + } + _ => (), } } } @@ -188,7 +210,7 @@ impl NodePostProcessor for RenameTypeDeclarationProcessor { } block.filter_mut_statements(|statement| { - if let Statement::TypeDeclaration(_declaration) = statement { + if let Statement::TypeDeclaration(_) | Statement::TypeFunction(_) = statement { let mut current = mem::replace(statement, DoStatement::default().into()); let first_line = lines::statement_first(¤t); diff --git a/src/rules/filter_early_return.rs b/src/rules/filter_early_return.rs index 63fce6b0..8380f759 100644 --- a/src/rules/filter_early_return.rs +++ b/src/rules/filter_early_return.rs @@ -38,7 +38,8 @@ impl Processor { | Statement::NumericFor(_) | Statement::Repeat(_) | Statement::While(_) - | Statement::TypeDeclaration(_) => None, + | Statement::TypeDeclaration(_) + | Statement::TypeFunction(_) => None, }) } } diff --git a/src/rules/remove_comments.rs b/src/rules/remove_comments.rs index fb1ec3aa..e65a1d1e 100644 --- a/src/rules/remove_comments.rs +++ b/src/rules/remove_comments.rs @@ -78,6 +78,10 @@ impl NodeProcessor for RemoveCommentProcessor { type_declaration.clear_comments(); } + fn process_type_function(&mut self, function: &mut TypeFunctionStatement) { + function.clear_comments(); + } + fn process_expression(&mut self, expression: &mut Expression) { match expression { Expression::False(token) @@ -322,6 +326,10 @@ impl NodeProcessor for FilterCommentProcessor<'_> { type_declaration.filter_comments(|trivia| self.ignore_trivia(trivia)); } + fn process_type_function(&mut self, type_function: &mut TypeFunctionStatement) { + type_function.filter_comments(|trivia| self.ignore_trivia(trivia)); + } + fn process_expression(&mut self, expression: &mut Expression) { match expression { Expression::False(token) diff --git a/src/rules/remove_spaces.rs b/src/rules/remove_spaces.rs index afad7900..e8f831fd 100644 --- a/src/rules/remove_spaces.rs +++ b/src/rules/remove_spaces.rs @@ -78,6 +78,10 @@ impl NodeProcessor for RemoveWhitespacesProcessor { type_declaration.clear_whitespaces(); } + fn process_type_function(&mut self, type_function: &mut TypeFunctionStatement) { + type_function.clear_whitespaces(); + } + fn process_expression(&mut self, expression: &mut Expression) { match expression { Expression::False(token) diff --git a/src/rules/remove_types.rs b/src/rules/remove_types.rs index df8e8797..2c8ec0f4 100644 --- a/src/rules/remove_types.rs +++ b/src/rules/remove_types.rs @@ -13,7 +13,12 @@ struct RemoveTypesProcessor { impl NodeProcessor for RemoveTypesProcessor { fn process_block(&mut self, block: &mut Block) { - block.filter_statements(|statement| !matches!(statement, Statement::TypeDeclaration(_))); + block.filter_statements(|statement| { + !matches!( + statement, + Statement::TypeDeclaration(_) | Statement::TypeFunction(_) + ) + }); } fn process_local_assign_statement(&mut self, local_assign: &mut LocalAssignStatement) { diff --git a/src/rules/replace_referenced_tokens.rs b/src/rules/replace_referenced_tokens.rs index fb67f8f6..3cdde9c1 100644 --- a/src/rules/replace_referenced_tokens.rs +++ b/src/rules/replace_referenced_tokens.rs @@ -86,6 +86,10 @@ impl NodeProcessor for Processor<'_> { type_declaration.replace_referenced_tokens(self.code); } + fn process_type_function(&mut self, function: &mut TypeFunctionStatement) { + function.replace_referenced_tokens(self.code); + } + fn process_expression(&mut self, expression: &mut Expression) { match expression { Expression::False(token) diff --git a/src/rules/shift_token_line.rs b/src/rules/shift_token_line.rs index 8c6888af..3ddb311c 100644 --- a/src/rules/shift_token_line.rs +++ b/src/rules/shift_token_line.rs @@ -86,6 +86,10 @@ impl NodeProcessor for ShiftTokenLineProcessor { type_declaration.shift_token_line(self.shift_amount); } + fn process_type_function(&mut self, function: &mut TypeFunctionStatement) { + function.shift_token_line(self.shift_amount); + } + fn process_expression(&mut self, expression: &mut Expression) { match expression { Expression::False(token) diff --git a/src/rules/snapshots/darklua_core__rules__remove_comments__test__remove_comments_in_code.snap b/src/rules/snapshots/darklua_core__rules__remove_comments__test__remove_comments_in_code.snap index 0743f92a..c5c50772 100644 --- a/src/rules/snapshots/darklua_core__rules__remove_comments__test__remove_comments_in_code.snap +++ b/src/rules/snapshots/darklua_core__rules__remove_comments__test__remove_comments_in_code.snap @@ -79,3 +79,15 @@ export type PublicMyT = (MyT) type Opt = Module . OtherType type Try = (...'a' ) -> typeof ( fn() ) + +type function identity_fn(t): () + return t +end + +export type function identity_fn ( ... ) + return (...) +end + +type function example_complex_type_function < T , U, R...>(first: T & U, opts: { [number ]: string }? , ...: R...) : () + return first +end diff --git a/src/rules/snapshots/darklua_core__rules__remove_comments__test__remove_comments_in_code_with_exception.snap b/src/rules/snapshots/darklua_core__rules__remove_comments__test__remove_comments_in_code_with_exception.snap index faf1c977..2bb610ff 100644 --- a/src/rules/snapshots/darklua_core__rules__remove_comments__test__remove_comments_in_code_with_exception.snap +++ b/src/rules/snapshots/darklua_core__rules__remove_comments__test__remove_comments_in_code_with_exception.snap @@ -79,3 +79,15 @@ export --[[ this is exported ]] type PublicMyT = (MyT) type Opt = Module . OtherType type Try = (...'a' ) -> typeof ( fn() ) + +type function identity_fn(t): () + return t +end + +export type function identity_fn ( ... ) + return (...) +end + +type function example_complex_type_function < T , U, R...>(first: T & U, opts: { [number ]: string }? , ...: R...) : () + return first +end diff --git a/src/rules/snapshots/darklua_core__rules__remove_spaces__test__remove_spaces_in_code.snap b/src/rules/snapshots/darklua_core__rules__remove_spaces__test__remove_spaces_in_code.snap index 86139370..9ecc6249 100644 --- a/src/rules/snapshots/darklua_core__rules__remove_spaces__test__remove_spaces_in_code.snap +++ b/src/rules/snapshots/darklua_core__rules__remove_spaces__test__remove_spaces_in_code.snap @@ -79,3 +79,15 @@ export--[[ this is exported ]]type PublicMyT=(MyT) type Opt =Module.OtherType--[[extract a type from a module]] type Try=(...'a'--[[literal type]])->typeof(fn()--[[get the return type]]) + +type function identity_fn(t):()-- end +return t +end + +export--[=[ exported! ]=]type function identity_fn--[[type name]](...) +return(...) +end + +type function example_complex_type_function(first:T&U,opts:{[number--[[index type]] ]:string}?--[[opts]],...:R...):() +return first +end diff --git a/src/utils/lines.rs b/src/utils/lines.rs index 96aef670..98288a1e 100644 --- a/src/utils/lines.rs +++ b/src/utils/lines.rs @@ -83,6 +83,9 @@ fn last_statement_token(statement: &Statement) -> Option<&Token> { Statement::TypeDeclaration(type_declaration) => { last_type_token(type_declaration.get_type()) } + Statement::TypeFunction(type_function) => { + type_function.get_tokens().map(|tokens| &tokens.end) + } } } @@ -206,6 +209,13 @@ fn first_statement_token(statement: &Statement) -> Option<&Token> { } }) } + Statement::TypeFunction(type_function) => type_function.get_tokens().and_then(|tokens| { + if type_function.is_exported() { + tokens.export.as_ref() + } else { + Some(&tokens.r#type) + } + }), } } diff --git a/tests/ast_fuzzer/fuzzer_work.rs b/tests/ast_fuzzer/fuzzer_work.rs index e3517eda..48154df2 100644 --- a/tests/ast_fuzzer/fuzzer_work.rs +++ b/tests/ast_fuzzer/fuzzer_work.rs @@ -128,6 +128,11 @@ pub enum AstFuzzerWork { MakeTypeDeclaration { type_parameter_with_defaults: Vec, }, + MakeTypeFunction { + parameters: usize, + has_return_type: bool, + has_variadic_type: bool, + }, MakeTableType { properties: usize, literal_properties: usize, diff --git a/tests/ast_fuzzer/mod.rs b/tests/ast_fuzzer/mod.rs index 540cc664..8f2458d5 100644 --- a/tests/ast_fuzzer/mod.rs +++ b/tests/ast_fuzzer/mod.rs @@ -104,7 +104,7 @@ impl AstFuzzer { AstFuzzerWork::FuzzStatement => { match self .random - .range(if self.budget.has_types() { 12 } else { 11 }) + .range(if self.budget.has_types() { 13 } else { 11 }) { 0 => { let variables = self.random.assignment_variables(); @@ -237,7 +237,7 @@ impl AstFuzzer { self.budget.take_expression(); self.fuzz_expression(); } - _ => { + 12 => { // take type for declared type self.budget.take_type(); @@ -290,6 +290,20 @@ impl AstFuzzer { } } } + _ => { + // take type for function type + self.budget.take_type(); + + self.generate_function( + |parameters, has_return_type, has_variadic_type| { + AstFuzzerWork::MakeTypeFunction { + parameters, + has_return_type, + has_variadic_type, + } + }, + ); + } } } AstFuzzerWork::MakeTypeDeclaration { @@ -397,6 +411,39 @@ impl AstFuzzer { self.statements.push(type_declaration.into()); } + AstFuzzerWork::MakeTypeFunction { + parameters, + has_return_type, + has_variadic_type, + } => { + let block = self.pop_block(); + let parameters = self.pop_typed_identifiers(parameters); + + let mut type_function = TypeFunctionStatement::new( + self.random.identifier(), + block, + parameters, + has_variadic_type || self.random.function_is_variadic(), + ); + + if let Some(generics) = self.generate_function_generics() { + type_function.set_generic_parameters(generics); + } + + if has_return_type { + type_function.set_return_type(self.pop_return_type()); + } + + if has_variadic_type { + type_function.set_variadic_type(self.pop_type()); + } + + if self.random.export_type_function() { + type_function.set_exported(); + } + + self.statements.push(type_function.into()); + } AstFuzzerWork::FuzzLastStatement => match self.random.range(2) { 0 => { self.last_statements.push(LastStatement::new_break()); diff --git a/tests/ast_fuzzer/random.rs b/tests/ast_fuzzer/random.rs index 902c815c..eff7a524 100644 --- a/tests/ast_fuzzer/random.rs +++ b/tests/ast_fuzzer/random.rs @@ -301,6 +301,10 @@ impl RandomAst { rng().random_bool(0.5) } + pub fn export_type_function(&self) -> bool { + rng().random_bool(0.5) + } + pub fn table_type_indexer(&self) -> bool { rng().random_bool(0.25) } diff --git a/tests/bundle.rs b/tests/bundle.rs index 1b95ee9f..01160c24 100644 --- a/tests/bundle.rs +++ b/tests/bundle.rs @@ -433,6 +433,18 @@ mod without_rules { ); } + #[test] + fn require_lua_file_forward_exported_type_functions() { + process_main( + &memory_resources!( + "src/value.lua" => "export type function Nillable(ty) return types.unionof(ty, types.singleton(nil)) end return true", + "src/main.lua" => "local value = require('./value.lua') export type Nillable = value.Nillable", + ".darklua.json" => DARKLUA_BUNDLE_ONLY_READABLE_CONFIG, + ), + "require_lua_file_forward_exported_type_functions", + ); + } + #[test] fn require_lua_file_forward_exported_types_with_generics() { process_main( diff --git a/tests/rule_tests/remove_types.rs b/tests/rule_tests/remove_types.rs index 25077d15..48cba19d 100644 --- a/tests/rule_tests/remove_types.rs +++ b/tests/rule_tests/remove_types.rs @@ -20,6 +20,8 @@ test_rule!( remove_type_declaration_keep_leading_comment("--!native\ntype T = string | number") => "--!native\n", remove_type_declaration_keep_trailing_comment("type T = string | number\n-- end of file") => "\n-- end of file", remove_exported_type_declaration("export type T = { Node }") => "", + remove_type_function_statement("type function foo(x) return x end") => "", + remove_exported_type_function_statement("export type function foo(x) return x end") => "", remove_type_in_local_assign("local var: boolean = true") => "local var = true", remove_type_in_numeric_for("for i: number=a, b do end") => "for i=a, b do end", remove_types_in_generic_for("for k: string, v: boolean in pairs({}) do end") diff --git a/tests/snapshots/bundle__without_rules__bundle_without_rules_require_lua_file_forward_exported_type_functions.snap b/tests/snapshots/bundle__without_rules__bundle_without_rules_require_lua_file_forward_exported_type_functions.snap new file mode 100644 index 00000000..3e871a0e --- /dev/null +++ b/tests/snapshots/bundle__without_rules__bundle_without_rules_require_lua_file_forward_exported_type_functions.snap @@ -0,0 +1,36 @@ +--- +source: tests/bundle.rs +expression: main +--- +type function Nillable__DARKLUA_TYPE_a(ty) + return types.unionof(ty, types.singleton(nil)) +end + +local __DARKLUA_BUNDLE_MODULES = { + cache = {}::any, +} + +do + do + local function __modImpl() + return true + end + + function __DARKLUA_BUNDLE_MODULES.a(): typeof(__modImpl()) + local v = __DARKLUA_BUNDLE_MODULES.cache.a + + if not v then + v = { + c = __modImpl(), + } + __DARKLUA_BUNDLE_MODULES.cache.a = v + end + + return v.c + end + end +end + +local value = __DARKLUA_BUNDLE_MODULES.a() + +export type Nillable = Nillable__DARKLUA_TYPE_a diff --git a/tests/test_cases/spaces_and_comments.lua b/tests/test_cases/spaces_and_comments.lua index c04a7108..80fd2e2a 100644 --- a/tests/test_cases/spaces_and_comments.lua +++ b/tests/test_cases/spaces_and_comments.lua @@ -75,3 +75,15 @@ export --[[ this is exported ]] type PublicMyT = (MyT) type Opt = Module . OtherType --[[extract a type from a module]] type Try = (... 'a' --[[literal type]] ) -> typeof ( fn() --[[get the return type]] ) + +type function identity_fn(t): () -- end + return t +end + +export --[=[ exported! ]=] type function identity_fn--[[type name]] ( ... ) + return (...) +end + +type function example_complex_type_function < T --[[]], U, R... --[[for variadic ]]>(first: T & U, opts: { [number --[[index type]] ]: string }? --[[opts]], ...: R... ) : () + return first +end