File tree 10 files changed +180
-1
lines changed
compiler/src/dotty/tools/dotc/typer
10 files changed +180
-1
lines changed Original file line number Diff line number Diff line change @@ -767,7 +767,22 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
767
767
val qual1 = qual.cast(liftedTp)
768
768
val tree1 = cpy.Select (tree0)(qual1, selName)
769
769
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
771
786
else EmptyTree
772
787
773
788
// Otherwise, try to expand a named tuple selection
Original file line number Diff line number Diff line change
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`
Original file line number Diff line number Diff line change
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`
Original file line number Diff line number Diff line change
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 ]
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 ]
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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.
Original file line number Diff line number Diff line change
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
+ }
You can’t perform that action at this time.
0 commit comments