Skip to content

Commit 3fe9304

Browse files
Make better-fors a preview feature in 3.7 (#22776)
Makes the SIP-62 better fors a preview feature in Scala 3.7. Shall be stabilised no earlier then Scala 3.8
1 parent 9e7aab7 commit 3fe9304

File tree

19 files changed

+176
-81
lines changed

19 files changed

+176
-81
lines changed

Diff for: compiler/src/dotty/tools/dotc/ast/Desugar.scala

+10-10
Original file line numberDiff line numberDiff line change
@@ -1953,9 +1953,9 @@ object desugar {
19531953
/** Create tree for for-comprehension `<for (enums) do body>` or
19541954
* `<for (enums) yield body>` where mapName and flatMapName are chosen
19551955
* corresponding to whether this is a for-do or a for-yield.
1956-
* If sourceVersion >= 3.7 are enabled, the creation performs the following rewrite rules:
1956+
* If betterFors are enabled, the creation performs the following rewrite rules:
19571957
*
1958-
* 1. if sourceVersion >= 3.7:
1958+
* 1. if betterFors is enabled:
19591959
*
19601960
* for () do E ==> E
19611961
* or
@@ -1986,13 +1986,13 @@ object desugar {
19861986
* ==>
19871987
* for (P <- G.withFilter (P => E); ...) ...
19881988
*
1989-
* 6. For any N, if sourceVersion >= 3.7:
1989+
* 6. For any N, if betterFors is enabled:
19901990
*
19911991
* for (P <- G; P_1 = E_1; ... P_N = E_N; P1 <- G1; ...) ...
19921992
* ==>
19931993
* G.flatMap (P => for (P_1 = E_1; ... P_N = E_N; ...))
19941994
*
1995-
* 7. For any N, if sourceVersion >= 3.7:
1995+
* 7. For any N, if betterFors is enabled:
19961996
*
19971997
* for (P <- G; P_1 = E_1; ... P_N = E_N) ...
19981998
* ==>
@@ -2013,7 +2013,7 @@ object desugar {
20132013
* If any of the P_i are variable patterns, the corresponding `x_i @ P_i` is not generated
20142014
* and the variable constituting P_i is used instead of x_i
20152015
*
2016-
* 9. For any N, if sourceVersion >= 3.7:
2016+
* 9. For any N, if betterFors is enabled:
20172017
*
20182018
* for (P_1 = E_1; ... P_N = E_N; ...)
20192019
* ==>
@@ -2157,15 +2157,15 @@ object desugar {
21572157
case _ => false
21582158

21592159
def markTrailingMap(aply: Apply, gen: GenFrom, selectName: TermName): Unit =
2160-
if sourceVersion.isAtLeast(`3.7`)
2160+
if sourceVersion.enablesBetterFors
21612161
&& selectName == mapName
21622162
&& gen.checkMode != GenCheckMode.Filtered // results of withFilter have the wrong type
21632163
&& (deepEquals(gen.pat, body) || deepEquals(body, Tuple(Nil)))
21642164
then
21652165
aply.putAttachment(TrailingForMap, ())
21662166

21672167
enums match {
2168-
case Nil if sourceVersion.isAtLeast(`3.7`) => body
2168+
case Nil if sourceVersion.enablesBetterFors => body
21692169
case (gen: GenFrom) :: Nil =>
21702170
val aply = Apply(rhsSelect(gen, mapName), makeLambda(gen, body))
21712171
markTrailingMap(aply, gen, mapName)
@@ -2174,7 +2174,7 @@ object desugar {
21742174
val cont = makeFor(mapName, flatMapName, rest, body)
21752175
Apply(rhsSelect(gen, flatMapName), makeLambda(gen, cont))
21762176
case (gen: GenFrom) :: rest
2177-
if sourceVersion.isAtLeast(`3.7`)
2177+
if sourceVersion.enablesBetterFors
21782178
&& rest.dropWhile(_.isInstanceOf[GenAlias]).headOption.forall(e => e.isInstanceOf[GenFrom]) // possible aliases followed by a generator or end of for
21792179
&& !rest.takeWhile(_.isInstanceOf[GenAlias]).exists(a => isNestedGivenPattern(a.asInstanceOf[GenAlias].pat)) =>
21802180
val cont = makeFor(mapName, flatMapName, rest, body)
@@ -2202,9 +2202,9 @@ object desugar {
22022202
makeFor(mapName, flatMapName, vfrom1 :: rest1, body)
22032203
case (gen: GenFrom) :: test :: rest =>
22042204
val filtered = Apply(rhsSelect(gen, nme.withFilter), makeLambda(gen, test))
2205-
val genFrom = GenFrom(gen.pat, filtered, if sourceVersion.isAtLeast(`3.7`) then GenCheckMode.Filtered else GenCheckMode.Ignore)
2205+
val genFrom = GenFrom(gen.pat, filtered, if sourceVersion.enablesBetterFors then GenCheckMode.Filtered else GenCheckMode.Ignore)
22062206
makeFor(mapName, flatMapName, genFrom :: rest, body)
2207-
case GenAlias(_, _) :: _ if sourceVersion.isAtLeast(`3.7`) =>
2207+
case GenAlias(_, _) :: _ if sourceVersion.enablesBetterFors =>
22082208
val (valeqs, rest) = enums.span(_.isInstanceOf[GenAlias])
22092209
val pats = valeqs.map { case GenAlias(pat, _) => pat }
22102210
val rhss = valeqs.map { case GenAlias(_, rhs) => rhs }

Diff for: compiler/src/dotty/tools/dotc/config/SourceVersion.scala

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package dotc
33
package config
44

55
import core.Decorators.*
6+
import core.Contexts.*
7+
import Feature.isPreviewEnabled
68
import util.Property
79

810
enum SourceVersion:
@@ -35,6 +37,7 @@ enum SourceVersion:
3537
def enablesClauseInterleaving = isAtLeast(`3.6`)
3638
def enablesNewGivens = isAtLeast(`3.6`)
3739
def enablesNamedTuples = isAtLeast(`3.7`)
40+
def enablesBetterFors(using Context) = isAtLeast(`3.7`) && isPreviewEnabled
3841

3942
object SourceVersion extends Property.Key[SourceVersion]:
4043
def defaultSourceVersion = `3.7`

Diff for: compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -2956,7 +2956,7 @@ object Parsers {
29562956
/** Enumerators ::= Generator {semi Enumerator | Guard}
29572957
*/
29582958
def enumerators(): List[Tree] =
2959-
if sourceVersion.isAtLeast(`3.7`) then
2959+
if sourceVersion.enablesBetterFors then
29602960
aliasesUntilGenerator() ++ enumeratorsRest()
29612961
else
29622962
generator() :: enumeratorsRest()

Diff for: compiler/test/dotty/tools/debug/DebugTests.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ class DebugTests:
1818
implicit val testGroup: TestGroup = TestGroup("debug")
1919
CompilationTest.aggregateTests(
2020
compileFile("tests/debug-custom-args/eval-explicit-nulls.scala", TestConfiguration.explicitNullsOptions),
21-
compileFilesInDir("tests/debug", TestConfiguration.defaultOptions)
21+
compileFilesInDir("tests/debug", TestConfiguration.defaultOptions),
22+
compileFilesInDir("tests/debug-preview", TestConfiguration.defaultOptions.and("-preview"))
2223
).checkDebug()
2324

2425
object DebugTests extends ParallelTesting:

Diff for: docs/_docs/reference/changed-features/better-fors.md renamed to docs/_docs/reference/preview/better-fors.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
22
layout: doc-page
33
title: "Better fors"
4-
nightlyOf: https://docs.scala-lang.org/scala3/reference/changed-features/better-fors.html
4+
nightlyOf: https://docs.scala-lang.org/scala3/reference/preview/better-fors.html
55
---
66

7-
Starting in Scala `3.7`, the usability of `for`-comprehensions is improved.
7+
Starting in Scala `3.7` under `-preview` mode, the usability of `for`-comprehensions is improved.
88

99
The biggest user facing change is the new ability to start `for`-comprehensions with aliases. This means that the following previously invalid code is now valid:
1010

Diff for: docs/_docs/reference/preview/overview.md

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
layout: doc-page
3+
title: "Preview"
4+
nightlyOf: https://docs.scala-lang.org/scala3/reference/preview/overview.html
5+
---
6+
7+
## Preview language features
8+
9+
New Scala language features or standard library APIs are initially introduced as experimental, but once they become fully implemented and accepted by the [SIP](https://docs.scala-lang.org/sips/) these can become a preview features.
10+
11+
Preview language features and APIs are guaranteed to be standardized in some next Scala minor release, but allow the compiler team to introduce small, possibly binary incompatible, changes based on the community feedback.
12+
These can be used by early adopters who can accept the possibility of binary compatibility breakage. For instance, preview features could be used in some internal tool or application. On the other hand, preview features are discouraged in publicly available libraries.
13+
14+
More information about preview featues can be found in [preview defintions guide](../other-new-features/preview-defs.md)
15+
16+
### `-preview` compiler flag
17+
18+
This flag enables the use of all preview language feature in the project.
19+
20+
21+
## List of available preview features
22+
23+
* [`better-fors`](./better-fors.md): Enables new for-comprehension behaviour under SIP-62 under `-source:3.7` or later
24+

Diff for: docs/sidebar.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ subsection:
116116
- page: reference/changed-features/lazy-vals-init.md
117117
- page: reference/changed-features/main-functions.md
118118
- page: reference/changed-features/interpolation-escapes.md
119-
- page: reference/changed-features/better-fors.md
120119
- title: Dropped Features
121120
index: reference/dropped-features/dropped-features.md
122121
subsection:
@@ -140,6 +139,11 @@ subsection:
140139
- page: reference/dropped-features/nonlocal-returns.md
141140
- page: reference/dropped-features/this-qualifier.md
142141
- page: reference/dropped-features/wildcard-init.md
142+
- title: Preview Features
143+
directory: preview
144+
index: reference/preview/overview.md
145+
subsection:
146+
- page: reference/preview/better-fors.md
143147
- title: Experimental Features
144148
directory: experimental
145149
index: reference/experimental/overview.md

Diff for: library/src/scala/runtime/stdLibPatches/language.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ object language:
140140
* @see [[https://github.com/scala/improvement-proposals/pull/79]]
141141
*/
142142
@compileTimeOnly("`betterFors` can only be used at compile time in import statements")
143-
@deprecated("The `experimental.betterFors` language import is no longer needed since the feature is now standard", since = "3.7")
143+
@deprecated("The `experimental.betterFors` language import no longer has any effect, the feature is being stablised and can be enabled using `-preview` flag", since = "3.7")
144144
object betterFors
145145

146146
/** Experimental support for package object values

Diff for: presentation-compiler/test/dotty/tools/pc/tests/tokens/SemanticTokensSuite.scala

+4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package dotty.tools.pc.tests.tokens
22

33
import dotty.tools.pc.base.BaseSemanticTokensSuite
4+
import java.nio.file.Path
45

56
import org.junit.Test
67

78
class SemanticTokensSuite extends BaseSemanticTokensSuite:
9+
// -preview required for `for-comprehension` test
10+
override protected def scalacOptions(classpath: Seq[Path]): Seq[String] =
11+
super.scalacOptions(classpath) ++ Seq("-preview")
812

913
@Test def `class, object, var, val(readonly), method, type, parameter, String(single-line)` =
1014
check(

Diff for: tests/debug-preview/eval-in-for-comprehension.check

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
break Test$ 5 // in main
2+
eval list(0)
3+
result 1
4+
// TODO can we remove debug line in adapted methods?
5+
break Test$ 5 // in main$$anonfun$adapted$1
6+
break Test$ 6 // in main$$anonfun$1
7+
eval list(0)
8+
result 1
9+
eval x
10+
result 1
11+
break Test$ 7 // in main$$anonfun$1$$anonfun$1
12+
eval x + y
13+
result 2
14+
15+
break Test$ 11 // in main$$anonfun$2
16+
eval x
17+
result 1
18+
19+
break Test$ 13 // in main
20+
eval list(0)
21+
result 1
22+
break Test$ 13 // in main$$anonfun$4
23+
24+
break Test$ 14 // in main
25+
eval list(0)
26+
result 1
27+
break Test$ 14 // in main$$anonfun$5

Diff for: tests/debug-preview/eval-in-for-comprehension.scala

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
object Test:
2+
def main(args: Array[String]): Unit =
3+
val list = List(1)
4+
for
5+
x <- list
6+
y <- list
7+
z = x + y
8+
yield x
9+
for
10+
x <- list
11+
if x == 1
12+
yield x
13+
for x <- list yield x
14+
for x <- list do println(x)

Diff for: tests/debug/eval-in-for-comprehension.check

+7
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,16 @@ eval list(0)
88
result 1
99
eval x
1010
result 1
11+
break Test$ 6 // in main$$anonfun$1$$anonfun$adapted$1
1112
break Test$ 7 // in main$$anonfun$1$$anonfun$1
1213
eval x + y
1314
result 2
15+
// TODO this line position does not make any sense
16+
break Test$ 6 // in main$$anonfun$1$$anonfun$1
17+
break Test$ 7 // in main$$anonfun$1$$anonfun$1
18+
break Test$ 6 // in main$$anonfun$1$$anonfun$2
19+
break Test$ 6 // in main$$anonfun$1$$anonfun$2
20+
break Test$ 7 // in main$$anonfun$1$$anonfun$2
1421

1522
break Test$ 11 // in main$$anonfun$2
1623
eval x

Diff for: tests/pos/better-fors-given.scala

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//> using options -preview
2+
13
@main def Test: Unit =
24
for
35
x <- Option(23 -> "abc")

Diff for: tests/pos/better-fors-i21804.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import scala.language.experimental.betterFors
1+
//> using options -preview
2+
// import scala.language.experimental.betterFors
23

34
case class Container[A](val value: A) {
45
def map[B](f: A => B): Container[B] = Container(f(value))

Diff for: tests/run/better-fors-map-elim.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import scala.language.experimental.betterFors
1+
//> using options -preview
2+
// import scala.language.experimental.betterFors
23

34
class myOptionModule(doOnMap: => Unit) {
45
sealed trait MyOption[+A] {

Diff for: tests/run/better-fors.scala

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
//> using options -preview
2+
// import scala.language.experimental.betterFors
3+
14
def for1 =
25
for {
36
a = 1

Diff for: tests/run/fors.scala

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//> using options -preview
12
//############################################################################
23
// for-comprehensions (old and new syntax)
34
//############################################################################

Diff for: tests/semanticdb/expect/ForComprehension.expect.scala

+26-26
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,43 @@ package example
33
class ForComprehension/*<-example::ForComprehension#*/ {
44
for {
55
a/*<-local0*/ <- List/*->scala::package.List.*/(1)
6-
b/*<-local1*/ <- List/*->scala::package.List.*/(1)
6+
b/*<-local1*//*->local1*/ <- List/*->scala::package.List.*/(1)
77
if b/*->local1*/ >/*->scala::Int#`>`(+3).*/ 1
8-
c/*<-local2*/ = a/*->local0*/ +/*->scala::Int#`+`(+4).*/ b/*->local1*/
8+
c/*<-local2*//*->local2*/ = a/*->local0*/ +/*->scala::Int#`+`(+4).*/ b/*->local1*/
99
} yield (a/*->local0*/, b/*->local1*/, c/*->local2*/)
1010
for {
11-
a/*<-local3*/ <- List/*->scala::package.List.*/(1)
12-
b/*<-local4*/ <- List/*->scala::package.List.*/(a/*->local3*/)
11+
a/*<-local4*/ <- List/*->scala::package.List.*/(1)
12+
b/*<-local5*/ <- List/*->scala::package.List.*/(a/*->local4*/)
1313
if (
14-
a/*->local3*/,
15-
b/*->local4*/
14+
a/*->local4*/,
15+
b/*->local5*/
1616
) ==/*->scala::Any#`==`().*/ (1, 2)
1717
(
18-
c/*<-local6*/,
19-
d/*<-local7*/
20-
) <- List/*->scala::package.List.*/((a/*->local3*/, b/*->local4*/))
18+
c/*<-local7*/,
19+
d/*<-local8*/
20+
) <- List/*->scala::package.List.*/((a/*->local4*/, b/*->local5*/))
2121
if (
22-
a/*->local3*/,
23-
b/*->local4*/,
24-
c/*->local6*/,
25-
d/*->local7*/
22+
a/*->local4*/,
23+
b/*->local5*/,
24+
c/*->local7*/,
25+
d/*->local8*/
2626
) ==/*->scala::Any#`==`().*/ (1, 2, 3, 4)
27-
e/*<-local8*//*->local8*/ = (
28-
a/*->local3*/,
29-
b/*->local4*/,
30-
c/*->local6*/,
31-
d/*->local7*/
27+
e/*<-local9*//*->local9*/ = (
28+
a/*->local4*/,
29+
b/*->local5*/,
30+
c/*->local7*/,
31+
d/*->local8*/
3232
)
33-
if e/*->local8*/ ==/*->scala::Any#`==`().*/ (1, 2, 3, 4)
34-
f/*<-local9*/ <- List/*->scala::package.List.*/(e/*->local8*/)
33+
if e/*->local9*/ ==/*->scala::Any#`==`().*/ (1, 2, 3, 4)
34+
f/*<-local10*/ <- List/*->scala::package.List.*/(e/*->local9*/)
3535
} yield {
3636
(
37-
a/*->local3*/,
38-
b/*->local4*/,
39-
c/*->local6*/,
40-
d/*->local7*/,
41-
e/*->local8*/,
42-
f/*->local9*/
37+
a/*->local4*/,
38+
b/*->local5*/,
39+
c/*->local7*/,
40+
d/*->local8*/,
41+
e/*->local9*/,
42+
f/*->local10*/
4343
)
4444
}
4545
}

0 commit comments

Comments
 (0)