Skip to content

Commit 4fc8564

Browse files
committed
Capture the kse3 issue in test cases
The underlying type of an opaque type is only visible to anything within the scope that contains that opaque type. So, for instance, a Worm opaque type in a Worms object, anything within the Worms object sees the underlying type. So Worms.Worm is an opaque abstract type with bounds, while Worms.this.Worm is an opaque type with an underlying type. But you can also reference Worms.Worm while being inside of the Worms object. The change I made to opaque types allowed for member selection to see the underlying type when in a scope that can see that, which makes it consistent with how TypeComparer allows those two types to be seen as equivalent, when in the right scope. In kse3, it seems, the fact that this wasn't done was utilised by using an "external" reference to the opaque type, which avoided the underlying type's method being selected, and the extension method being selected instead. While my change broke kse3, I believe the change is good, as it brings consistency. And it is possible to fix the kse3 code, by using the "universal function call syntax" (to borrow from Rust nomenclature) for calling the extension method. Alternatively, the extension methods can be defined where they really don't see the underlying type, and then the companion object can be made to include the extension methods (to keep them in implicit scope).
1 parent 0e36424 commit 4fc8564

7 files changed

+141
-0
lines changed

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

0 commit comments

Comments
 (0)