Skip to content

Make SIP-62 better-fors a stable feature #23630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/SourceVersion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ enum SourceVersion:
def enablesClauseInterleaving = isAtLeast(`3.6`)
def enablesNewGivens = isAtLeast(`3.6`)
def enablesNamedTuples = isAtLeast(`3.7`)
def enablesBetterFors(using Context) = isAtLeast(`3.7`) && isPreviewEnabled
def enablesBetterFors = isAtLeast(`3.8`)
Copy link
Contributor Author

@WojciechMazur WojciechMazur Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder should we also enable it under -source:3.7, -preview - it makes sense to me as well

Suggested change
def enablesBetterFors = isAtLeast(`3.8`)
def enablesBetterFors(using Context) = isAtLeast(`3.8`) || ( isAtLeast(`3.7`) && isPreviewEnabled)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense to me, too


def requiresNewSyntax = isAtLeast(future)

Expand Down
1 change: 0 additions & 1 deletion compiler/test/dotty/tools/debug/DebugTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class DebugTests:
CompilationTest.aggregateTests(
compileFile("tests/debug-custom-args/eval-explicit-nulls.scala", TestConfiguration.explicitNullsOptions),
compileFilesInDir("tests/debug", TestConfiguration.defaultOptions),
compileFilesInDir("tests/debug-preview", TestConfiguration.defaultOptions.and("-preview"))
).checkDebug()

object DebugTests extends ParallelTesting:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: "Better fors"
nightlyOf: https://docs.scala-lang.org/scala3/reference/preview/better-fors.html
---

Starting in Scala `3.7` under `-preview` mode, the usability of `for`-comprehensions is improved.
Starting in Scala `3.8` or under `-preview` mode using Scala `3.7`, the usability of `for`-comprehensions has improved.

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:

Expand Down
3 changes: 1 addition & 2 deletions docs/_docs/reference/preview/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,4 @@ This flag enables the use of all preview language feature in the project.

## List of available preview features

* [`better-fors`](./better-fors.md): Enables new for-comprehension behaviour under SIP-62 under `-source:3.7` or later

Currently there are no available preview features.
3 changes: 1 addition & 2 deletions docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ subsection:
- page: reference/other-new-features/preview-defs.md
- page: reference/other-new-features/binary-literals.md
- page: reference/other-new-features/toplevel-definitions.md
- page: reference/other-new-features/better-fors.md
- title: Other Changed Features
directory: changed-features
index: reference/changed-features/changed-features.md
Expand Down Expand Up @@ -143,8 +144,6 @@ subsection:
- title: Preview Features
directory: preview
index: reference/preview/overview.md
subsection:
- page: reference/preview/better-fors.md
- title: Experimental Features
directory: experimental
index: reference/experimental/overview.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import java.nio.file.Path
import org.junit.Test

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

@Test def `class, object, var, val(readonly), method, type, parameter, String(single-line)` =
check(
Expand Down
27 changes: 0 additions & 27 deletions tests/debug-preview/eval-in-for-comprehension.check

This file was deleted.

14 changes: 0 additions & 14 deletions tests/debug-preview/eval-in-for-comprehension.scala

This file was deleted.

7 changes: 0 additions & 7 deletions tests/debug/eval-in-for-comprehension.check
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,9 @@ eval list(0)
result 1
eval x
result 1
break Test$ 6 // in main$$anonfun$1$$anonfun$adapted$1
break Test$ 7 // in main$$anonfun$1$$anonfun$1
eval x + y
result 2
// TODO this line position does not make any sense
break Test$ 6 // in main$$anonfun$1$$anonfun$1
break Test$ 7 // in main$$anonfun$1$$anonfun$1
break Test$ 6 // in main$$anonfun$1$$anonfun$2
break Test$ 6 // in main$$anonfun$1$$anonfun$2
break Test$ 7 // in main$$anonfun$1$$anonfun$2

break Test$ 11 // in main$$anonfun$2
eval x
Expand Down
2 changes: 0 additions & 2 deletions tests/pos/better-fors-given.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//> using options -preview

@main def Test: Unit =
for
x <- Option(23 -> "abc")
Expand Down
3 changes: 0 additions & 3 deletions tests/pos/better-fors-i21804.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//> using options -preview
// import scala.language.experimental.betterFors

case class Container[A](val value: A) {
def map[B](f: A => B): Container[B] = Container(f(value))
}
Expand Down
3 changes: 0 additions & 3 deletions tests/run/better-fors-map-elim.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//> using options -preview
// import scala.language.experimental.betterFors

class myOptionModule(doOnMap: => Unit) {
sealed trait MyOption[+A] {
def map[B](f: A => B): MyOption[B] = this match {
Expand Down
3 changes: 0 additions & 3 deletions tests/run/better-fors.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//> using options -preview
// import scala.language.experimental.betterFors

def for1 =
for {
a = 1
Expand Down
1 change: 0 additions & 1 deletion tests/run/fors.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//> using options -preview
//############################################################################
// for-comprehensions (old and new syntax)
//############################################################################
Expand Down
52 changes: 26 additions & 26 deletions tests/semanticdb/expect/ForComprehension.expect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,43 @@ package example
class ForComprehension/*<-example::ForComprehension#*/ {
for {
a/*<-local0*/ <- List/*->scala::package.List.*/(1)
b/*<-local1*//*->local1*/ <- List/*->scala::package.List.*/(1)
b/*<-local1*/ <- List/*->scala::package.List.*/(1)
if b/*->local1*/ >/*->scala::Int#`>`(+3).*/ 1
c/*<-local2*//*->local2*/ = a/*->local0*/ +/*->scala::Int#`+`(+4).*/ b/*->local1*/
c/*<-local2*/ = a/*->local0*/ +/*->scala::Int#`+`(+4).*/ b/*->local1*/
} yield (a/*->local0*/, b/*->local1*/, c/*->local2*/)
for {
a/*<-local4*/ <- List/*->scala::package.List.*/(1)
b/*<-local5*/ <- List/*->scala::package.List.*/(a/*->local4*/)
a/*<-local3*/ <- List/*->scala::package.List.*/(1)
b/*<-local4*/ <- List/*->scala::package.List.*/(a/*->local3*/)
if (
a/*->local4*/,
b/*->local5*/
a/*->local3*/,
b/*->local4*/
) ==/*->scala::Any#`==`().*/ (1, 2)
(
c/*<-local7*/,
d/*<-local8*/
) <- List/*->scala::package.List.*/((a/*->local4*/, b/*->local5*/))
c/*<-local6*/,
d/*<-local7*/
) <- List/*->scala::package.List.*/((a/*->local3*/, b/*->local4*/))
if (
a/*->local4*/,
b/*->local5*/,
c/*->local7*/,
d/*->local8*/
a/*->local3*/,
b/*->local4*/,
c/*->local6*/,
d/*->local7*/
) ==/*->scala::Any#`==`().*/ (1, 2, 3, 4)
e/*<-local9*//*->local9*/ = (
a/*->local4*/,
b/*->local5*/,
c/*->local7*/,
d/*->local8*/
e/*<-local8*//*->local8*/ = (
a/*->local3*/,
b/*->local4*/,
c/*->local6*/,
d/*->local7*/
)
if e/*->local9*/ ==/*->scala::Any#`==`().*/ (1, 2, 3, 4)
f/*<-local10*/ <- List/*->scala::package.List.*/(e/*->local9*/)
if e/*->local8*/ ==/*->scala::Any#`==`().*/ (1, 2, 3, 4)
f/*<-local9*/ <- List/*->scala::package.List.*/(e/*->local8*/)
} yield {
(
a/*->local4*/,
b/*->local5*/,
c/*->local7*/,
d/*->local8*/,
e/*->local9*/,
f/*->local10*/
a/*->local3*/,
b/*->local4*/,
c/*->local6*/,
d/*->local7*/,
e/*->local8*/,
f/*->local9*/
)
}
}
82 changes: 38 additions & 44 deletions tests/semanticdb/metac.expect
Original file line number Diff line number Diff line change
Expand Up @@ -1682,24 +1682,23 @@ Schema => SemanticDB v4
Uri => ForComprehension.scala
Text => empty
Language => Scala
Symbols => 13 entries
Occurrences => 53 entries
Synthetics => 23 entries
Symbols => 12 entries
Occurrences => 51 entries
Synthetics => 22 entries

Symbols:
example/ForComprehension# => class ForComprehension extends Object { self: ForComprehension => +1 decls }
example/ForComprehension#`<init>`(). => primary ctor <init> (): ForComprehension
local0 => param a: Int
local1 => param b: Int
local2 => val local c: Int
local3 => param x$1: Tuple2[Int, Int]
local4 => param a: Int
local5 => param b: Int
local6 => param x$1: Tuple2[Int, Int]
local7 => val local c: Int
local8 => val local d: Int
local9 => val local e: Tuple4[Int, Int, Int, Int]
local10 => param f: Tuple4[Int, Int, Int, Int]
local3 => param a: Int
local4 => param b: Int
local5 => param x$1: Tuple2[Int, Int]
local6 => val local c: Int
local7 => val local d: Int
local8 => val local e: Tuple4[Int, Int, Int, Int]
local9 => param f: Tuple4[Int, Int, Int, Int]

Occurrences:
[0:8..0:15): example <- example/
Expand All @@ -1708,60 +1707,55 @@ Occurrences:
[4:4..4:5): a <- local0
[4:9..4:13): List -> scala/package.List.
[5:4..5:5): b <- local1
[5:4..5:5): b -> local1
[5:9..5:13): List -> scala/package.List.
[6:7..6:8): b -> local1
[6:9..6:10): > -> scala/Int#`>`(+3).
[7:4..7:5): c <- local2
[7:4..7:5): c -> local2
[7:8..7:9): a -> local0
[7:10..7:11): + -> scala/Int#`+`(+4).
[7:12..7:13): b -> local1
[8:11..8:12): a -> local0
[8:14..8:15): b -> local1
[8:17..8:18): c -> local2
[10:4..10:5): a <- local4
[10:4..10:5): a <- local3
[10:9..10:13): List -> scala/package.List.
[11:4..11:5): b <- local5
[11:4..11:5): b <- local4
[11:9..11:13): List -> scala/package.List.
[11:14..11:15): a -> local4
[13:6..13:7): a -> local4
[14:6..14:7): b -> local5
[11:14..11:15): a -> local3
[13:6..13:7): a -> local3
[14:6..14:7): b -> local4
[15:6..15:8): == -> scala/Any#`==`().
[17:6..17:7): c <- local7
[18:6..18:7): d <- local8
[17:6..17:7): c <- local6
[18:6..18:7): d <- local7
[19:9..19:13): List -> scala/package.List.
[19:15..19:16): a -> local4
[19:18..19:19): b -> local5
[21:6..21:7): a -> local4
[22:6..22:7): b -> local5
[23:6..23:7): c -> local7
[24:6..24:7): d -> local8
[19:15..19:16): a -> local3
[19:18..19:19): b -> local4
[21:6..21:7): a -> local3
[22:6..22:7): b -> local4
[23:6..23:7): c -> local6
[24:6..24:7): d -> local7
[25:6..25:8): == -> scala/Any#`==`().
[26:4..26:5): e <- local9
[26:4..26:5): e -> local9
[27:6..27:7): a -> local4
[28:6..28:7): b -> local5
[29:6..29:7): c -> local7
[30:6..30:7): d -> local8
[32:7..32:8): e -> local9
[26:4..26:5): e <- local8
[26:4..26:5): e -> local8
[27:6..27:7): a -> local3
[28:6..28:7): b -> local4
[29:6..29:7): c -> local6
[30:6..30:7): d -> local7
[32:7..32:8): e -> local8
[32:9..32:11): == -> scala/Any#`==`().
[33:4..33:5): f <- local10
[33:4..33:5): f <- local9
[33:9..33:13): List -> scala/package.List.
[33:14..33:15): e -> local9
[36:6..36:7): a -> local4
[37:6..37:7): b -> local5
[38:6..38:7): c -> local7
[39:6..39:7): d -> local8
[40:6..40:7): e -> local9
[41:6..41:7): f -> local10
[33:14..33:15): e -> local8
[36:6..36:7): a -> local3
[37:6..37:7): b -> local4
[38:6..38:7): c -> local6
[39:6..39:7): d -> local7
[40:6..40:7): e -> local8
[41:6..41:7): f -> local9

Synthetics:
[4:9..4:13):List => *.apply[Int]
[4:9..4:16):List(1) => List.apply[Int](*)
[5:4..7:5):b <- List(1)
if b > 1
c => Tuple2.apply[Int, Int](*)
[5:9..5:13):List => *.apply[Int]
[5:9..5:16):List(1) => List.apply[Int](*)
[8:10..8:19):(a, b, c) => Tuple3.apply[Int, Int, Int](*)
Expand Down
Loading