Skip to content

Commit 07c821a

Browse files
committed
Add tweaks and docs
1 parent f56aa22 commit 07c821a

File tree

16 files changed

+110
-84
lines changed

16 files changed

+110
-84
lines changed

compiler/src/dotty/tools/dotc/config/Feature.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ object Feature:
3333
val pureFunctions = experimental("pureFunctions")
3434
val captureChecking = experimental("captureChecking")
3535
val into = experimental("into")
36-
val avoidLoopingGivens = experimental("avoidLoopingGivens")
36+
val givenLoopPrevention = experimental("givenLoopPrevention")
3737

3838
val globalOnlyImports: Set[TermName] = Set(pureFunctions, captureChecking)
3939

compiler/src/dotty/tools/dotc/config/SourceVersion.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ enum SourceVersion:
1010
case `3.2-migration`, `3.2`
1111
case `3.3-migration`, `3.3`
1212
case `3.4-migration`, `3.4`
13+
case `3.5-migration`, `3.5`
1314
// !!! Keep in sync with scala.runtime.stdlibPatches.language !!!
1415
case `future-migration`, `future`
1516

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,19 +1569,18 @@ trait Implicits:
15691569

15701570
/** Does candidate `cand` come too late for it to be considered as an
15711571
* eligible candidate? This is the case if `cand` appears in the same
1572-
* scope as a given definition enclosing the search point (with no
1573-
* class methods between the given definition and the search point)
1574-
* and `cand` comes later in the source or coincides with that given
1575-
* definition.
1572+
* scope as a given definition of the form `given ... = ...` that
1573+
* encloses the search point and `cand` comes later in the source or
1574+
* coincides with that given definition.
15761575
*/
15771576
def comesTooLate(cand: Candidate): Boolean =
15781577
val candSym = cand.ref.symbol
15791578
def candSucceedsGiven(sym: Symbol): Boolean =
1580-
if sym.owner == candSym.owner then
1581-
if sym.is(ModuleClass) then candSucceedsGiven(sym.sourceModule)
1582-
else sym.is(Given) && sym.span.exists && sym.span.start <= candSym.span.start
1583-
else if sym.owner.isClass then false
1584-
else candSucceedsGiven(sym.owner)
1579+
val owner = sym.owner
1580+
if owner == candSym.owner then
1581+
sym.is(GivenVal) && sym.span.exists && sym.span.start <= candSym.span.start
1582+
else if owner.isClass then false
1583+
else candSucceedsGiven(owner)
15851584

15861585
ctx.isTyper
15871586
&& !candSym.isOneOf(TermParamOrAccessor | Synthetic)
@@ -1596,7 +1595,7 @@ trait Implicits:
15961595

15971596
def checkResolutionChange(result: SearchResult) =
15981597
if (eligible ne preEligible)
1599-
&& !Feature.enabled(Feature.avoidLoopingGivens)
1598+
&& !Feature.enabled(Feature.givenLoopPrevention)
16001599
then
16011600
val prevResult = searchImplicit(preEligible, contextual)
16021601
prevResult match
@@ -1617,17 +1616,20 @@ trait Implicits:
16171616
case result: SearchSuccess if prevResult.ref frozen_=:= result.ref =>
16181617
// OK
16191618
case _ =>
1620-
report.error(
1621-
em"""Warning: result of implicit search for $pt will change.
1619+
val msg =
1620+
em"""Result of implicit search for $pt will change.
16221621
|Current result ${showResult(prevResult)} will be no longer eligible
16231622
| because it is not defined before the search position.
16241623
|Result with new rules: ${showResult(result)}.
1625-
|To opt into the new rules, use the `experimental.avoidLoopingGivens` language import.
1624+
|To opt into the new rules, use the `experimental.givenLoopPrevention` language import.
16261625
|
16271626
|To fix the problem without the language import, you could try one of the following:
1627+
| - use a `given ... with` clause as the enclosing given,
16281628
| - rearrange definitions so that ${showResult(prevResult)} comes earlier,
1629-
| - use an explicit $remedy.""",
1630-
srcPos)
1629+
| - use an explicit $remedy."""
1630+
if sourceVersion.isAtLeast(SourceVersion.`3.5`)
1631+
then report.error(msg, srcPos)
1632+
else report.warning(msg.append("\nThis will be an error in Scala 3.5 and later."), srcPos)
16311633
case _ =>
16321634
prevResult
16331635
else result

docs/_docs/reference/changed-features/implicit-resolution.md

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -164,26 +164,3 @@ The new rules are as follows: An implicit `a` defined in `A` is more specific th
164164
Condition (*) is new. It is necessary to ensure that the defined relation is transitive.
165165

166166
[//]: # todo: expand with precise rules
167-
168-
**9.** Implicit resolution now tries to avoid recursive givens that can lead to an infinite loop at runtime. Here is an example:
169-
170-
```scala
171-
object Prices {
172-
opaque type Price = BigDecimal
173-
174-
object Price{
175-
given Ordering[Price] = summon[Ordering[BigDecimal]] // was error, now avoided
176-
}
177-
}
178-
```
179-
180-
Previously, implicit resolution would resolve the `summon` to the given in `Price`, leading to an infinite loop (a warning was issued in that case). We now use the underlying given in `BigDecimal` instead. We achieve that by adding the following rule for implicit search:
181-
182-
- When doing an implicit search while checking the implementation of a `given` definition `G`, discard all search results that lead back to `G` or to a given
183-
with the same owner as `G` that comes later in the source than `G`.
184-
185-
The new behavior is enabled under `-source future`. In earlier versions, a
186-
warning is issued where that behavior will change.
187-
188-
Old-style implicit definitions are unaffected by this change.
189-
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
layout: doc-page
3+
title: Given Loop Prevention
4+
redirectFrom: /docs/reference/other-new-features/into-modifier.html
5+
nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/into-modifier.html
6+
---
7+
8+
Implicit resolution now avoids generating recursive givens that can lead to an infinite loop at runtime. Here is an example:
9+
10+
```scala
11+
object Prices {
12+
opaque type Price = BigDecimal
13+
14+
object Price{
15+
given Ordering[Price] = summon[Ordering[BigDecimal]] // was error, now avoided
16+
}
17+
}
18+
```
19+
20+
Previously, implicit resolution would resolve the `summon` to the given in `Price`, leading to an infinite loop (a warning was issued in that case). We now use the underlying given in `BigDecimal` instead. We achieve that by adding the following rule for implicit search:
21+
22+
- When doing an implicit search while checking the implementation of a `given` definition `G` of the form
23+
```
24+
given ... = ....
25+
```
26+
discard all search results that lead back to `G` or to a given with the same owner as `G` that comes later in the source than `G`.
27+
28+
The new behavior is enabled with the `experimental.givenLoopPrevention` language import. If no such import or setting is given, a warning is issued where the behavior would change under that import (for source version 3.4 and later).
29+
30+
Old-style implicit definitions are unaffected by this change.
31+

docs/sidebar.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ subsection:
153153
- page: reference/experimental/cc.md
154154
- page: reference/experimental/purefuns.md
155155
- page: reference/experimental/tupled-function.md
156+
- page: reference/experimental/given-loop-prevention.md
156157
- page: reference/syntax.md
157158
- title: Language Versions
158159
index: reference/language-versions/language-versions.md

library/src/scala/runtime/stdLibPatches/language.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,10 @@ object language:
9595
* givens. By the new rules, a given may not implicitly use itself or givens
9696
* defined after it.
9797
*
98-
* @see [[https://dotty.epfl.ch/docs/reference/experimental/avoid-looping-givens]]
98+
* @see [[https://dotty.epfl.ch/docs/reference/experimental/given-loop-prevention]]
9999
*/
100-
@compileTimeOnly("`avoidLoopingGivens` can only be used at compile time in import statements")
101-
object avoidLoopingGivens
100+
@compileTimeOnly("`givenLoopPrevention` can only be used at compile time in import statements")
101+
object givenLoopPrevention
102102

103103
/** Was needed to add support for relaxed imports of extension methods.
104104
* The language import is no longer needed as this is now a standard feature since SIP was accepted.

tests/neg/i15474.check

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
-- Error: tests/neg/i15474.scala:6:39 ----------------------------------------------------------------------------------
22
6 | given c: Conversion[ String, Int ] = _.toInt // error
33
| ^
4-
| Warning: result of implicit search for ?{ toInt: ? } will change.
5-
| Current result Test2.c will be no longer eligible
6-
| because it is not defined before the search position.
7-
| Result with new rules: augmentString.
8-
| To opt into the new rules, use the `experimental.avoidLoopingGivens` language import.
4+
| Result of implicit search for ?{ toInt: ? } will change.
5+
| Current result Test2.c will be no longer eligible
6+
| because it is not defined before the search position.
7+
| Result with new rules: augmentString.
8+
| To opt into the new rules, use the `experimental.givenLoopPrevention` language import.
99
|
10-
| To fix the problem without the language import, you could try one of the following:
11-
| - rearrange definitions so that Test2.c comes earlier,
12-
| - use an explicit conversion,
13-
| - use an import to get extension method into scope.
10+
| To fix the problem without the language import, you could try one of the following:
11+
| - use a `given ... with` clause as the enclosing given,
12+
| - rearrange definitions so that Test2.c comes earlier,
13+
| - use an explicit conversion,
14+
| - use an import to get extension method into scope.
15+
| This will be an error in Scala 3.5 and later.
1416
-- Error: tests/neg/i15474.scala:12:56 ---------------------------------------------------------------------------------
1517
12 | given Ordering[Price] = summon[Ordering[BigDecimal]] // error
1618
| ^
17-
| Warning: result of implicit search for Ordering[BigDecimal] will change.
18-
| Current result Prices.Price.given_Ordering_Price will be no longer eligible
19-
| because it is not defined before the search position.
20-
| Result with new rules: scala.math.Ordering.BigDecimal.
21-
| To opt into the new rules, use the `experimental.avoidLoopingGivens` language import.
19+
| Result of implicit search for Ordering[BigDecimal] will change.
20+
| Current result Prices.Price.given_Ordering_Price will be no longer eligible
21+
| because it is not defined before the search position.
22+
| Result with new rules: scala.math.Ordering.BigDecimal.
23+
| To opt into the new rules, use the `experimental.givenLoopPrevention` language import.
2224
|
23-
| To fix the problem without the language import, you could try one of the following:
24-
| - rearrange definitions so that Prices.Price.given_Ordering_Price comes earlier,
25-
| - use an explicit argument.
25+
| To fix the problem without the language import, you could try one of the following:
26+
| - use a `given ... with` clause as the enclosing given,
27+
| - rearrange definitions so that Prices.Price.given_Ordering_Price comes earlier,
28+
| - use an explicit argument.
29+
| This will be an error in Scala 3.5 and later.

tests/neg/i6716.check

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
-- Error: tests/neg/i6716.scala:12:39 ----------------------------------------------------------------------------------
22
12 | given Monad[Bar] = summon[Monad[Foo]] // error
33
| ^
4-
| Warning: result of implicit search for Monad[Foo] will change.
5-
| Current result Bar.given_Monad_Bar will be no longer eligible
6-
| because it is not defined before the search position.
7-
| Result with new rules: Foo.given_Monad_Foo.
8-
| To opt into the new rules, use the `experimental.avoidLoopingGivens` language import.
4+
| Result of implicit search for Monad[Foo] will change.
5+
| Current result Bar.given_Monad_Bar will be no longer eligible
6+
| because it is not defined before the search position.
7+
| Result with new rules: Foo.given_Monad_Foo.
8+
| To opt into the new rules, use the `experimental.givenLoopPrevention` language import.
99
|
10-
| To fix the problem without the language import, you could try one of the following:
11-
| - rearrange definitions so that Bar.given_Monad_Bar comes earlier,
12-
| - use an explicit argument.
10+
| To fix the problem without the language import, you could try one of the following:
11+
| - use a `given ... with` clause as the enclosing given,
12+
| - rearrange definitions so that Bar.given_Monad_Bar comes earlier,
13+
| - use an explicit argument.
14+
| This will be an error in Scala 3.5 and later.

tests/neg/i7294-a.check

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
1-
-- Error: tests/neg/i7294-a.scala:6:10 ---------------------------------------------------------------------------------
2-
6 | case x: T => x.g(10) // error // error
1+
-- [E007] Type Mismatch Error: tests/neg/i7294-a.scala:8:18 ------------------------------------------------------------
2+
8 | case x: T => x.g(10) // error // error
3+
| ^^^^^^^
4+
| Found: Any
5+
| Required: T
6+
|
7+
| where: T is a type in given instance f with bounds <: foo.Foo
8+
|
9+
| longer explanation available when compiling with `-explain`
10+
-- Error: tests/neg/i7294-a.scala:8:10 ---------------------------------------------------------------------------------
11+
8 | case x: T => x.g(10) // error // error
312
| ^
4-
| Warning: result of implicit search for scala.reflect.TypeTest[Nothing, T] will change.
13+
| Result of implicit search for scala.reflect.TypeTest[Nothing, T] will change.
514
| Current result foo.i7294-a$package.f will be no longer eligible
615
| because it is not defined before the search position.
716
| Result with new rules: No Matching Implicit.
8-
| To opt into the new rules, use the `experimental.avoidLoopingGivens` language import.
17+
| To opt into the new rules, use the `experimental.givenLoopPrevention` language import.
918
|
1019
| To fix the problem without the language import, you could try one of the following:
20+
| - use a `given ... with` clause as the enclosing given,
1121
| - rearrange definitions so that foo.i7294-a$package.f comes earlier,
1222
| - use an explicit argument.
23+
| This will be an error in Scala 3.5 and later.
1324
|
1425
| where: T is a type in given instance f with bounds <: foo.Foo
15-
-- [E007] Type Mismatch Error: tests/neg/i7294-a.scala:6:18 ------------------------------------------------------------
16-
6 | case x: T => x.g(10) // error // error
17-
| ^^^^^^^
18-
| Found: Any
19-
| Required: T
20-
|
21-
| where: T is a type in given instance f with bounds <: foo.Foo
22-
|
23-
| longer explanation available when compiling with `-explain`

tests/neg/i7294-a.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//> using options -Xfatal-warnings
2+
13
package foo
24

35
trait Foo { def g(x: Int): Any }

tests/neg/i7294-b.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//> using options -Xfatal-warnings
2+
13
package foo
24

35
trait Foo { def g(x: Any): Any }

tests/neg/looping-givens.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//> using options -Xfatal-warnings
2+
13
class A
24
class B
35

tests/pos/i15474.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//> using options -Xfatal-warnings
22
import scala.language.implicitConversions
3-
import scala.language.experimental.avoidLoopingGivens
3+
import scala.language.experimental.givenLoopPrevention
44

55
object Test2:
66
given c: Conversion[ String, Int ] = _.toInt // now avoided, was loop not detected, could be used as a fallback to avoid the warning.

tests/pos/looping-givens.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import language.experimental.avoidLoopingGivens
1+
import language.experimental.givenLoopPrevention
22

33
class A
44
class B

tests/run/i6716.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//> using options -Xfatal-warnings
22

3-
import scala.language.experimental.avoidLoopingGivens
3+
import scala.language.experimental.givenLoopPrevention
44

55
trait Monad[T]:
66
def id: String
@@ -11,7 +11,7 @@ object Foo {
1111

1212
opaque type Bar = Foo
1313
object Bar {
14-
given Monad[Bar] = summon[Monad[Foo]] // was error fixed by avoidLoopingGivens
14+
given Monad[Bar] = summon[Monad[Foo]] // was error, fixed by givenLoopPrevention
1515
}
1616

1717
object Test extends App {

0 commit comments

Comments
 (0)