Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FormatOps: handle fewer braces and refined types #3446

Merged
merged 2 commits into from
Jan 23, 2023
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -2037,6 +2037,34 @@ class FormatOps(
def splits = Some(getSplits(ft, t, forceNL = true))
def rightBrace = if (isSeqMulti(t.stats)) treeLast(t) else None
})
case t: Type.Refine =>
Some(new OptionalBracesRegion {
def owner = Some(t)
def splits = Some(getSplits(ft, t, forceNL = true))
def rightBrace = treeLast(t)
})
case t: Term.ArgClause if tokens.getHead(t) eq ft =>
def funcSplit(arg: Term.FunctionTerm)(implicit fl: FileLine) = {
val end = tokens.getLast(arg)
val opt = getOptimalTokenFor(getFuncArrow(arg).getOrElse(end))
Split(Space, 0)
.withSingleLine(opt)
.andPolicy(decideNewlinesOnlyAfterToken(opt))
}
Some(new OptionalBracesRegion {
def owner = t.parent
def splits = Some(t.values match {
case (tf: Term.FunctionTerm) :: Nil
if !style.newlines.alwaysBeforeCurlyLambdaParams &&
t.parent.exists(_.is[Term.Apply]) =>
getSplits(ft, t, forceNL = false) match {
case s +: rs if !s.isNL => funcSplit(tf)(s.fileLine) +: rs
case ss => ss
}
case _ => getSplits(ft, t, forceNL = true)
})
def rightBrace = treeLast(t)
})
case _ => None
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ object Indent {
case x => new IndentImpl(x, expire, expiresAt)
}

@inline def empty: Indent = Empty
case object Empty extends Indent {
override def withStateOffset(offset: Int): Option[ActualIndent] = None
override def switch(trigger: Token, on: Boolean): Indent = this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,12 @@ class Router(formatOps: FormatOps) {
val indent = // don't indent if the body is empty `{ x => }`
if (isEmptyFunctionBody(leftOwner) && !right.is[T.Comment]) 0
else if (leftOwner.is[Template]) 0 // { applied the indent
else style.indent.main
else
leftOwner.parent match {
case Some(ArgClauseParent(p: Term.Apply)) if isFewerBraces(p) =>
style.indent.getSignificant
case _ => style.indent.main
}

def noSingleLine = {
// for constructors with empty args lambda
Expand Down Expand Up @@ -1718,6 +1723,23 @@ class Router(formatOps: FormatOps) {
val expire = getLastEnclosedToken(expireTree)
val indentLen = style.indent.main

val nextDotIfSig = nextSelect.flatMap { ns =>
val ok = ns.qual match {
case p: Term.Apply => isFewerBraces(p)
case p: Term.Match => !tokenBefore(p.cases).left.is[T.LeftBrace]
case _ => false
}
if (ok) Some(tokenBefore(ns.nameToken)) else None
}
def forcedBreakOnNextDotPolicy = nextSelect.map { selectLike =>
val tree = selectLike.tree
Policy.before(selectLike.nameToken) {
case d @ Decision(FormatToken(_, _: T.Dot, m), _)
if m.rightOwner eq tree =>
d.onlyNewlinesWithoutFallback
}
}

def breakOnNextDot: Policy =
nextSelect.fold(Policy.noPolicy) { selectLike =>
val tree = selectLike.tree
Expand Down Expand Up @@ -1828,29 +1850,29 @@ class Router(formatOps: FormatOps) {
if (prevSelect.isEmpty && nextSelect.isEmpty)
Seq(Split(NoSplit, 0), Split(Newline, 1))
else {
val forcedBreakPolicy = nextSelect.map { selectLike =>
val tree = selectLike.tree
Policy.before(selectLike.nameToken) {
case d @ Decision(FormatToken(_, _: T.Dot, m), _)
if m.rightOwner eq tree =>
d.onlyNewlinesWithoutFallback
}
}
Seq(
Split(NoSplit, 0).withSingleLine(expire, noSyntaxNL = true),
Split(NewlineT(alt = Some(NoSplit)), 1)
.withPolicyOpt(forcedBreakPolicy)
.withPolicyOpt(forcedBreakOnNextDotPolicy)
)
}

case Newlines.fold =>
val end =
nextSelect.fold(expire)(x => getLastNonTrivialToken(x.qual))
def exclude = insideBracesBlock(t, end, true)
Seq(
Split(NoSplit, 0).withSingleLine(end, exclude),
Split(NewlineT(alt = Some(NoSplit)), 1)
)
if (nextDotIfSig.isEmpty) {
val end =
nextSelect.fold(expire)(x => getLastNonTrivialToken(x.qual))
def exclude = insideBracesBlock(t, end, true)
Seq(
Split(NoSplit, 0).withSingleLine(end, exclude),
Split(NewlineT(alt = Some(NoSplit)), 1)
)
} else {
val policy = forcedBreakOnNextDotPolicy
Seq(
Split(NoSplit, 0).withPolicyOpt(policy),
Split(NewlineT(alt = Some(NoSplit)), 1).withPolicyOpt(policy)
)
}
}

val delayedBreakPolicyOpt = nextSelect.map { selectLike =>
Expand All @@ -1866,11 +1888,13 @@ class Router(formatOps: FormatOps) {
}

// trigger indent only on the first newline
val indent = Indent(indentLen, expire, After)
val nlIndent = Indent(indentLen, expire, After)
val spcIndent = nextDotIfSig
.fold(Indent.empty)(x => Indent(indentLen, x.left, ExpiresOn.Before))
val willBreak = nextNonCommentSameLine(tokens(t, 2)).right.is[T.Comment]
val splits = baseSplits.map { s =>
if (willBreak || s.isNL) s.withIndent(indent)
else s.andFirstPolicyOpt(delayedBreakPolicyOpt)
if (willBreak || s.isNL) s.withIndent(nlIndent)
else s.andFirstPolicyOpt(delayedBreakPolicyOpt).withIndent(spcIndent)
}

if (prevSelect.isEmpty) splits
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,7 @@ object ParamClauseParent {
case p => p
}
}

object ArgClauseParent {
def unapply(t: Member.ArgClause): Option[Tree] = t.parent
}
Original file line number Diff line number Diff line change
Expand Up @@ -1004,4 +1004,7 @@ object TreeOps {
case _ => None
})

def isFewerBraces(tree: Term.Apply)(implicit dialect: Dialect): Boolean =
dialect.allowFewerBraces && tree.argClause.tokens.head.is[Colon]

}
184 changes: 184 additions & 0 deletions scalafmt-tests/src/test/resources/scala3/OptionalBraces.stat
Original file line number Diff line number Diff line change
Expand Up @@ -3598,3 +3598,187 @@ object a {
val a = test("" +: Seq("").map { _ + "" }: _*)
val a = test(("" +: Seq("").map { _ + "" })*)
}
<<< coloneol in fewer braces 1
maxColumn = 80
===
object a:
tgodzik marked this conversation as resolved.
Show resolved Hide resolved
def f(): Unit =
List(1, 2, 3).foldLeft(1):
case (a, b) => a + b
>>>
object a:
def f(): Unit =
List(1, 2, 3).foldLeft(1):
case (a, b) => a + b
<<< coloneol in fewer braces 1, main > sig
maxColumn = 80
indent.main = 4
===
object a:
def f(): Unit =
List(1, 2, 3).foldLeft(1):
case (a, b) => a + b
>>>
object a:
def f(): Unit =
List(1, 2, 3).foldLeft(1):
case (a, b) => a + b
<<< coloneol in fewer braces 2
maxColumn = 80
===
object a:
def f(): Unit =
List(1, 2, 3).foo: a =>
a + 2
.foo: a =>
2 + a
.apply:
12 + 3
>>>
object a:
def f(): Unit =
List(1, 2, 3)
.foo: a =>
a + 2
.foo: a =>
2 + a
.apply:
12 + 3
<<< coloneol in fewer braces 2, main > sig
maxColumn = 80
indent.main = 4
===
object a:
def f(): Unit =
List(1, 2, 3).foo: a =>
a + 2
.foo: a =>
2 + a
.apply:
12 + 3
>>>
object a:
def f(): Unit =
List(1, 2, 3)
.foo: a =>
a + 2
.foo: a =>
2 + a
.apply:
12 + 3
<<< coloneol in fewer braces 3
maxColumn = 80
===
object a:
def f(): Unit =
List(1, 2, 3).foo:
case a : Int =>
case _ =>
otherTerm()
>>>
object a:
def f(): Unit =
List(1, 2, 3).foo:
case a: Int =>
case _ =>
otherTerm()
<<< coloneol in fewer braces 3, main > sig
maxColumn = 80
indent.main = 4
===
object a:
def f(): Unit =
List(1, 2, 3).foo:
case a : Int =>
case _ =>
otherTerm()
>>>
object a:
def f(): Unit =
List(1, 2, 3).foo:
case a: Int =>
case _ =>
otherTerm()
<<< match with eol
maxColumn = 80
===
object a:
def f(): Unit =
List(1, 2, 3).match
case _ => a + 2
.foo: a =>
2 + a
.apply:
12 + 3
>>>
object a:
def f(): Unit =
List(1, 2, 3).match
case _ => a + 2
.foo: a =>
2 + a
.apply:
12 + 3
<<< match with eol, main > sig
maxColumn = 80
indent.main = 4
===
object a:
def f(): Unit =
List(1, 2, 3).match
case _ => a + 2
.foo: a =>
2 + a
.apply:
12 + 3
>>>
object a:
def f(): Unit =
List(1, 2, 3).match
case _ => a + 2
.foo: a =>
2 + a
.apply:
12 + 3
<<< coloneol in refined types
maxColumn = 80
===
object a:
type T = String:
type U = Int
>>>
object a:
type T = String:
type U = Int
<<< coloneol in refined types, main > sig
maxColumn = 80
indent.main = 4
===
object a:
type T = String:
type U = Int
>>>
object a:
type T = String:
type U = Int
<<< with in refined types
maxColumn = 80
===
object a:
type T = String with
type U = Int
>>>
object a:
type T = String with
type U = Int
<<< with in refined types, main > sig
maxColumn = 80
indent.main = 4
===
object a:
type T = String with
type U = Int
>>>
object a:
type T = String with
type U = Int
Loading