Skip to content

Commit

Permalink
Support bundling with Luau types (#249)
Browse files Browse the repository at this point in the history
Correctly preserve types when bundling Luau

Note that because of how Luau currently works, types that uses `typeof`
(like `type Example = typeof(variable)`) can't be hoisted correctly if
they include a local variable.
  • Loading branch information
jeparlefrancais authored Jan 10, 2025
1 parent d33962c commit a6a8f33
Show file tree
Hide file tree
Showing 25 changed files with 985 additions and 287 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Changelog

* support Luau types when bundling ([#249](https://github.com/seaofvoices/darklua/pull/249))

## 0.15.0

* improve file watching: re-process specific files, sourcemap changes re-process the project, bundling re-starts whenever a dependent file changes ([#239](https://github.com/seaofvoices/darklua/pull/239))
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ durationfmt = "0.1.1"
elsa = "1.10.0"
env_logger = "0.11.5"
full_moon = { version = "1.0.0", features = ["roblox"] }
indexmap = "2.7.0"
json5 = "0.4.1"
log = "0.4.22"
pathdiff = "0.2.3"
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ impl Arguments {
}
}

pub(crate) fn shift_token_line(&mut self, amount: usize) {
pub(crate) fn shift_token_line(&mut self, amount: isize) {
match self {
Arguments::Tuple(tuple) => tuple.shift_token_line(amount),
Arguments::String(_) | Arguments::Table(_) => {}
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/expressions/interpolated_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl InterpolationSegment {
}
}

pub(crate) fn shift_token_line(&mut self, amount: usize) {
pub(crate) fn shift_token_line(&mut self, amount: isize) {
match self {
InterpolationSegment::String(segment) => segment.shift_token_line(amount),
InterpolationSegment::Value(segment) => segment.shift_token_line(amount),
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/expressions/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ impl NumberExpression {
}
}

pub(crate) fn shift_token_line(&mut self, amount: usize) {
pub(crate) fn shift_token_line(&mut self, amount: isize) {
match self {
NumberExpression::Decimal(number) => number.shift_token_line(amount),
NumberExpression::Hex(number) => number.shift_token_line(amount),
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/expressions/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ impl TableEntry {
}
}

pub(crate) fn shift_token_line(&mut self, amount: usize) {
pub(crate) fn shift_token_line(&mut self, amount: isize) {
match self {
TableEntry::Field(entry) => entry.shift_token_line(amount),
TableEntry::Index(entry) => entry.shift_token_line(amount),
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ macro_rules! impl_token_fns {
)*)?
}

pub(crate) fn shift_token_line(&mut self, amount: usize) {
pub(crate) fn shift_token_line(&mut self, amount: isize) {
$(
self.$field.shift_token_line(amount);
)*
Expand Down
10 changes: 9 additions & 1 deletion src/nodes/statements/type_declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ impl TypeDeclarationStatement {
self.exported = true;
}

#[inline]
pub fn remove_exported(&mut self) {
self.exported = false;
if let Some(tokens) = self.tokens.as_mut() {
tokens.export.take();
}
}

#[inline]
pub fn is_exported(&self) -> bool {
self.exported
Expand Down Expand Up @@ -195,7 +203,7 @@ impl TypeDeclarationStatement {
}
}

pub(crate) fn shift_token_line(&mut self, amount: usize) {
pub(crate) fn shift_token_line(&mut self, amount: isize) {
self.name.shift_token_line(amount);
if let Some(tokens) = &mut self.tokens {
tokens.shift_token_line(amount);
Expand Down
6 changes: 4 additions & 2 deletions src/nodes/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,12 @@ impl Token {
}
}

pub(crate) fn shift_token_line(&mut self, amount: usize) {
pub(crate) fn shift_token_line(&mut self, amount: isize) {
match &mut self.position {
Position::LineNumberReference { line_number, .. }
| Position::LineNumber { line_number, .. } => *line_number += amount,
| Position::LineNumber { line_number, .. } => {
*line_number = line_number.saturating_add_signed(amount);
}
Position::Any { .. } => {}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/types/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl TableEntryType {
}
}

pub(crate) fn shift_token_line(&mut self, amount: usize) {
pub(crate) fn shift_token_line(&mut self, amount: isize) {
match self {
TableEntryType::Property(property) => property.shift_token_line(amount),
TableEntryType::Literal(literal) => literal.shift_token_line(amount),
Expand Down
2 changes: 1 addition & 1 deletion src/process/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ pub use node_counter::NodeCounter;
pub use node_processor::{NodePostProcessor, NodeProcessor};
pub use post_visitor::{DefaultPostVisitor, NodePostVisitor};
pub(crate) use scope_visitor::IdentifierTracker;
pub use scope_visitor::{Scope, ScopeVisitor};
pub use scope_visitor::{Scope, ScopePostVisitor, ScopeVisitor};
pub use visitors::{DefaultVisitor, NodeVisitor};
221 changes: 221 additions & 0 deletions src/process/scope_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::process::utils::is_valid_identifier;
use crate::process::{NodeProcessor, NodeVisitor};

use super::utils::{identifier_permutator, Permutator};
use super::{NodePostProcessor, NodePostVisitor};

/// Defines methods to interact with the concept of lexical scoping. The struct implementing this
/// trait should be able to keep track of identifiers when used along the ScopeVisitor.
Expand Down Expand Up @@ -228,6 +229,226 @@ impl<T: NodeProcessor + Scope> NodeVisitor<T> for ScopeVisitor {
}
}

/// A visitor that can be used only with a NodeProcessor that also implements the Scope trait.
pub struct ScopePostVisitor;

impl ScopePostVisitor {
fn visit_block_without_push<T: NodeProcessor + NodePostProcessor + Scope>(
block: &mut Block,
scope: &mut T,
) {
scope.process_block(block);

block
.iter_mut_statements()
.for_each(|statement| Self::visit_statement(statement, scope));

if let Some(last_statement) = block.mutate_last_statement() {
Self::visit_last_statement(last_statement, scope);
};
scope.process_after_block(block);
}
}

impl<T: NodeProcessor + NodePostProcessor + Scope> NodePostVisitor<T> for ScopePostVisitor {
fn visit_block(block: &mut Block, scope: &mut T) {
scope.push();
Self::visit_block_without_push(block, scope);
scope.pop();
}

fn visit_local_assign(statement: &mut LocalAssignStatement, scope: &mut T) {
scope.process_local_assign_statement(statement);

statement
.iter_mut_values()
.for_each(|value| Self::visit_expression(value, scope));

for r#type in statement
.iter_mut_variables()
.filter_map(TypedIdentifier::mutate_type)
{
Self::visit_type(r#type, scope);
}

statement.for_each_assignment(|variable, expression| {
scope.insert_local(variable.mutate_name(), expression)
});

scope.process_after_local_assign_statement(statement);
}

fn visit_function_expression(function: &mut FunctionExpression, scope: &mut T) {
scope.process_function_expression(function);

for r#type in function
.iter_mut_parameters()
.filter_map(TypedIdentifier::mutate_type)
{
Self::visit_type(r#type, scope);
}

if let Some(variadic_type) = function.mutate_variadic_type() {
Self::visit_function_variadic_type(variadic_type, scope);
}

if let Some(return_type) = function.mutate_return_type() {
Self::visit_function_return_type(return_type, scope);
}

scope.push();
function
.mutate_parameters()
.iter_mut()
.for_each(|parameter| scope.insert(parameter.mutate_name()));

scope.process_scope(function.mutate_block(), None);

Self::visit_block(function.mutate_block(), scope);
scope.pop();

scope.process_after_function_expression(function);
}

fn visit_function_statement(statement: &mut FunctionStatement, scope: &mut T) {
scope.process_function_statement(statement);
scope.process_variable_expression(statement.mutate_function_name().mutate_identifier());

for r#type in statement
.iter_mut_parameters()
.filter_map(TypedIdentifier::mutate_type)
{
Self::visit_type(r#type, scope);
}

if let Some(variadic_type) = statement.mutate_variadic_type() {
Self::visit_function_variadic_type(variadic_type, scope);
}

if let Some(return_type) = statement.mutate_return_type() {
Self::visit_function_return_type(return_type, scope);
}

scope.push();
if statement.get_name().has_method() {
scope.insert_self();
}
statement
.mutate_parameters()
.iter_mut()
.for_each(|parameter| scope.insert(parameter.mutate_name()));

scope.process_scope(statement.mutate_block(), None);

Self::visit_block(statement.mutate_block(), scope);
scope.pop();

scope.process_after_function_statement(statement);
}

fn visit_local_function(statement: &mut LocalFunctionStatement, scope: &mut T) {
scope.process_local_function_statement(statement);

scope.insert_local_function(statement);

for r#type in statement
.iter_mut_parameters()
.filter_map(TypedIdentifier::mutate_type)
{
Self::visit_type(r#type, scope);
}

if let Some(variadic_type) = statement.mutate_variadic_type() {
Self::visit_function_variadic_type(variadic_type, scope);
}

if let Some(return_type) = statement.mutate_return_type() {
Self::visit_function_return_type(return_type, scope);
}

scope.push();
statement
.mutate_parameters()
.iter_mut()
.for_each(|parameter| scope.insert(parameter.mutate_name()));

scope.process_scope(statement.mutate_block(), None);

Self::visit_block(statement.mutate_block(), scope);
scope.pop();

scope.process_after_local_function_statement(statement);
}

fn visit_generic_for(statement: &mut GenericForStatement, scope: &mut T) {
scope.process_generic_for_statement(statement);

statement
.iter_mut_expressions()
.for_each(|expression| Self::visit_expression(expression, scope));

scope.push();
statement
.iter_mut_identifiers()
.for_each(|identifier| scope.insert(identifier.mutate_name()));

for r#type in statement
.iter_mut_identifiers()
.filter_map(TypedIdentifier::mutate_type)
{
Self::visit_type(r#type, scope);
}

scope.process_scope(statement.mutate_block(), None);

Self::visit_block(statement.mutate_block(), scope);
scope.pop();

scope.process_after_generic_for_statement(statement);
}

fn visit_numeric_for(statement: &mut NumericForStatement, scope: &mut T) {
scope.process_numeric_for_statement(statement);

Self::visit_expression(statement.mutate_start(), scope);
Self::visit_expression(statement.mutate_end(), scope);

if let Some(step) = statement.mutate_step() {
Self::visit_expression(step, scope);
};

if let Some(r#type) = statement.mutate_identifier().mutate_type() {
Self::visit_type(r#type, scope);
}

scope.push();
scope.insert(statement.mutate_identifier().mutate_name());

scope.process_scope(statement.mutate_block(), None);

Self::visit_block(statement.mutate_block(), scope);
scope.pop();

scope.process_after_numeric_for_statement(statement);
}

fn visit_repeat_statement(statement: &mut RepeatStatement, scope: &mut T) {
scope.process_repeat_statement(statement);

scope.push();

let (block, condition) = statement.mutate_block_and_condition();
scope.process_scope(block, Some(condition));

Self::visit_block_without_push(statement.mutate_block(), scope);
Self::visit_expression(statement.mutate_condition(), scope);

scope.pop();

scope.process_after_repeat_statement(statement);
}
}

#[derive(Debug, Clone, Default)]
pub(crate) struct IdentifierTracker {
identifiers: Vec<HashSet<String>>,
Expand Down
2 changes: 1 addition & 1 deletion src/rules/append_text_comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ impl Rule for AppendTextComment {
}

let shift_lines = text.lines().count();
ShiftTokenLine::new(shift_lines).flawless_process(block, context);
ShiftTokenLine::new(shift_lines as isize).flawless_process(block, context);

match self.location {
AppendLocation::Start => {
Expand Down
2 changes: 2 additions & 0 deletions src/rules/bundle/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub(crate) mod path_require_mode;
mod rename_type_declaration;
mod require_mode;

use std::path::Path;
Expand All @@ -9,6 +10,7 @@ use crate::rules::{
};
use crate::Parser;

pub(crate) use rename_type_declaration::RenameTypeDeclarationProcessor;
pub use require_mode::BundleRequireMode;
use wax::Pattern;

Expand Down
Loading

0 comments on commit a6a8f33

Please sign in to comment.