Skip to content

Make better-fors a preview feature in 3.7 #22776

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 5 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
20 changes: 10 additions & 10 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1953,9 +1953,9 @@ object desugar {
/** Create tree for for-comprehension `<for (enums) do body>` or
* `<for (enums) yield body>` where mapName and flatMapName are chosen
* corresponding to whether this is a for-do or a for-yield.
* If sourceVersion >= 3.7 are enabled, the creation performs the following rewrite rules:
* If betterFors are enabled, the creation performs the following rewrite rules:
*
* 1. if sourceVersion >= 3.7:
* 1. if betterFors is enabled:
*
* for () do E ==> E
* or
Expand Down Expand Up @@ -1986,13 +1986,13 @@ object desugar {
* ==>
* for (P <- G.withFilter (P => E); ...) ...
*
* 6. For any N, if sourceVersion >= 3.7:
* 6. For any N, if betterFors is enabled:
*
* for (P <- G; P_1 = E_1; ... P_N = E_N; P1 <- G1; ...) ...
* ==>
* G.flatMap (P => for (P_1 = E_1; ... P_N = E_N; ...))
*
* 7. For any N, if sourceVersion >= 3.7:
* 7. For any N, if betterFors is enabled:
*
* for (P <- G; P_1 = E_1; ... P_N = E_N) ...
* ==>
Expand All @@ -2013,7 +2013,7 @@ object desugar {
* If any of the P_i are variable patterns, the corresponding `x_i @ P_i` is not generated
* and the variable constituting P_i is used instead of x_i
*
* 9. For any N, if sourceVersion >= 3.7:
* 9. For any N, if betterFors is enabled:
*
* for (P_1 = E_1; ... P_N = E_N; ...)
* ==>
Expand Down Expand Up @@ -2157,15 +2157,15 @@ object desugar {
case _ => false

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

enums match {
case Nil if sourceVersion.isAtLeast(`3.7`) => body
case Nil if sourceVersion.enablesBetterFors => body
case (gen: GenFrom) :: Nil =>
val aply = Apply(rhsSelect(gen, mapName), makeLambda(gen, body))
markTrailingMap(aply, gen, mapName)
Expand All @@ -2174,7 +2174,7 @@ object desugar {
val cont = makeFor(mapName, flatMapName, rest, body)
Apply(rhsSelect(gen, flatMapName), makeLambda(gen, cont))
case (gen: GenFrom) :: rest
if sourceVersion.isAtLeast(`3.7`)
if sourceVersion.enablesBetterFors
&& rest.dropWhile(_.isInstanceOf[GenAlias]).headOption.forall(e => e.isInstanceOf[GenFrom]) // possible aliases followed by a generator or end of for
&& !rest.takeWhile(_.isInstanceOf[GenAlias]).exists(a => isNestedGivenPattern(a.asInstanceOf[GenAlias].pat)) =>
val cont = makeFor(mapName, flatMapName, rest, body)
Expand Down Expand Up @@ -2202,9 +2202,9 @@ object desugar {
makeFor(mapName, flatMapName, vfrom1 :: rest1, body)
case (gen: GenFrom) :: test :: rest =>
val filtered = Apply(rhsSelect(gen, nme.withFilter), makeLambda(gen, test))
val genFrom = GenFrom(gen.pat, filtered, if sourceVersion.isAtLeast(`3.7`) then GenCheckMode.Filtered else GenCheckMode.Ignore)
val genFrom = GenFrom(gen.pat, filtered, if sourceVersion.enablesBetterFors then GenCheckMode.Filtered else GenCheckMode.Ignore)
makeFor(mapName, flatMapName, genFrom :: rest, body)
case GenAlias(_, _) :: _ if sourceVersion.isAtLeast(`3.7`) =>
case GenAlias(_, _) :: _ if sourceVersion.enablesBetterFors =>
val (valeqs, rest) = enums.span(_.isInstanceOf[GenAlias])
val pats = valeqs.map { case GenAlias(pat, _) => pat }
val rhss = valeqs.map { case GenAlias(_, rhs) => rhs }
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/config/SourceVersion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package dotc
package config

import core.Decorators.*
import core.Contexts.*
import Feature.isPreviewEnabled
import util.Property

enum SourceVersion:
Expand Down Expand Up @@ -35,6 +37,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

object SourceVersion extends Property.Key[SourceVersion]:
def defaultSourceVersion = `3.7`
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2956,7 +2956,7 @@ object Parsers {
/** Enumerators ::= Generator {semi Enumerator | Guard}
*/
def enumerators(): List[Tree] =
if sourceVersion.isAtLeast(`3.7`) then
if sourceVersion.enablesBetterFors then
aliasesUntilGenerator() ++ enumeratorsRest()
else
generator() :: enumeratorsRest()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
layout: doc-page
title: "Better fors"
nightlyOf: https://docs.scala-lang.org/scala3/reference/changed-features/better-fors.html
nightlyOf: https://docs.scala-lang.org/scala3/reference/preview/better-fors.html
---

Starting in Scala `3.7`, the usability of `for`-comprehensions is improved.
Starting in Scala `3.7` under `-preview` mode, the usability of `for`-comprehensions is 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
24 changes: 24 additions & 0 deletions docs/_docs/reference/preview/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
layout: doc-page
title: "Preview"
nightlyOf: https://docs.scala-lang.org/scala3/reference/preview/overview.html
---

## Preview language features

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.

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.
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.

More information about preview featues can be found in [preview defintions guide](../other-new-features/preview-defs.md)

### `-preview` compiler flag

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

6 changes: 5 additions & 1 deletion docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ subsection:
- page: reference/changed-features/lazy-vals-init.md
- page: reference/changed-features/main-functions.md
- page: reference/changed-features/interpolation-escapes.md
- page: reference/changed-features/better-fors.md
- title: Dropped Features
index: reference/dropped-features/dropped-features.md
subsection:
Expand All @@ -140,6 +139,11 @@ subsection:
- page: reference/dropped-features/nonlocal-returns.md
- page: reference/dropped-features/this-qualifier.md
- page: reference/dropped-features/wildcard-init.md
- 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
2 changes: 1 addition & 1 deletion library/src/scala/runtime/stdLibPatches/language.scala
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ object language:
* @see [[https://github.com/scala/improvement-proposals/pull/79]]
*/
@compileTimeOnly("`betterFors` can only be used at compile time in import statements")
@deprecated("The `experimental.betterFors` language import is no longer needed since the feature is now standard", since = "3.7")
@deprecated("The `experimental.betterFors` language import is no longer needed since the feature is now in preview", since = "3.7")
Copy link
Member

Choose a reason for hiding this comment

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

Should this message direct the user to how the preview feature can be used? I don't think most people know how it works. (I don't fully know how it works. Is there a more fine-grained way to enable preview? Like with a language import?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll change to message to give more context
There is no way to enable selectively preview features - you either enable all or none of them. The reasoning for that is the feature is ready, but we (compiler team) don't want yet to want to enforce on us backward binary compatibility. This gives us room for changes, and the users have a sneak peak into next minor to test it out - I assume typically preview period would take 1 minor release cycle.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, thanks. Though I meant fine grained scope-wise, not feature-wise. The analogy being language import vs language option.

object betterFors

/** Experimental support for package object values
Expand Down
2 changes: 2 additions & 0 deletions tests/pos/better-fors-given.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//> using options -preview

@main def Test: Unit =
for
x <- Option(23 -> "abc")
Expand Down
3 changes: 2 additions & 1 deletion tests/pos/better-fors-i21804.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import scala.language.experimental.betterFors
//> 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: 2 additions & 1 deletion tests/run/better-fors-map-elim.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import scala.language.experimental.betterFors
//> using options -preview
// import scala.language.experimental.betterFors

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

def for1 =
for {
a = 1
Expand Down
1 change: 1 addition & 0 deletions tests/run/fors.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//> using options -preview
//############################################################################
// for-comprehensions (old and new syntax)
//############################################################################
Expand Down
Loading