Skip to content

Commit afcb0ad

Browse files
authored
Capture the kse3 issue in test cases and close it (#21260)
2 parents 8b911ef + 6c86910 commit afcb0ad

10 files changed

+180
-1
lines changed

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,22 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
767767
val qual1 = qual.cast(liftedTp)
768768
val tree1 = cpy.Select(tree0)(qual1, selName)
769769
val rawType1 = selectionType(tree1, qual1)
770-
tryType(tree1, qual1, rawType1)
770+
val adapted = tryType(tree1, qual1, rawType1)
771+
if !adapted.isEmpty && sourceVersion == `3.6-migration` then
772+
val adaptedOld = tryExt(tree, qual)
773+
if !adaptedOld.isEmpty then
774+
val symOld = adaptedOld.symbol
775+
val underlying = liftedTp match
776+
case tp: TypeProxy => i" ${tp.translucentSuperType}"
777+
case _ => ""
778+
report.migrationWarning(
779+
em"""Previously this selected the extension ${symOld}${symOld.showExtendedLocation}
780+
|Now it selects $selName on the opaque type's underlying type$underlying
781+
|
782+
|You can change this back by selecting $adaptedOld
783+
|Or by defining the extension method outside of the opaque type's scope.
784+
|""", tree0)
785+
adapted
771786
else EmptyTree
772787

773788
// Otherwise, try to expand a named tuple selection

tests/neg/i21239.check

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E007] Type Mismatch Error: tests/neg/i21239.scala:14:18 ------------------------------------------------------------
2+
14 | def get2: V = get // error
3+
| ^^^
4+
| Found: AnyRef
5+
| Required: V
6+
|
7+
| longer explanation available when compiling with `-explain`

tests/neg/i21239.orig.check

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E007] Type Mismatch Error: tests/neg/i21239.orig.scala:32:8 --------------------------------------------------------
2+
32 | get // error
3+
| ^^^
4+
| Found: AnyRef
5+
| Required: V
6+
|
7+
| longer explanation available when compiling with `-explain`

tests/neg/i21239.orig.scala

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// 1
2+
// A re-minimisated reproduction of the original issue in kse3
3+
// The one in the issue removes the usage of the package
4+
// in the second extension bundle, which is crucial to
5+
// why my change broke this code
6+
package kse.flow
7+
8+
import java.util.concurrent.atomic.AtomicReference
9+
10+
opaque type Worm[V] = AtomicReference[AnyRef]
11+
object Worm:
12+
val notSetSentinel: AnyRef = new AnyRef {}
13+
14+
extension [V](worm: Worm[V])
15+
inline def wormAsAtomic: AtomicReference[AnyRef] = worm
16+
17+
extension [V](worm: kse.flow.Worm[V])
18+
19+
inline def setIfEmpty(v: => V): Boolean =
20+
var old = worm.wormAsAtomic.get()
21+
if old eq Worm.notSetSentinel then
22+
worm.wormAsAtomic.compareAndSet(old, v.asInstanceOf[AnyRef])
23+
else false
24+
25+
inline def get: V = worm.wormAsAtomic.get() match
26+
case x if x eq Worm.notSetSentinel => throw new java.lang.IllegalStateException("Retrieved value before being set")
27+
case x => x.asInstanceOf[V]
28+
29+
inline def getOrSet(v: => V): V = worm.wormAsAtomic.get() match
30+
case x if x eq Worm.notSetSentinel =>
31+
setIfEmpty(v)
32+
get // error
33+
case x => x.asInstanceOf[V]

tests/neg/i21239.scala

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// 2
2+
// A more minimised reproduction
3+
package lib
4+
5+
import java.util.concurrent.atomic.AtomicReference
6+
7+
opaque type Worm[V] = AtomicReference[AnyRef]
8+
object Worm:
9+
extension [V](worm: Worm[V])
10+
inline def wormAsAtomic: AtomicReference[AnyRef] = worm
11+
12+
extension [V](worm: lib.Worm[V])
13+
def get: V = worm.wormAsAtomic.get().asInstanceOf[V]
14+
def get2: V = get // error

tests/pos/i21239.alt.scala

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// 4
2+
// An alternative way to fix it,
3+
// defining the extension method externally,
4+
// in a scope that doesn't see through
5+
// the opaque type definition.
6+
// The setup here also makes sure those extension
7+
// are on the opaque type's companion object
8+
// (via class extension), meaning that they continue
9+
// to be in implicit scope (as enforced by the usage test)
10+
import java.util.concurrent.atomic.AtomicReference
11+
12+
package lib:
13+
object Worms:
14+
opaque type Worm[V] = AtomicReference[AnyRef]
15+
object Worm extends WormOps:
16+
extension [V](worm: Worm[V])
17+
inline def wormAsAtomic: AtomicReference[AnyRef] = worm
18+
19+
import Worms.Worm
20+
trait WormOps:
21+
extension [V](worm: Worm[V])
22+
def get: V = worm.wormAsAtomic.get().asInstanceOf[V]
23+
def get2: V = get
24+
25+
package test:
26+
import lib.Worms.Worm
27+
object Test:
28+
def usage(worm: Worm[String]): String = worm.get2

tests/pos/i21239.orig.scala

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// 5
2+
// Finally, an alternative way to fix the original issue,
3+
// by reimplementing `getOrSet` to not even need
4+
// our `get` extension.
5+
import java.util.concurrent.atomic.AtomicReference
6+
7+
opaque type Worm[V] = AtomicReference[AnyRef]
8+
object Worm:
9+
val notSetSentinel: AnyRef = new AnyRef {}
10+
11+
extension [V](worm: Worm[V])
12+
inline def wormAsAtomic: AtomicReference[AnyRef] = worm // deprecate?
13+
14+
inline def setIfEmpty(v: => V): Boolean =
15+
val x = worm.get()
16+
if x eq notSetSentinel then
17+
val value = v
18+
worm.set(value.asInstanceOf[AnyRef])
19+
true
20+
else false
21+
22+
inline def get: V =
23+
val x = worm.get()
24+
if x eq notSetSentinel then
25+
throw IllegalStateException("Retrieved value before being set")
26+
else x.asInstanceOf[V]
27+
28+
inline def getOrSet(v: => V): V =
29+
val x = worm.get()
30+
if x eq notSetSentinel then
31+
val value = v
32+
worm.set(value.asInstanceOf[AnyRef])
33+
value
34+
else x.asInstanceOf[V]

tests/pos/i21239.scala

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// 3
2+
// One way to fix the issue, using the
3+
// "universal function call syntax"
4+
// (to borrow from what Rust calls the syntax to
5+
// disambiguate which trait's method is intended.)
6+
import java.util.concurrent.atomic.AtomicReference
7+
8+
package lib:
9+
opaque type Worm[V] = AtomicReference[AnyRef]
10+
object Worm:
11+
extension [V](worm: Worm[V])
12+
def get: V = worm.get().asInstanceOf[V]
13+
def get2: V = Worm.get(worm)
14+
15+
package test:
16+
import lib.Worm
17+
object Test:
18+
def usage(worm: Worm[String]): String = worm.get2

tests/warn/i21239.Frac.check

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Migration Warning: tests/warn/i21239.Frac.scala:14:8 ----------------------------------------------------------------
2+
14 | f + Frac.wrap(((-g.numerator).toLong << 32) | (g.unwrap & 0xFFFFFFFFL)) // warn
3+
| ^^^
4+
| Previously this selected the extension method + in object Frac
5+
| Now it selects + on the opaque type's underlying type Long
6+
|
7+
| You can change this back by selecting kse.maths.Frac.+(f)
8+
| Or by defining the extension method outside of the opaque type's scope.

tests/warn/i21239.Frac.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package kse.maths
2+
3+
import scala.language.`3.6-migration`
4+
5+
opaque type Frac = Long
6+
object Frac {
7+
inline def wrap(f: Long): kse.maths.Frac = f
8+
extension (f: Frac)
9+
inline def unwrap: Long = f
10+
inline def numerator: Int = ((f: Long) >>> 32).toInt
11+
extension (f: kse.maths.Frac)
12+
def +(g: Frac): kse.maths.Frac = f // eliding domain-specific addition logic
13+
def -(g: Frac): kse.maths.Frac =
14+
f + Frac.wrap(((-g.numerator).toLong << 32) | (g.unwrap & 0xFFFFFFFFL)) // warn
15+
}

0 commit comments

Comments
 (0)