diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 09410c78eba4..3ff88e290fbe 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -41,6 +41,7 @@ object Feature: val multiSpreads = experimental("multiSpreads") val subCases = experimental("subCases") val relaxedLambdaSyntax = experimental("relaxedLambdaSyntax") + val relaxedColonSyntax = experimental("relaxedColonSyntax") def experimentalAutoEnableFeatures(using Context): List[TermName] = defn.languageExperimentalFeatures @@ -73,6 +74,7 @@ object Feature: (multiSpreads, "Enable experimental varargs with multi-spreads"), (subCases, "Enable experimental match expressions with sub-cases"), (relaxedLambdaSyntax, "Enable experimental relaxed lambda syntax"), + (relaxedColonSyntax, "Enable experimental relaxed colon syntax"), ) // legacy language features from Scala 2 that are no longer supported. diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 796704759722..e69a88a27700 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -628,7 +628,13 @@ object Scanners { insert(if (pastBlankLine) NEWLINES else NEWLINE, lineOffset) else if indentIsSignificant then if nextWidth < lastWidth - || nextWidth == lastWidth && (indentPrefix == MATCH || indentPrefix == CATCH) && token != CASE then + || nextWidth == lastWidth + && token != CASE + && indentPrefix.match + case MATCH | CATCH => true + case CASE => featureEnabled(Feature.relaxedColonSyntax) + case _ => false + then if currentRegion.isOutermost then if nextWidth < lastWidth then currentRegion = topLevelRegion(nextWidth) else if canDedent then @@ -654,9 +660,13 @@ object Scanners { else if r.isInstanceOf[InBraces] && !closingRegionTokens.contains(token) then report.warning("Line is indented too far to the left, or a `}` is missing", sourcePos()) else if lastWidth < nextWidth - || lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH) && token == CASE then + || lastWidth == nextWidth + && token == CASE + && (lastToken == MATCH || lastToken == CATCH || featureEnabled(Feature.relaxedColonSyntax)) + then if canStartIndentTokens.contains(lastToken) then - currentRegion = Indented(nextWidth, lastToken, currentRegion) + val prefix = if token == CASE && featureEnabled(Feature.relaxedColonSyntax) then CASE else lastToken + currentRegion = Indented(nextWidth, prefix, currentRegion) insert(INDENT, offset) else if lastToken == SELFARROW then currentRegion.knownWidth = nextWidth @@ -1694,7 +1704,7 @@ object Scanners { case class Indented(width: IndentWidth, prefix: Token, outer: Region | Null) extends Region(OUTDENT): knownWidth = width - /** Other indendation widths > width of lines in the same region */ + /** Other indentation widths > width of lines in the same region */ var otherIndentWidths: Set[IndentWidth] = Set() override def coversIndent(w: IndentWidth) = width == w || otherIndentWidths.contains(w) diff --git a/library/src/scala/language.scala b/library/src/scala/language.scala index ee3efb4fa66b..90a12c44ac67 100644 --- a/library/src/scala/language.scala +++ b/library/src/scala/language.scala @@ -368,13 +368,16 @@ object language { @compileTimeOnly("`subCases` can only be used at compile time in import statements") object subCases - /** Experimental support for single-line lambdas and case clause expressions after `:` - */ + /** Experimental support for single-line lambdas and case clause expressions after `:`. */ @compileTimeOnly("`relaxedLambdaSyntax` can only be used at compile time in import statements") object relaxedLambdaSyntax + + /** Experimental support for unindented case block after `:`. */ + @compileTimeOnly("`relaxedColonSyntax` can only be used at compile time in import statements") + object relaxedColonSyntax } - /** The deprecated object contains features that are no longer officially suypported in Scala. + /** The deprecated object contains features that are no longer officially suypported in Scala. * Features in this object are slated for removal. New code should not use them and * old code should migrate away from them. */ @@ -448,7 +451,7 @@ object language { object `future-migration` /** Sets source version to 2.13. Effectively, this doesn't change the source language, - * but rather adapts the generated code as if it was compiled with Scala 2.13 + * but rather adapts the generated code as if it were compiled with Scala 2.13. */ @compileTimeOnly("`2.13` can only be used at compile time in import statements") private[scala] object `2.13` diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 75f4c9e86465..1a8bfe55955d 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -11,6 +11,9 @@ object MiMaFilters { ProblemFilters.exclude[DirectMissingMethodProblem]("scala.caps.package#package.freeze"), // scala/scala3#24545 / scala/scala3#24788 ProblemFilters.exclude[MissingClassProblem]("scala.annotation.unchecked.uncheckedOverride"), + ProblemFilters.exclude[MissingFieldProblem]("scala.language#experimental.relaxedColonSyntax"), + ProblemFilters.exclude[MissingClassProblem]("scala.language$experimental$relaxedColonSyntax$"), + // scala/scala3#24841 ), ) diff --git a/tests/pos/case-indent.scala b/tests/pos/case-indent.scala new file mode 100644 index 000000000000..f52c3c454b4e --- /dev/null +++ b/tests/pos/case-indent.scala @@ -0,0 +1,49 @@ +import language.experimental.relaxedColonSyntax +import language.experimental.relaxedLambdaSyntax + +def f[A](xs: List[A]): List[String] = + xs.map: + case s: String => s + case x => x.toString + .map: + case s: String if s.length > 4 => s + case s => f"$s%.04s" + +def g(xs: List[Int]): List[String] = + xs.map: case i: Int => i.toString // "relaxed lambda" + .map: + case s: String if s.length > 4 => s + case s => f"$s%.04s" + +class Extra: + val pf: PartialFunction[String, Int] = + case "foo" => 1 + case "bar" => 2 + + def tryit(xs: List[String]) = xs.collect(pf) + +class Possibly(b: Boolean): + val pf: PartialFunction[String, Int] = + if b then + case "foo" => 1 + case "bar" => 2 + else + case "foo" => 42 + case "bar" => 27 + + def tryit(xs: List[String]) = xs.collect(pf) + +class Functional: + val f: Int => PartialFunction[String, Int] = + i => + case _ => i + +@main def main = + println: + f(List(42)) + println: + g(List(42)) + println: + Extra().tryit("baz" :: "bar" :: "foo" :: Nil) + println: + Possibly(false).tryit("baz" :: "bar" :: "foo" :: Nil)