diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala index 75fdb97b81..c2b3c667c2 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala @@ -932,7 +932,7 @@ class FormatOps( indentLen: Int, extendsThenWith: => Boolean = false, )(implicit fileLine: FileLine, ft: FT, style: ScalafmtConfig): Seq[Split] = { - val nlMod = NewlineT(alt = Some(Space)) + val nlMod = Newline.withAlt(Space) def nlPolicy(ignore: Boolean) = Policy ? (ignore || owners.isEmpty) || Policy.onRight(lastFt, prefix = "WITH") { case d @ Decision(FT(_, _: T.KwWith, m), _) if owners(m.rightOwner) => @@ -1161,7 +1161,7 @@ class FormatOps( style.newlines.forceAfterImplicitParamListModifier val nlNoAlt = implicitNL || !rightIsImplicit && style.verticalMultiline.newlineAfterOpenParen - val nlMod = NewlineT(alt = if (nlNoAlt) None else Some(slbSplit.modExt)) + val nlMod = Newline.withAltIf(!nlNoAlt)(slbSplit.modExt) val spaceImplicit = !implicitNL && implicitParams.lengthCompare(1) > 0 && style.newlines.notBeforeImplicitParamListModifier Seq( diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/ModExt.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/ModExt.scala index cbbe9ce541..a5a1d2eaf4 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/ModExt.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/ModExt.scala @@ -2,19 +2,46 @@ package org.scalafmt.internal import scala.meta.tokens.{Token => T} -import scala.language.implicitConversions +import scala.annotation.tailrec /** @param mod * Is this a space, no space, newline or 2 newlines? * @param indents * Does this add indentation? */ -case class ModExt(mod: Modification, indents: Seq[Indent] = Seq.empty) { - lazy val indentation = indents.mkString("[", ", ", "]") - +case class ModExt( + mod: Modification, + indents: Seq[Indent] = Nil, + altOpt: Option[ModExt] = None, +) { @inline def isNL: Boolean = mod.isNL + @tailrec + private def toString(prefix: String, indentPrefix: String): String = { + @inline + def res(suffix: String) = { + val ind = if (indents.isEmpty) "" else indents.mkString("[", ", ", "]") + s"$prefix$mod($indentPrefix$ind)$suffix" + } + + altOpt match { + case None => res("") + case Some(x) => x.toString(res("|"), "+") + } + } + + override def toString: String = toString("", "") + + def withAlt(alt: ModExt): ModExt = + if (altOpt.contains(alt)) this else copy(altOpt = Some(alt)) + + @inline + def withAltIf(ok: Boolean)(alt: => ModExt): ModExt = + if (ok) withAlt(alt) else this + + def orMod(flag: Boolean, mod: => ModExt): ModExt = if (flag) this else mod + def withIndent(length: => Length, expire: => FT, when: ExpiresOn): ModExt = length match { case Length.Num(0, _) => this @@ -75,9 +102,3 @@ case class ModExt(mod: Modification, indents: Seq[Indent] = Seq.empty) { .flatMap(_.withStateOffset(offset + mod.length)) } - -object ModExt { - - implicit def implicitModToModExt(mod: Modification): ModExt = ModExt(mod) - -} diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Modification.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Modification.scala index b99d593543..b9ebfae6f0 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Modification.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Modification.scala @@ -34,25 +34,18 @@ case object NoSplit extends Modification { * optional additional set of indents) if the newline will indent beyond the * current column? For example, used by select chains in [[Router]]. */ -case class NewlineT( - isDouble: Boolean = false, - noIndent: Boolean = false, - alt: Option[ModExt] = None, -) extends Modification { +case class NewlineT(isDouble: Boolean = false, noIndent: Boolean = false) + extends Modification { override def toString = { val double = if (isDouble) "x2" else "" val indent = if (noIndent) "[NoIndent]" else "" - val altStr = alt.fold("")(x => "|" + x.mod.toString) - "NL" + double + indent + altStr + "NL" + double + indent } override val newlines: Int = if (isDouble) 2 else 1 override val length: Int = 0 } -object Newline extends NewlineT { - def orMod(flag: Boolean, mod: => Modification): Modification = - if (flag) this else mod -} +object Newline extends NewlineT object Newline2x extends NewlineT(isDouble = true) { def apply(isDouble: Boolean): NewlineT = if (isDouble) this else Newline diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Router.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Router.scala index c1485ec6a2..a46aa7c336 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Router.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Router.scala @@ -1474,11 +1474,11 @@ class Router(formatOps: FormatOps) { val nlIndentLen = if (nlClosedOnOpenEffective eq NlClosedOnOpen.Cfg) indentLen else bpIndentLen - val nlMod = - if (nlOnly && noBreak() && right.is[T.Comment]) Space - else NewlineT(alt = if (singleLineOnly) Some(NoSplit) else None) + val nlMod = { + if (nlOnly && noBreak() && right.is[T.Comment]) Space.toExt + else Newline.withAltIf(singleLineOnly)(NoSplit) + }.withIndent(nlIndentLen, close, Before) val nlSplit = Split(nlMod, bracketPenalty * (if (oneline) 4 else 2)) - .withIndent(nlIndentLen, close, Before) .withSingleLineNoOptimal(close, ignore = !singleLineOnly).andPolicy( Policy ? singleLineOnly || nlPolicy & penalizeNewlinesPolicy, ).andPolicy(singleArgAsInfix.map(InfixSplits(_, ft).nlPolicy)) @@ -1850,7 +1850,7 @@ class Router(formatOps: FormatOps) { if (ok) Some(nd) else None } val altIndent = endSelect.map(Indent(-indentLen, _, After)) - NewlineT(alt = Some(ModExt(modSpace).withIndentOpt(altIndent))) + Newline.withAlt(modSpace.withIndentOpt(altIndent)) } val prevChain = inSelectChain(prevSelect, thisSelect, expireTree) @@ -1889,9 +1889,8 @@ class Router(formatOps: FormatOps) { val nlCost = nlBaseCost + nestedPenalty + chainLengthPenalty val nlMod = getNlMod val legacySplit = Split(!prevChain, 1) { // must come first, for backwards compat - if (style.optIn.breaksInsideChains) Newline - .orMod(hasBreak(), modSpace) - else nlMod + if (!style.optIn.breaksInsideChains) nlMod + else Newline.orMod(hasBreak(), modSpace) }.withPolicy(newlinePolicy).onlyFor(SplitTag.SelectChainSecondNL) val slbSplit = if (ignoreNoSplit) Split.ignored @@ -1907,7 +1906,7 @@ class Router(formatOps: FormatOps) { .withSingleLineNoOptimal(chainExpire, noSyntaxNL = true) } }.andPolicy(penalizeBreaks) - val nlSplit = Split(if (ignoreNoSplit) Newline else nlMod, nlCost) + val nlSplit = Split(Newline.orMod(ignoreNoSplit, nlMod), nlCost) .withPolicy(newlinePolicy) Seq(legacySplit, slbSplit, nlSplit) } else { @@ -1945,14 +1944,14 @@ class Router(formatOps: FormatOps) { else Seq( Split(nlOnly, 0)(modSpace) .withSingleLine(expire, noSyntaxNL = true), - Split(NewlineT(alt = Some(modSpace)), nlCost) + Split(Newline.withAlt(modSpace), nlCost) .withPolicy(forcedBreakOnNextDotPolicy), ) case Newlines.fold => def nlSplitBase(cost: Int, policy: Policy = NoPolicy)(implicit fileLine: FileLine, - ) = Split(NewlineT(alt = Some(modSpace)), cost, policy = policy) + ) = Split(Newline.withAlt(modSpace), cost, policy = policy) if (nextDotIfSig.isEmpty) if (nlOnly) Seq(nlSplitBase(0)) else { diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Split.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Split.scala index 507c51bf39..89f51535de 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Split.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Split.scala @@ -59,7 +59,7 @@ case class Split( import PolicyOps._ def withNoIndent: Split = mod match { - case x @ NewlineT(_, false, _) => + case x: NewlineT if !x.noIndent => copy(modExt = modExt.copy(mod = x.copy(noIndent = true))) case _ => this } @@ -70,9 +70,6 @@ case class Split( @inline def fileLine: FileLine = fileLineStack.fileLineLast - @inline - def indentation: String = modExt.indentation - @inline def isNL: Boolean = modExt.isNL @@ -318,7 +315,7 @@ case class Split( else "" } val opt = optimalAt.fold("")(", opt=" + _) - s"""${prefix}c=$cost[$penalty] $mod:[$fileLineStack](indents=$indentation, $policy$opt)""" + s"""${prefix}c=$cost[$penalty] $modExt:[$fileLineStack]($policy$opt)""" } } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala index 0b11ffd02c..10adf652fc 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala @@ -70,7 +70,7 @@ final class State( if (right.is[T.EOF]) (initialNextSplit, 0, Seq.empty) else { val offset = column - indentation - def getUnexpired(modExt: ModExt, indents: Seq[ActualIndent] = Nil) = { + def getUnexpired(modExt: ModExt, indents: Seq[ActualIndent]) = { val extendedEnd = getRelativeToLhsLastLineEnd(modExt.isNL) (modExt.getActualIndents(offset) ++ indents).flatMap { x => if (x.notExpiredBy(tok)) Some(x) @@ -80,19 +80,17 @@ final class State( } val initialModExt = initialNextSplit.modExt - val indents = initialModExt.indents val nextPushes = getUnexpired(initialModExt, pushes) val nextIndent = Indent.getIndent(nextPushes) - initialNextSplit.mod match { - case m: NewlineT - if !tok.left.is[T.Comment] && m.alt.isDefined && - nextIndent >= m.alt.get.mod.length + column => - val alt = m.alt.get - val altPushes = getUnexpired(alt) - val altIndent = Indent.getIndent(altPushes) - val split = initialNextSplit.withMod(alt.withIndents(indents)) - (split, nextIndent + altIndent, nextPushes ++ altPushes) - case _ => (initialNextSplit, nextIndent, nextPushes) + initialModExt.altOpt.flatMap { alt => + if (tok.left.is[T.Comment]) None + else if (nextIndent < alt.mod.length + column) None + else Some(alt.withIndents(initialModExt.indents)) + }.fold((initialNextSplit, nextIndent, nextPushes)) { alt => + val altPushes = getUnexpired(alt, pushes) + val altIndent = Indent.getIndent(altPushes) + val split = initialNextSplit.withMod(alt) + (split, altIndent, altPushes) } } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/package.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/package.scala index 58e8c4187b..58eee921b7 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/package.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/package.scala @@ -1,8 +1,20 @@ package org.scalafmt +import scala.language.implicitConversions + package object internal { private[scalafmt] type FT = FormatToken private[scalafmt] val FT = FormatToken + private[scalafmt] implicit def implicitModToModExt( + mod: Modification, + ): ModExt = ModExt(mod) + + private[scalafmt] implicit class ImplicitModification( + private val mod: Modification, + ) extends AnyVal { + def toExt: ModExt = mod + } + } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/LoggerOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/LoggerOps.scala index 9f09c349fb..80ed125b60 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/LoggerOps.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/LoggerOps.scala @@ -47,7 +47,7 @@ object LoggerOps { s"d=${s.depth} w=${s.cost}[${s.appliedPenalty}] i=${s.indentation} col=${s .column} #nl=${s.lineId}$policies;${delim}s=${log(s.split)}$nls" } - def log(split: Split): String = s"$split" + def log(split: Split): String = split.toString def log(formatToken: FT): String = s"""|${log(formatToken.left)}