Skip to content

Commit 87f0078

Browse files
Make SIP-62 - betterFors a standard feature (#22652)
- Deprecate experimental language import - Make betterFors conditional on `-source >= 3.7` instead - Drop the `experimental.betterFors` import from tests
1 parent 6887748 commit 87f0078

File tree

13 files changed

+164
-104
lines changed

13 files changed

+164
-104
lines changed

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

+23-12
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import NameKinds.{UniqueName, ContextBoundParamName, ContextFunctionParamName, D
1111
import typer.{Namer, Checking}
1212
import util.{Property, SourceFile, SourcePosition, SrcPos, Chars}
1313
import config.{Feature, Config}
14-
import config.Feature.{sourceVersion, migrateTo3, enabled, betterForsEnabled}
14+
import config.Feature.{sourceVersion, migrateTo3, enabled}
1515
import config.SourceVersion.*
1616
import collection.mutable
1717
import reporting.*
@@ -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 betterFors are enabled, the creation performs the following rewrite rules:
1956+
* If sourceVersion >= 3.7 are enabled, the creation performs the following rewrite rules:
19571957
*
1958-
* 1. if betterFors is enabled:
1958+
* 1. if sourceVersion >= 3.7:
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 betterFors is enabled:
1989+
* 6. For any N, if sourceVersion >= 3.7:
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 betterFors is enabled:
1995+
* 7. For any N, if sourceVersion >= 3.7:
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 betterFors is enabled:
2016+
* 9. For any N, if sourceVersion >= 3.7:
20172017
*
20182018
* for (P_1 = E_1; ... P_N = E_N; ...)
20192019
* ==>
@@ -2044,6 +2044,16 @@ object desugar {
20442044
makeCaseLambda(CaseDef(gen.pat, EmptyTree, body) :: Nil, matchCheckMode)
20452045
}
20462046

2047+
def hasGivenBind(pat: Tree): Boolean = pat.existsSubTree {
2048+
case pat @ Bind(_, pat1) => pat.mods.is(Given)
2049+
case _ => false
2050+
}
2051+
2052+
/** Does this pattern define any given bindings */
2053+
def isNestedGivenPattern(pat: Tree): Boolean = pat match
2054+
case pat @ Bind(_, pat1) => hasGivenBind(pat1)
2055+
case _ => hasGivenBind(pat)
2056+
20472057
/** If `pat` is not an Identifier, a Typed(Ident, _), or a Bind, wrap
20482058
* it in a Bind with a fresh name. Return the transformed pattern, and the identifier
20492059
* that refers to the bound variable for the pattern. Wildcard Binds are
@@ -2147,15 +2157,15 @@ object desugar {
21472157
case _ => false
21482158

21492159
def markTrailingMap(aply: Apply, gen: GenFrom, selectName: TermName): Unit =
2150-
if betterForsEnabled
2160+
if sourceVersion.isAtLeast(`3.7`)
21512161
&& selectName == mapName
21522162
&& gen.checkMode != GenCheckMode.Filtered // results of withFilter have the wrong type
21532163
&& (deepEquals(gen.pat, body) || deepEquals(body, Tuple(Nil)))
21542164
then
21552165
aply.putAttachment(TrailingForMap, ())
21562166

21572167
enums match {
2158-
case Nil if betterForsEnabled => body
2168+
case Nil if sourceVersion.isAtLeast(`3.7`) => body
21592169
case (gen: GenFrom) :: Nil =>
21602170
val aply = Apply(rhsSelect(gen, mapName), makeLambda(gen, body))
21612171
markTrailingMap(aply, gen, mapName)
@@ -2164,8 +2174,9 @@ object desugar {
21642174
val cont = makeFor(mapName, flatMapName, rest, body)
21652175
Apply(rhsSelect(gen, flatMapName), makeLambda(gen, cont))
21662176
case (gen: GenFrom) :: rest
2167-
if betterForsEnabled
2168-
&& rest.dropWhile(_.isInstanceOf[GenAlias]).headOption.forall(e => e.isInstanceOf[GenFrom]) => // possible aliases followed by a generator or end of for
2177+
if sourceVersion.isAtLeast(`3.7`)
2178+
&& rest.dropWhile(_.isInstanceOf[GenAlias]).headOption.forall(e => e.isInstanceOf[GenFrom]) // possible aliases followed by a generator or end of for
2179+
&& !rest.takeWhile(_.isInstanceOf[GenAlias]).exists(a => isNestedGivenPattern(a.asInstanceOf[GenAlias].pat)) =>
21692180
val cont = makeFor(mapName, flatMapName, rest, body)
21702181
val selectName =
21712182
if rest.exists(_.isInstanceOf[GenFrom]) then flatMapName
@@ -2191,9 +2202,9 @@ object desugar {
21912202
makeFor(mapName, flatMapName, vfrom1 :: rest1, body)
21922203
case (gen: GenFrom) :: test :: rest =>
21932204
val filtered = Apply(rhsSelect(gen, nme.withFilter), makeLambda(gen, test))
2194-
val genFrom = GenFrom(gen.pat, filtered, if betterForsEnabled then GenCheckMode.Filtered else GenCheckMode.Ignore)
2205+
val genFrom = GenFrom(gen.pat, filtered, if sourceVersion.isAtLeast(`3.7`) then GenCheckMode.Filtered else GenCheckMode.Ignore)
21952206
makeFor(mapName, flatMapName, genFrom :: rest, body)
2196-
case GenAlias(_, _) :: _ if betterForsEnabled =>
2207+
case GenAlias(_, _) :: _ if sourceVersion.isAtLeast(`3.7`) =>
21972208
val (valeqs, rest) = enums.span(_.isInstanceOf[GenAlias])
21982209
val pats = valeqs.map { case GenAlias(pat, _) => pat }
21992210
val rhss = valeqs.map { case GenAlias(_, rhs) => rhs }

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

+1-5
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ object Feature:
3535
val into = experimental("into")
3636
val modularity = experimental("modularity")
3737
val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions")
38-
val betterFors = experimental("betterFors")
3938
val packageObjectValues = experimental("packageObjectValues")
4039

4140
def experimentalAutoEnableFeatures(using Context): List[TermName] =
@@ -62,8 +61,7 @@ object Feature:
6261
(pureFunctions, "Enable pure functions for capture checking"),
6362
(captureChecking, "Enable experimental capture checking"),
6463
(into, "Allow into modifier on parameter types"),
65-
(modularity, "Enable experimental modularity features"),
66-
(betterFors, "Enable improvements in `for` comprehensions")
64+
(modularity, "Enable experimental modularity features")
6765
)
6866

6967
// legacy language features from Scala 2 that are no longer supported.
@@ -118,8 +116,6 @@ object Feature:
118116

119117
def namedTypeArgsEnabled(using Context) = enabled(namedTypeArguments)
120118

121-
def betterForsEnabled(using Context) = enabled(betterFors)
122-
123119
def genericNumberLiteralsEnabled(using Context) = enabled(genericNumberLiterals)
124120

125121
def scala2ExperimentalMacroEnabled(using Context) = enabled(scala2macros)

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 in.featureEnabled(Feature.betterFors) then
2959+
if sourceVersion.isAtLeast(`3.7`) then
29602960
aliasesUntilGenerator() ++ enumeratorsRest()
29612961
else
29622962
generator() :: enumeratorsRest()

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

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

7-
The `betterFors` language extension improves the usability of `for`-comprehensions.
8-
9-
The extension is enabled by the language import `import scala.language.experimental.betterFors` or by setting the command line option `-language:experimental.betterFors`.
7+
Starting in Scala `3.7`, the usability of `for`-comprehensions is improved.
108

119
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:
1210

@@ -30,11 +28,11 @@ for
3028
yield a + b
3129
```
3230

33-
Additionally this extension changes the way `for`-comprehensions are desugared. The desugaring is now done in a more intuitive way and the desugared code can be more efficient, because it avoids some unnecessary method calls. There are two main changes in the desugaring:
31+
Additionally, this extension changes the way `for`-comprehensions are desugared. The desugaring is now done in a more intuitive way and the desugared code can be more efficient, because it avoids some unnecessary method calls. There are two main changes in the desugaring:
3432

3533
1. **Simpler Desugaring for Pure Aliases**:
3634
When an alias is not followed by a guard, the desugaring is simplified. The last generator and the aliases don't have to be wrapped in a tuple, and instead the aliases are simply introduced as local variables in a block with the next generator.
37-
**Current Desugaring**:
35+
**Previous Desugaring**:
3836
```scala
3937
for {
4038
a <- doSth(arg)

Diff for: docs/sidebar.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ 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
119120
- title: Dropped Features
120121
index: reference/dropped-features/dropped-features.md
121122
subsection:
@@ -162,7 +163,6 @@ subsection:
162163
- page: reference/experimental/modularity.md
163164
- page: reference/experimental/typeclasses.md
164165
- page: reference/experimental/runtimeChecked.md
165-
- page: reference/experimental/better-fors.md
166166
- page: reference/experimental/unrolled-defs.md
167167
- page: reference/experimental/package-object-values.md
168168
- page: reference/syntax.md

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

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +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")
143144
object betterFors
144145

145146
/** Experimental support for package object values

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -350,9 +350,9 @@ class SemanticTokensSuite extends BaseSemanticTokensSuite:
350350
|
351351
|object <<B>>/*class*/ {
352352
| val <<a>>/*variable,definition,readonly*/ = for {
353-
| <<foo>>/*variable,definition,readonly*/ <- <<List>>/*class*/("a", "b", "c")
353+
| <<foo>>/*parameter,declaration,readonly*/ <- <<List>>/*class*/("a", "b", "c")
354354
| <<_>>/*class,abstract*/ = <<println>>/*method*/("print!")
355-
| } yield <<foo>>/*variable,readonly*/
355+
| } yield <<foo>>/*parameter,readonly*/
356356
|}
357357
|""".stripMargin
358358
)

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

-7
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,9 @@ eval list(0)
88
result 1
99
eval x
1010
result 1
11-
break Test$ 6 // in main$$anonfun$1$$anonfun$adapted$1
1211
break Test$ 7 // in main$$anonfun$1$$anonfun$1
1312
eval x + y
1413
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
2114

2215
break Test$ 11 // in main$$anonfun$2
2316
eval x

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

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
@main def Test: Unit =
2+
for
3+
x <- Option(23 -> "abc")
4+
(a @ given Int, b @ given String) = x
5+
_ <- Option(1)
6+
yield
7+
assert(summon[Int] == 23)
8+
9+
for
10+
x <- Option((1.3, 23 -> "abc"))
11+
(_, (a @ given Int, b @ given String)) = x
12+
_ <- Option(1)
13+
yield
14+
assert(summon[Int] == 23)
15+
16+
for
17+
x <- Option(Some(23 -> "abc"))
18+
Some(a @ given Int, b @ given String) = x
19+
_ <- Option(1)
20+
yield
21+
assert(summon[Int] == 23)
22+
23+
for
24+
x <- Option(Some(23))
25+
Some(a @ given Int) = x
26+
_ <- Option(1)
27+
yield
28+
assert(summon[Int] == 23)
29+
30+
for
31+
x <- Option(23)
32+
a @ given Int = x
33+
yield
34+
assert(summon[Int] == 23)
35+
36+
for
37+
x <- Option(23)
38+
_ @ given Int = x
39+
yield
40+
assert(summon[Int] == 23)
41+
42+
for
43+
x <- Option(23)
44+
given Int = x
45+
yield
46+
assert(summon[Int] == 23)
47+
48+
for
49+
x <- Option(23)
50+
given Int = x
51+
_ <- Option(1)
52+
yield
53+
assert(summon[Int] == 23)
54+
55+
for
56+
a @ given Int <- Option(23)
57+
yield
58+
assert(summon[Int] == 23)
59+
60+
for
61+
_ @ given Int <- Option(23)
62+
yield
63+
assert(summon[Int] == 23)
64+
65+
for
66+
given Int <- Option(23)
67+
yield
68+
assert(summon[Int] == 23)

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

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import scala.language.experimental.betterFors
2-
31
def for1 =
42
for {
53
a = 1

Diff for: tests/run/fors.scala

-2
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,6 @@ object Test extends App {
113113

114114
/////////////////// elimination of map ///////////////////
115115

116-
import scala.language.experimental.betterFors
117-
118116
@tailrec
119117
def pair[B](xs: List[Int], ys: List[B], n: Int): List[(Int, B)] =
120118
if n == 0 then xs.zip(ys)

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

0 commit comments

Comments
 (0)