forked from scala/scala3
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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).
- Loading branch information
Showing
7 changed files
with
141 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
-- [E007] Type Mismatch Error: tests/neg/i21239.scala:14:18 ------------------------------------------------------------ | ||
14 | def get2: V = get // error | ||
| ^^^ | ||
| Found: AnyRef | ||
| Required: V | ||
| | ||
| longer explanation available when compiling with `-explain` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
-- [E007] Type Mismatch Error: tests/neg/i21239.orig.scala:32:8 -------------------------------------------------------- | ||
32 | get // error | ||
| ^^^ | ||
| Found: AnyRef | ||
| Required: V | ||
| | ||
| longer explanation available when compiling with `-explain` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// 1 | ||
// A re-minimisated reproduction of the original issue in kse3 | ||
// The one in the issue removes the usage of the package | ||
// in the second extension bundle, which is crucial to | ||
// why my change broke this code | ||
package kse.flow | ||
|
||
import java.util.concurrent.atomic.AtomicReference | ||
|
||
opaque type Worm[V] = AtomicReference[AnyRef] | ||
object Worm: | ||
val notSetSentinel: AnyRef = new AnyRef {} | ||
|
||
extension [V](worm: Worm[V]) | ||
inline def wormAsAtomic: AtomicReference[AnyRef] = worm | ||
|
||
extension [V](worm: kse.flow.Worm[V]) | ||
|
||
inline def setIfEmpty(v: => V): Boolean = | ||
var old = worm.wormAsAtomic.get() | ||
if old eq Worm.notSetSentinel then | ||
worm.wormAsAtomic.compareAndSet(old, v.asInstanceOf[AnyRef]) | ||
else false | ||
|
||
inline def get: V = worm.wormAsAtomic.get() match | ||
case x if x eq Worm.notSetSentinel => throw new java.lang.IllegalStateException("Retrieved value before being set") | ||
case x => x.asInstanceOf[V] | ||
|
||
inline def getOrSet(v: => V): V = worm.wormAsAtomic.get() match | ||
case x if x eq Worm.notSetSentinel => | ||
setIfEmpty(v) | ||
get // error | ||
case x => x.asInstanceOf[V] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// 2 | ||
// A more minimised reproduction | ||
package lib | ||
|
||
import java.util.concurrent.atomic.AtomicReference | ||
|
||
opaque type Worm[V] = AtomicReference[AnyRef] | ||
object Worm: | ||
extension [V](worm: Worm[V]) | ||
inline def wormAsAtomic: AtomicReference[AnyRef] = worm | ||
|
||
extension [V](worm: lib.Worm[V]) | ||
def get: V = worm.wormAsAtomic.get().asInstanceOf[V] | ||
def get2: V = get // error |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// 4 | ||
// An alternative way to fix it, | ||
// defining the extension method externally, | ||
// in a scope that doesn't see through | ||
// the opaque type definition. | ||
// The setup here also makes sure those extension | ||
// are on the opaque type's companion object | ||
// (via class extension), meaning that they continue | ||
// to be in implicit scope (as enforced by the usage test) | ||
import java.util.concurrent.atomic.AtomicReference | ||
|
||
package lib: | ||
object Worms: | ||
opaque type Worm[V] = AtomicReference[AnyRef] | ||
object Worm extends WormOps: | ||
extension [V](worm: Worm[V]) | ||
inline def wormAsAtomic: AtomicReference[AnyRef] = worm | ||
|
||
import Worms.Worm | ||
trait WormOps: | ||
extension [V](worm: Worm[V]) | ||
def get: V = worm.wormAsAtomic.get().asInstanceOf[V] | ||
def get2: V = get | ||
|
||
package test: | ||
import lib.Worms.Worm | ||
object Test: | ||
def usage(worm: Worm[String]): String = worm.get2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// 5 | ||
// Finally, an alternative way to fix the original issue, | ||
// by reimplementing `getOrSet` to not even need | ||
// our `get` extension. | ||
import java.util.concurrent.atomic.AtomicReference | ||
|
||
opaque type Worm[V] = AtomicReference[AnyRef] | ||
object Worm: | ||
val notSetSentinel: AnyRef = new AnyRef {} | ||
|
||
extension [V](worm: Worm[V]) | ||
inline def wormAsAtomic: AtomicReference[AnyRef] = worm // deprecate? | ||
|
||
inline def setIfEmpty(v: => V): Boolean = | ||
val x = worm.get() | ||
if x eq notSetSentinel then | ||
val value = v | ||
worm.set(value.asInstanceOf[AnyRef]) | ||
true | ||
else false | ||
|
||
inline def get: V = | ||
val x = worm.get() | ||
if x eq notSetSentinel then | ||
throw IllegalStateException("Retrieved value before being set") | ||
else x.asInstanceOf[V] | ||
|
||
inline def getOrSet(v: => V): V = | ||
val x = worm.get() | ||
if x eq notSetSentinel then | ||
val value = v | ||
worm.set(value.asInstanceOf[AnyRef]) | ||
value | ||
else x.asInstanceOf[V] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// 3 | ||
// One way to fix the issue, using the | ||
// "universal function call syntax" | ||
// (to borrow from what Rust calls the syntax to | ||
// disambiguate which trait's method is intended.) | ||
import java.util.concurrent.atomic.AtomicReference | ||
|
||
package lib: | ||
opaque type Worm[V] = AtomicReference[AnyRef] | ||
object Worm: | ||
extension [V](worm: Worm[V]) | ||
def get: V = worm.get().asInstanceOf[V] | ||
def get2: V = Worm.get(worm) | ||
|
||
package test: | ||
import lib.Worm | ||
object Test: | ||
def usage(worm: Worm[String]): String = worm.get2 |