Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Fixed necessary parentheses being removed from the LHS of a binary operation when the expression is hung over multiple lines. This affected `(-X) ^ Y` (changing the meaning of the expression) and, in Luau, `(expr :: assertion) < Y` (causing a syntax error)

## [2.5.2] - 2026-05-16

### Fixed
Expand Down
42 changes: 20 additions & 22 deletions src/formatters/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ pub fn format_binop(ctx: &Context, binop: &BinOp, shape: Shape) -> BinOp {
})
}

/// The context to use when formatting the expression on the LHS of the given binop
fn binary_lhs_context(binop: &BinOp) -> ExpressionContext {
if let BinOp::Caret(_) = binop {
ExpressionContext::BinaryLHSExponent
} else {
ExpressionContext::BinaryLHS
}
}

/// Check to determine whether expression parentheses are required, depending on the provided
/// internal expression contained within the parentheses
fn check_excess_parentheses(internal_expression: &Expression, context: ExpressionContext) -> bool {
Expand Down Expand Up @@ -285,12 +294,7 @@ fn format_expression_internal(
}
}
Expression::BinaryOperator { lhs, binop, rhs } => {
let context = if let BinOp::Caret(_) = binop {
ExpressionContext::BinaryLHSExponent
} else {
ExpressionContext::BinaryLHS
};
let lhs = format_expression_internal(ctx, lhs, context, shape);
let lhs = format_expression_internal(ctx, lhs, binary_lhs_context(binop), shape);
let binop = format_binop(ctx, binop, shape);
let shape = shape.take_last_line(&lhs) + binop.to_string().len();
Expression::BinaryOperator {
Expand Down Expand Up @@ -1184,6 +1188,10 @@ fn hang_binop_expression(
new_binop = hang_binop(ctx, binop.to_owned(), shape, &rhs);
}

// The context of the lhs is determined by the binop it is attached to, not by the
// context that this expression was hung in
let lhs_context = binary_lhs_context(&binop);

let (lhs, rhs) = match should_hang {
true => {
let lhs_shape = shape;
Expand All @@ -1202,7 +1210,7 @@ fn hang_binop_expression(
},
lhs_shape,
lhs_range,
expression_context,
lhs_context,
),
if contains_comments(&*rhs) {
hang_binop_expression(
Expand Down Expand Up @@ -1230,15 +1238,10 @@ fn hang_binop_expression(
binop.clone(),
shape,
lhs_range,
expression_context,
lhs_context,
)
} else {
let context = if let BinOp::Caret(_) = binop {
ExpressionContext::BinaryLHSExponent
} else {
ExpressionContext::BinaryLHS
};
format_expression_internal(ctx, &lhs, context, lhs_shape)
format_expression_internal(ctx, &lhs, lhs_context, lhs_shape)
},
hang_binop_expression(
ctx,
Expand All @@ -1265,15 +1268,10 @@ fn hang_binop_expression(
binop.to_owned(),
shape,
lhs_range,
expression_context,
lhs_context,
)
} else {
let context = if let BinOp::Caret(_) = binop {
ExpressionContext::BinaryLHSExponent
} else {
ExpressionContext::BinaryLHS
};
format_expression_internal(ctx, &lhs, context, shape)
format_expression_internal(ctx, &lhs, lhs_context, shape)
};

let rhs = if contains_comments(&*rhs) {
Expand Down Expand Up @@ -1457,7 +1455,7 @@ fn format_hanging_expression_(
binop.to_owned(),
shape,
lhs_range,
ExpressionContext::UnaryOrBinary,
binary_lhs_context(binop),
);

let current_shape = shape.take_last_line(&lhs) + 1; // 1 = space before binop
Expand Down
6 changes: 6 additions & 0 deletions tests/inputs-luau/excess-parentheses-type-assertion.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
local x = if (foo :: number) < bar
then very + very + very + long + line + right + here + hopefully
else lets + ensure + stylua + writes + this + out + using + multiple + lines

local isNegative = someExtremelyLongVariableNameHere ~= nil and (someExtremelyLongVariableNameHereAsWellOkayThenFine :: number) < someExtremelyLongThresholdVariableNameHereTooRight

if someExtremelyLongVariableNameHere ~= nil and (someExtremelyLongVariableNameHereAsWellOkayThenFine :: number) < someExtremelyLongThresholdVariableNameHereTooRight then
print(someExtremelyLongVariableNameHere)
end
4 changes: 4 additions & 0 deletions tests/inputs/excess-parentheses-dont-remove.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ local _ = (not true) and false
-- https://github.com/JohnnyMorganz/StyLua/issues/623
-- Changes meaning
local y = (-X) ^ Y

-- The same holds when the expression is hung over multiple lines
local someVeryLongVariableNameHere = (-someExtremelyLongOperandNameOnTheLeft) ^ someOtherExtremelyLongOperandNameOnTheRight
local anotherVeryLongVariableNameHere = (not someExtremelyLongConditionNameOnTheLeft) and someOtherExtremelyLongConditionNameOnTheRight
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,15 @@ snapshot_kind: text
local x = if (foo :: number) < bar
then very + very + very + long + line + right + here + hopefully
else lets + ensure + stylua + writes + this + out + using + multiple + lines

local isNegative = someExtremelyLongVariableNameHere ~= nil
and (someExtremelyLongVariableNameHereAsWellOkayThenFine :: number)
< someExtremelyLongThresholdVariableNameHereTooRight

if
someExtremelyLongVariableNameHere ~= nil
and (someExtremelyLongVariableNameHereAsWellOkayThenFine :: number)
< someExtremelyLongThresholdVariableNameHereTooRight
then
print(someExtremelyLongVariableNameHere)
end
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ local _ = (not true) and false
-- Changes meaning
local y = (-X) ^ Y

-- The same holds when the expression is hung over multiple lines
local someVeryLongVariableNameHere = (-someExtremelyLongOperandNameOnTheLeft)
^ someOtherExtremelyLongOperandNameOnTheRight
local anotherVeryLongVariableNameHere = (not someExtremelyLongConditionNameOnTheLeft)
and someOtherExtremelyLongConditionNameOnTheRight

Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ end
if
name
and (
not strictFiltering
(not strictFiltering)
and (
tokenTable[subgroup]
or tokenTable[className]
Expand Down