diff --git a/project/Dependencies.scala b/project/Dependencies.scala index aad78c3356..1d0f3add99 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,7 +8,7 @@ import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ object Dependencies { val metaconfigV = "0.13.0" - val scalametaV = "4.11.2" + val scalametaV = "4.12.0" val scalacheckV = "1.18.1" val coursier = "2.1.10" val munitV = "1.0.2" 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 6977b0a7f7..5fb35de125 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 @@ -1351,7 +1351,7 @@ class FormatOps( } ok && (thisTree match { - case _: Term.Match => // like select and apply in one + case _: Term.SelectMatch => // like select and apply in one !tokenAfter(thisSelectLike.nameFt).right.is[T.LeftBrace] || style.includeCurlyBraceInSelectChains && nextSelect.isDefined && !nextSelect.contains(lastApply) @@ -1525,7 +1525,7 @@ class FormatOps( case _: Term.If => getSlbSplits() case _: Term.TryClause => if (hasStateColumn) getSplits(getSpaceSplit(1)) else getSlbSplits() - case _: Term.Block | _: Term.Match | _: Type.Match | + case _: Term.Block | _: Term.MatchLike | _: Type.Match | _: Term.NewAnonymous => getSplits(getSpaceSplit(1)) case t: Term.ForYield => getDelimsIfEnclosed(t.enumsBlock) match { case Some((forEnumHead, forEnumLast)) => @@ -1743,25 +1743,27 @@ class FormatOps( val OnRight = new FT.ExtractFromMeta(m => onRightOpt(m.rightOwner, tokens(m.idx))) - private[FormatOps] def onRightOpt( + private def get( ro: Tree, - ftOrNull: => FT, - ): Option[SelectLike] = ro match { + )(onMatch: Term.SelectMatch => Option[FT]): Option[SelectLike] = ro match { case x: Term.Select => Some(SelectLike(x)) - case x: Term.Match if dialect.allowMatchAsOperator => - val ft = Option(ftOrNull).getOrElse(tokenAfter(x.expr)) - if (!ft.right.is[T.Dot]) None - else nextNonCommentAfter(ft) match { - case xft @ FT(_, _: T.KwMatch, _) => Some(SelectLike(x, next(xft))) - case _ => None - } + case x: Term.SelectMatch => onMatch(x).map(ft => SelectLike(x, ft)) case _ => None } + private[FormatOps] def onRightOpt(ro: Tree, ft: => FT): Option[SelectLike] = + get(ro) { _ => + nextNonCommentAfter(ft) match { + case xft @ FT(_, _: T.KwMatch, _) => Some(next(xft)) + case _ => None + } + } + def onRightOpt(ft: FT): Option[SelectLike] = onRightOpt(ft.meta.rightOwner, ft) - def unapply(tree: Tree): Option[SelectLike] = onRightOpt(tree, null) + def unapply(tree: Tree): Option[SelectLike] = + get(tree)(x => Some(tokenBefore(x.casesBlock))) } def getSplitsForTypeBounds( @@ -2968,7 +2970,7 @@ object FormatOps { object SelectLike { def apply(tree: Term.Select)(implicit ftoks: FormatTokens): SelectLike = new SelectLike(tree, tree.qual, ftoks.getHead(tree.name)) - def apply(tree: Term.Match, kw: FT): SelectLike = + def apply(tree: Term.SelectMatch, kw: FT): SelectLike = new SelectLike(tree, tree.expr, kw) } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala index 672a242dd2..973b50b023 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala @@ -1944,7 +1944,7 @@ object FormatWriter { case _: Defn.ExtensionGroup => "extension" case _: Term.If => "if" case _: Term.While => "while" - case _: Term.Match | _: Type.Match => "match" + case _: Term.MatchLike | _: Type.Match => "match" case _: Term.ForClause => "for" case _: Term.TryClause => "try" case _ => null 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 5482e9ab2c..8c459f080c 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 @@ -225,10 +225,10 @@ class Router(formatOps: FormatOps) { // optional braces: block follows case FT( _: T.Equals | _: T.Colon | _: T.KwWith | _: T.RightParen | - _: T.KwReturn | _: T.ContextArrow | _: T.LeftArrow | - _: T.RightArrow | _: T.KwMatch | _: T.KwThen | _: T.KwElse | - _: T.KwThrow | _: T.KwTry | _: T.KwCatch | _: T.KwFinally | - _: T.KwFor | _: T.KwDo | _: T.KwWhile | _: T.KwYield | _: T.KwIf, + _: T.KwReturn | _: T.FunctionArrow | _: T.LeftArrow | _: T.KwMatch | + _: T.KwThen | _: T.KwElse | _: T.KwThrow | _: T.KwTry | + _: T.KwCatch | _: T.KwFinally | _: T.KwFor | _: T.KwDo | + _: T.KwWhile | _: T.KwYield | _: T.KwIf, _, OptionalBraces(splits), ) if dialect.allowSignificantIndentation => splits @@ -285,12 +285,8 @@ class Router(formatOps: FormatOps) { } (arrow, 0, nlOnly) case (t: Term.PartialFunction) :: Nil => getLambdaInfo(t.cases) - case (t: Term.CasesBlock) :: Nil if (t.parent match { - case Some(t: Term.Match) - if dialect.allowMatchAsOperator && - tokenAfter(t.expr).right.is[T.Dot] => true - case _ => false - }) => getLambdaInfo(t.cases) + case (t: Term.CasesBlock) :: Nil if t.parent.is[Term.SelectMatch] => + getLambdaInfo(t.cases) case (t: Term.Block) :: Nil if !isEnclosedInBraces(t) => getLambdaInfo(t.stats) case _ => getLambdaNone @@ -510,7 +506,7 @@ class Router(formatOps: FormatOps) { case _ => splits } - case FT(_: T.RightArrow | _: T.ContextArrow, r, _) if (leftOwner match { + case FT(_: T.FunctionArrow, r, _) if (leftOwner match { case t: Term.FunctionTerm => !r.is[T.Comment] && !tokens.isEmpty(t.body) && isBlockFunction(t) case _ => false @@ -539,7 +535,7 @@ class Router(formatOps: FormatOps) { ) } else Seq(spaceSplitBase) - case FT(_: T.RightArrow | _: T.ContextArrow, right, _) if (leftOwner match { + case FT(_: T.FunctionArrow, right, _) if (leftOwner match { case _: Term.FunctionTerm | _: Term.PolyFunction => true case t: Self => t.ancestor(2).is[Term.NewAnonymous] case _ => false @@ -1777,7 +1773,7 @@ class Router(formatOps: FormatOps) { def checkFewerBraces(tree: Tree) = tree match { case p: Term.Apply => isFewerBraces(p) - case p: Term.Match => getHead(p.casesBlock).meta.leftOwner ne + case p: Term.MatchLike => getHead(p.casesBlock).meta.leftOwner ne p.casesBlock case p: Term.NewAnonymous => getHeadOpt(p.templ.body) .exists(_.left.is[T.Colon]) diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/TreeSyntacticGroup.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/TreeSyntacticGroup.scala index 3ab10e06c0..d507ab1f50 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/TreeSyntacticGroup.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/TreeSyntacticGroup.scala @@ -14,6 +14,7 @@ object TreeSyntacticGroup { // Term case _: Term.Name => g.Path case _: Term.Select => g.Path + case _: Term.SelectPostfix => g.Term.PostfixExpr case _: Term.Interpolate => g.Term.SimpleExpr1 case _: Term.Xml => g.Term.SimpleExpr1 case _: Term.Apply => g.Term.SimpleExpr1 @@ -29,7 +30,7 @@ object TreeSyntacticGroup { case _: Term.Tuple => g.Term.SimpleExpr1 // ???, breaks a op ((b, c)) // case _: Term.Tuple => g.Term.Expr1 // ??? Was SimpleExpr1, which is buggy for `a op ((b, c)) case _: Term.If => g.Term.Expr1 - case _: Term.Match => g.Term.Expr1 + case _: Term.MatchLike => g.Term.Expr1 case _: Term.TryClause => g.Term.Expr1 case _: Term.FunctionTerm => g.Term.Expr case _: Term.PolyFunction => g.Term.Expr diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/AvoidInfix.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/AvoidInfix.scala index a42e26064e..13be989f3c 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/AvoidInfix.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/AvoidInfix.scala @@ -33,26 +33,17 @@ class AvoidInfix(implicit ctx: RewriteCtx) extends RewriteSession { override def rewrite(tree: Tree): Unit = tree match { case x: Term.ApplyInfix => rewriteImpl(x.lhs, Right(x.op), x.arg, x.targClause) - case x: Term.Select if !cfg.excludePostfix && noDot(x.name.tokens.head) => + case x: Term.SelectPostfix if !cfg.excludePostfix => rewriteImpl(x.qual, Right(x.name)) case x: Term.Match => noDotMatch(x) .foreach(op => rewriteImpl(x.expr, Left(op), null)) case _ => } - private def noDot(opToken: T): Boolean = - !ctx.tokenTraverser.prevNonTrivialToken(opToken).forall(_.is[T.Dot]) - - private def noDotMatch(t: Term.Match): Either[Boolean, T] = - if (allowMatchAsOperator && t.mods.isEmpty) - ctx.tokenTraverser.prevNonTrivialToken(t.casesBlock.tokens.head) match { - case Some(kw) => - if (!noDot(kw)) Left(true) - else if (cfg.excludeMatch) Left(false) - else Right(kw) - case _ => Left(false) - } - else Left(false) + private def noDotMatch(t: Term.Match): Option[T] = + if (allowMatchAsOperator && t.mods.isEmpty && !cfg.excludeMatch) ctx + .tokenTraverser.prevNonTrivialToken(t.casesBlock.tokens.head) + else None private def rewriteImpl( lhs: Term, @@ -68,8 +59,8 @@ class AvoidInfix(implicit ctx: RewriteCtx) extends RewriteSession { (lhs match { case t: Term.ApplyInfix => checkMatchingInfix(t.lhs, t.op.value, t.arg) case t: Term.Match => noDotMatch(t) match { - case Left(ok) => ok - case Right(kw) => checkMatchingInfix(t.expr, kw.text, t.casesBlock) + case None => false + case Some(kw) => checkMatchingInfix(t.expr, kw.text, t.casesBlock) } case _ => false }) @@ -115,7 +106,7 @@ class AvoidInfix(implicit ctx: RewriteCtx) extends RewriteSession { // foo _ compose bar => (foo _).compose(bar) // new Foo compose bar => (new Foo).compose(bar) case _: Term.Eta | _: Term.New | _: Term.Annotate => true - case t: Term.Select if rhs eq null => !noDot(t.name.tokens.head) + case _: Term.SelectPostfix => rhs eq null case _ => false }) if (shouldWrapLhs) { @@ -150,8 +141,8 @@ class AvoidInfix(implicit ctx: RewriteCtx) extends RewriteSession { case Some(x) => x case None if isWrapped(lhs) => true case None => noDotMatch(lhs) match { - case Left(ok) => ok - case Right(op) => checkMatchingInfix(lhs.expr, op.text, null) + case None => false + case Some(op) => checkMatchingInfix(lhs.expr, op.text, null) } } case _ => true diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantBraces.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantBraces.scala index a3439c0a3b..6ad2a22a4a 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantBraces.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantBraces.scala @@ -561,7 +561,7 @@ class RedundantBraces(implicit val ftoks: FormatTokens) } // can't do it for try until 2.13.3 - case _ if isPrefixExpr(stat) => false + case _ if RewriteCtx.isPrefixExpr(stat) => false case parentIf: Term.If if stat.is[Term.If] => // if (a) { if (b) c } else d @@ -583,7 +583,7 @@ class RedundantBraces(implicit val ftoks: FormatTokens) case _ => true // don't allow other non-infix } - case p: Term.Match => p.expr eq b + case p: Term.MatchLike => p.expr eq b case p: Type.Match => p.tpe eq b case p: Term.ForClause if p.body eq b => @@ -639,12 +639,6 @@ class RedundantBraces(implicit val ftoks: FormatTokens) private def okLineSpan(tree: Tree)(implicit style: ScalafmtConfig): Boolean = getTreeLineSpan(tree) <= settings.maxBreaks - // special case for Select which might contain a space instead of dot - private def isPrefixExpr(expr: Tree): Boolean = RewriteCtx - .isSimpleExprOr(expr) { case t: Term.Select => - ftoks(t.name.tokens.head, -1).left.is[T.Dot] - } - private def braceSeparatesTwoXmlTokens(implicit ft: FT): Boolean = ft.left .is[T.Xml.End] && ftoks.next(ft).right.is[T.Xml.Start] diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantParens.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantParens.scala index c79459ad38..3fde0bb495 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantParens.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantParens.scala @@ -134,20 +134,13 @@ class RedundantParens(implicit val ftoks: FormatTokens) } } - private def isSelectWithDot(t: Term.Select): Boolean = ftoks - .tokenBefore(t.name).left.is[T.Dot] - - private def okToReplaceOther( - t: Tree, - )(implicit style: ScalafmtConfig): Boolean = t match { + private def okToReplaceOther(t: Tree): Boolean = t match { case _: Lit => t.tokens.length == 1 || !t.parent.is[Term.Ref] case _: Term.ApplyUnary => !t.parent.is[Term.Ref] case _: Member.Apply | _: Term.Interpolate | _: Term.PartialFunction => true - case t: Term.Select => isSelectWithDot(t) - case _: Ref => true // Ref must be after Select and ApplyUnary - case t: Term.Match => style.dialect.allowMatchAsOperator && - ftoks.tokenAfter(t.expr).right.is[T.Dot] && // like select - ftoks.getHead(t.casesBlock).left.is[T.LeftBrace] + case _: Term.SelectPostfix => false + case _: Ref => true // Ref must be after SelectPostfix and ApplyUnary + case t: Term.SelectMatch => ftoks.getHead(t.casesBlock).left.is[T.LeftBrace] case _ => false } @@ -157,7 +150,7 @@ class RedundantParens(implicit val ftoks: FormatTokens) case arg :: Nil => arg match { case _: Term.Block | _: Term.PartialFunction => !t.parent.isOpt[Init] case _: Lit.Unit | _: Member.Tuple => false - case t: Term.Select if !isSelectWithDot(t) => false + case _: Term.SelectPostfix => false case _ => t.parent.exists { case pia: Member.Infix => val keep = infixNeedsParens(pia, arg) @@ -191,7 +184,7 @@ class RedundantParens(implicit val ftoks: FormatTokens) case _: Lit | _: Name | _: Term.Interpolate => true case _: Term.PartialFunction => true case _: Term.AnonymousFunction => false - case t: Term.Select if !isSelectWithDot(t) => false + case _: Term.SelectPostfix => false case _ => style.rewrite.redundantParens.infixSide.isDefined } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RemoveScala3OptionalBraces.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RemoveScala3OptionalBraces.scala index aa1eab40e0..90bffb2713 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RemoveScala3OptionalBraces.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RemoveScala3OptionalBraces.scala @@ -87,7 +87,7 @@ private class RemoveScala3OptionalBraces(implicit val ftoks: FormatTokens) val notOkToRewrite = hasFormatOff || // can't force significant indentation (nextFt.meta.rightOwner match { case t: Term.Name => t.parent.exists { - case p: Term.Select => p.name eq t // select without `.` + case p: Term.SelectPostfix => p.name eq t // select without `.` case p: Term.ApplyInfix if p.op eq t => !style.dialect.allowInfixOperatorAfterNL || !t.tokens.head.isSymbolicInfixOperator diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/Rewrite.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/Rewrite.scala index e608d19cb5..d9c4655a6c 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/Rewrite.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/Rewrite.scala @@ -81,16 +81,6 @@ case class RewriteCtx(style: ScalafmtConfig, input: Input, tree: Tree) { case _ => Some(false) } - // special case for Select which might contain a space instead of dot - def isPrefixExpr(expr: Tree): Boolean = - isSimpleExprOr(expr) { case t: Term.Select => - val maybeDot = tokenTraverser.findBefore(t.name.tokens.head) { - case _: T.Trivia => None - case x => Some(x.is[T.Dot]) - } - maybeDot.isDefined - } - } trait Rewrite @@ -166,15 +156,19 @@ object RewriteCtx { )(orElse: PartialFunction[Tree, Boolean]): Boolean = expr match { case _: Lit | _: Name | _: Term.Interpolate => true case _: Term.New | _: Term.NewAnonymous => true - case _: Term.Apply | _: Term.ApplyUnary => true + case _: Term.Apply | _: Term.ApplyUnary | _: Term.Select => true case _ => orElse.applyOrElse(expr, (_: Tree) => false) } + @inline + def isPrefixExpr(expr: Tree): Boolean = + isSimpleExprOr(expr)(PartialFunction.empty) + @inline def isPostfixExpr(expr: Tree)(implicit style: ScalafmtConfig): Boolean = isSimpleExprOr(expr) { - case _: Term.Select | _: Term.ApplyInfix => true - case _: Term.Match if style.dialect.allowMatchAsOperator => true + case _: Term.SelectPostfix | _: Term.ApplyInfix => true + case _: Term.MatchLike if style.dialect.allowMatchAsOperator => true } @inline diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/StyleMap.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/StyleMap.scala index d3975fd541..0b2b5cc2c1 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/StyleMap.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/StyleMap.scala @@ -84,7 +84,7 @@ class StyleMap(tokens: FormatTokens, val init: ScalafmtConfig) { @tailrec private def isSimpleLiteral(tree: Tree): Boolean = tree match { - case t: Term.Select => isSimpleLiteral(t.qual) + case t: Term.SelectLike => isSimpleLiteral(t.qual) case t: Term.Assign => isSimpleLiteral(t.rhs) case _ => isBasicLiteral(tree) || (tree.children match { diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala index 03ee13e9da..424dd3a310 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala @@ -578,7 +578,7 @@ object TreeOps { def getEndOfFirstCall(tree: Tree)(implicit ftoks: FormatTokens) = { @tailrec def traverse(tree: Tree, res: Option[Tree]): Option[Tree] = tree match { - case t: Term.Select if res.isDefined => traverse(t.qual, Some(t.qual)) + case t: Term.SelectLike if res.isDefined => traverse(t.qual, Some(t.qual)) case t: Term.ApplyType => traverse(t.fun, Some(t)) case t: Member.Apply => traverse(t.fun, Some(t.fun)) case t: Init => traverse(t.tpe, Some(t.tpe)) @@ -622,7 +622,7 @@ object TreeOps { findInterpolate(tree).flatMap(getStripMarginChar) def getStripMarginChar(t: Tree): Option[Char] = t.parent match { - case Some(ts: Term.Select) if ts.name.value == "stripMargin" => + case Some(ts: Term.SelectLike) if ts.name.value == "stripMargin" => ts.parent match { case Some(Term.Apply.Initial(_, List(arg: Lit.Char))) => Some(arg.value) case _ => Some('|') diff --git a/scalafmt-tests/shared/src/test/scala/org/scalafmt/FormatTests.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/FormatTests.scala index 168719666e..91b4967ff6 100644 --- a/scalafmt-tests/shared/src/test/scala/org/scalafmt/FormatTests.scala +++ b/scalafmt-tests/shared/src/test/scala/org/scalafmt/FormatTests.scala @@ -144,7 +144,7 @@ class FormatTests extends FunSuite with CanRunTests with FormatAssertions { val explored = Debug.explored.get() logger.debug(s"Total explored: $explored") if (!onlyUnit && !onlyManual) - assertEquals(explored, 1084630, "total explored") + assertEquals(explored, 1084572, "total explored") val results = debugResults.result() // TODO(olafur) don't block printing out test results. // I don't want to deal with scalaz's Tasks :'(