Skip to content

Commit

Permalink
Fix problems with cycle checks
Browse files Browse the repository at this point in the history
Several improvements:

 - Follow opaque aliases as if they were aliases. Otherwise we
   risk crashing in phases where the `opaque` is dropped and these
   become regular aliases.
 - Update the `isInteresting` test to follow more types. Previously
   the test was too strict so some cycles were missed.
 - Make -explain-cyclic also explain for cycles detected by this check.

Fixes #19372
  • Loading branch information
odersky committed Jan 15, 2024
1 parent 31f837e commit 756ae34
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 80 deletions.
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2572,12 +2572,11 @@ class UnknownNamedEnclosingClassOrObject(name: TypeName)(using Context)

class IllegalCyclicTypeReference(val ex: CyclicReference, sym: Symbol, where: String, lastChecked: Type)(using Context)
extends CyclicMsg(IllegalCyclicTypeReferenceID) {
override def context = ""
def msg(using Context) =
val lastCheckedStr =
try lastChecked.show
catch case ex: CyclicReference => "..."
i"illegal cyclic type reference: ${where} ${hl(lastCheckedStr)} of $sym refers back to the type itself"
i"illegal cyclic type reference: ${where} ${hl(lastCheckedStr)} of $sym refers back to the type itself$context"
def explain(using Context) = ""
}

Expand Down
44 changes: 32 additions & 12 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,12 @@ object Checking {
|| sym.owner.isContainedIn(prefix.cls) // sym reachable through member references
)
case prefix: NamedType =>
(!sym.is(Private) && prefix.derivesFrom(sym.owner)) ||
(!prefix.symbol.moduleClass.isStaticOwner && isInteresting(prefix.prefix))
!sym.is(Private) && prefix.derivesFrom(sym.owner)
|| {
val pcls = prefix.symbol.moduleClass
if pcls.isStaticOwner then pcls.isDefinedInCurrentRun
else isInteresting(prefix.prefix)
}
case SuperType(thistp, _) => isInteresting(thistp)
case AndType(tp1, tp2) => isInteresting(tp1) || isInteresting(tp2)
case OrType(tp1, tp2) => isInteresting(tp1) && isInteresting(tp2)
Expand All @@ -342,15 +346,27 @@ object Checking {
case _ => false
}

if (isInteresting(pre)) {
val pre1 = this(pre, false, false)
if (locked.contains(tp) || tp.symbol.infoOrCompleter.isInstanceOf[NoCompleter])
throw CyclicReference(tp.symbol)
locked += tp
try if (!tp.symbol.isClass) checkInfo(tp.info)
finally locked -= tp
tp.withPrefix(pre1)
}
if isInteresting(pre) then
val traceCycles = CyclicReference.isTraced
try
if traceCycles then
CyclicReference.pushTrace("explore ", tp.symbol, " for cyclic references")
val pre1 = this(pre, false, false)
if locked.contains(tp)
|| tp.symbol.infoOrCompleter.isInstanceOf[NoCompleter]
then
throw CyclicReference(tp.symbol)
locked += tp
try
if tp.symbol.isOpaqueAlias then
checkInfo(TypeAlias(tp.translucentSuperType))
else if !tp.symbol.isClass then
checkInfo(tp.info)
finally
locked -= tp
tp.withPrefix(pre1)
finally
if traceCycles then CyclicReference.popTrace()
else tp
}
catch {
Expand Down Expand Up @@ -387,7 +403,11 @@ object Checking {
*/
def checkNonCyclic(sym: Symbol, info: Type, reportErrors: Boolean)(using Context): Type = {
val checker = withMode(Mode.CheckCyclic)(new CheckNonCyclicMap(sym, reportErrors))
try checker.checkInfo(info)
try
val toCheck = info match
case info: RealTypeBounds if sym.isOpaqueAlias => TypeAlias(sym.opaqueAlias)
case _ => info
checker.checkInfo(toCheck)
catch {
case ex: CyclicReference =>
if (reportErrors)
Expand Down
74 changes: 52 additions & 22 deletions tests/neg/i15507.check
Original file line number Diff line number Diff line change
@@ -1,40 +1,70 @@
-- Error: tests/neg/i15507.scala:2:40 ----------------------------------------------------------------------------------
2 | type _NestedSet1[X] = Set[_NestedSet1[?]] // error
-- Error: tests/neg/i15507.scala:3:40 ----------------------------------------------------------------------------------
3 | type _NestedSet1[X] = Set[_NestedSet1[?]] // error
| ^
| no wildcard type allowed here
-- Error: tests/neg/i15507.scala:3:41 ----------------------------------------------------------------------------------
3 | type _NestedSet2[X] <: Set[_NestedSet2[?]] // error
-- Error: tests/neg/i15507.scala:4:41 ----------------------------------------------------------------------------------
4 | type _NestedSet2[X] <: Set[_NestedSet2[?]] // error
| ^
| no wildcard type allowed here
-- [E140] Cyclic Error: tests/neg/i15507.scala:5:7 ---------------------------------------------------------------------
5 | type _NestedSet4[X] >: Set[_NestedSet4[X]] // error
-- [E140] Cyclic Error: tests/neg/i15507.scala:6:7 ---------------------------------------------------------------------
6 | type _NestedSet4[X] >: Set[_NestedSet4[X]] // error
| ^
| illegal cyclic type reference: lower bound ... of type _NestedSet4 refers back to the type itself
-- [E140] Cyclic Error: tests/neg/i15507.scala:6:7 ---------------------------------------------------------------------
6 | type _NestedSet5[X] = Set[_NestedSet5[X]] // error
|
| The error occurred while trying to compute the signature of type _NestedSet4
| which required to explore type _NestedSet4 for cyclic references
|
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
-- [E140] Cyclic Error: tests/neg/i15507.scala:7:7 ---------------------------------------------------------------------
7 | type _NestedSet5[X] = Set[_NestedSet5[X]] // error
| ^
| illegal cyclic type reference: alias ... of type _NestedSet5 refers back to the type itself
-- [E140] Cyclic Error: tests/neg/i15507.scala:7:7 ---------------------------------------------------------------------
7 | type _NestedSet6[X] = Set[_NestedSet6[Int]] // error
|
| The error occurred while trying to compute the signature of type _NestedSet5
| which required to explore type _NestedSet5 for cyclic references
|
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
-- [E140] Cyclic Error: tests/neg/i15507.scala:8:7 ---------------------------------------------------------------------
8 | type _NestedSet6[X] = Set[_NestedSet6[Int]] // error
| ^
| illegal cyclic type reference: alias ... of type _NestedSet6 refers back to the type itself
-- Error: tests/neg/i15507.scala:9:43 ----------------------------------------------------------------------------------
9 | type _NestedList1[X] = List[_NestedList1[?]] // error
| ^
| no wildcard type allowed here
-- Error: tests/neg/i15507.scala:10:44 ---------------------------------------------------------------------------------
10 | type _NestedList2[X] <: List[_NestedList2[?]] // error
|
| The error occurred while trying to compute the signature of type _NestedSet6
| which required to explore type _NestedSet6 for cyclic references
|
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
-- Error: tests/neg/i15507.scala:10:43 ---------------------------------------------------------------------------------
10 | type _NestedList1[X] = List[_NestedList1[?]] // error
| ^
| no wildcard type allowed here
-- Error: tests/neg/i15507.scala:11:44 ---------------------------------------------------------------------------------
11 | type _NestedList2[X] <: List[_NestedList2[?]] // error
| ^
| no wildcard type allowed here
-- [E140] Cyclic Error: tests/neg/i15507.scala:12:7 --------------------------------------------------------------------
12 | type _NestedList4[X] >: List[_NestedList4[X]] // error
-- [E140] Cyclic Error: tests/neg/i15507.scala:13:7 --------------------------------------------------------------------
13 | type _NestedList4[X] >: List[_NestedList4[X]] // error
| ^
| illegal cyclic type reference: lower bound ... of type _NestedList4 refers back to the type itself
-- [E140] Cyclic Error: tests/neg/i15507.scala:13:7 --------------------------------------------------------------------
13 | type _NestedList5[X] = List[_NestedList5[X]] // error
|
| The error occurred while trying to compute the signature of type _NestedList4
| which required to explore type _NestedList4 for cyclic references
|
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
-- [E140] Cyclic Error: tests/neg/i15507.scala:14:7 --------------------------------------------------------------------
14 | type _NestedList5[X] = List[_NestedList5[X]] // error
| ^
| illegal cyclic type reference: alias ... of type _NestedList5 refers back to the type itself
-- [E140] Cyclic Error: tests/neg/i15507.scala:14:7 --------------------------------------------------------------------
14 | type _NestedList6[X] = List[_NestedList6[Int]] // error
|
| The error occurred while trying to compute the signature of type _NestedList5
| which required to explore type _NestedList5 for cyclic references
|
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
-- [E140] Cyclic Error: tests/neg/i15507.scala:15:7 --------------------------------------------------------------------
15 | type _NestedList6[X] = List[_NestedList6[Int]] // error
| ^
| illegal cyclic type reference: alias ... of type _NestedList6 refers back to the type itself
|
| The error occurred while trying to compute the signature of type _NestedList6
| which required to explore type _NestedList6 for cyclic references
|
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
1 change: 1 addition & 0 deletions tests/neg/i15507.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//> using options -explain-cyclic
object TestNested:
type _NestedSet1[X] = Set[_NestedSet1[?]] // error
type _NestedSet2[X] <: Set[_NestedSet2[?]] // error
Expand Down
50 changes: 50 additions & 0 deletions tests/neg/i19372.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
-- [E140] Cyclic Error: tests/neg/i19372.scala:3:7 ---------------------------------------------------------------------
3 | type AAA = List[bar.BBB] // error: cyclic
| ^
| illegal cyclic type reference: alias List[Test1.bar.BBB] of type AAA refers back to the type itself
|
| The error occurred while trying to compute the signature of type AAA
| which required to explore type BBB for cyclic references
| which required to explore type AAA for cyclic references
|
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
-- [E140] Cyclic Error: tests/neg/i19372.scala:9:7 ---------------------------------------------------------------------
9 | type A = bar.B // error: cyclic
| ^
| illegal cyclic type reference: alias Test2.bar.B of type A refers back to the type itself
|
| The error occurred while trying to compute the signature of type A
| which required to explore type B for cyclic references
| which required to explore type A for cyclic references
|
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
-- [E140] Cyclic Error: tests/neg/i19372.scala:15:7 --------------------------------------------------------------------
15 | type AAA = List[bar.BBB] // error: cyclic
| ^
| illegal cyclic type reference: alias List[Test3.bar.BBB] of type AAA refers back to the type itself
|
| The error occurred while trying to compute the signature of type AAA
| which required to explore type BBB for cyclic references
| which required to explore type AAA for cyclic references
|
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
-- [E140] Cyclic Error: tests/neg/i19372.scala:21:7 --------------------------------------------------------------------
21 | type A = bar.B // error: cyclic
| ^
| illegal cyclic type reference: alias Test4.bar.B of type A refers back to the type itself
|
| The error occurred while trying to compute the signature of type A
| which required to explore type B for cyclic references
| which required to explore type A for cyclic references
|
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
-- [E140] Cyclic Error: tests/neg/i19372.scala:30:7 --------------------------------------------------------------------
30 | type UCharIteratorReserved = Ptr[UCharIterator] // error: cyclic
| ^
|illegal cyclic type reference: alias Ptr[structs.UCharIterator] of type UCharIteratorReserved refers back to the type itself
|
|The error occurred while trying to compute the signature of type UCharIteratorReserved
| which required to explore type UCharIterator for cyclic references
| which required to explore type UCharIteratorReserved for cyclic references
|
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
38 changes: 38 additions & 0 deletions tests/neg/i19372.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//> using options -explain-cyclic
object Test1:
type AAA = List[bar.BBB] // error: cyclic
def foo: AAA = ???
object bar:
opaque type BBB = AAA

object Test2:
type A = bar.B // error: cyclic
def foo: A = ???
object bar:
opaque type B = A

object Test3:
type AAA = List[bar.BBB] // error: cyclic
def foo: AAA = ???
object bar:
type BBB = AAA

object Test4:
type A = bar.B // error: cyclic
def foo: A = ???
object bar:
type B = A

trait Ptr[T]

object aliases:
import structs.*
type UCharIteratorReserved = Ptr[UCharIterator] // error: cyclic
object UCharIteratorReserved:
def iterator: UCharIterator = ???

object structs:
import aliases.{*, given}
opaque type UCharIterator = Ptr[UCharIteratorReserved]
object UCharIterator:
def reservedFn: UCharIteratorReserved = ???
47 changes: 3 additions & 44 deletions tests/neg/i8984.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import scala.annotation.tailrec
type |@[F[+_], G[+_]] = [a] =>> F[a] | G[a]

object Fix:
object Fix: // error
opaque type T[+F[+_]] = ApplyFix.T[F]

def apply[F[+_]](f: F[Fix[F]]): T[F] = ApplyFix(f)
def apply[F[+_]](f: F[Fix[F]]): T[F] = ApplyFix(f) // error // error

extension [F[+_]](fix: T[F])
extension [F[+_]](fix: T[F]) // error
def value: F[Fix[F]] = ApplyFix.unwrap(fix)

object ApplyFix:
Expand All @@ -18,44 +18,3 @@ object Fix:

type Fix[+F[+_]] = Fix.T[F]

final case class Cat[+R](name: String, fur: String, rest: R)
object Cat:
def of[R, F[+_]](name: String, fur: String, rest: Fix[F]): Fix[F |@ Cat] = Fix(new Cat(name, fur, rest))

final case class Dog[+R](name: String, size: Long, rest: R)
object Dog:
def of[R, F[+_]](name: String, size: Long, rest: Fix[F]): Fix[F |@ Dog] = Fix(new Dog(name, size, rest))

case object End:
type f[+a] = End.type
def apply() = Fix[f](End)

object DropRed:
@tailrec def dropRedCats[F[+a] >: Cat[a]](cats: Fix[F]): Fix[F] =
cats.value match
case Cat(_, "red", rest) => dropRedCats(rest) // error
case _ => cats

type CatDogVector = Vector[Either[Cat[Unit], Dog[Unit]]]
type CatOrDogs[+a] = Cat[a] | Dog[a] | End.type

extension (catDogs: Fix[CatOrDogs]) def toVector : CatDogVector =
@tailrec def go(acc: CatDogVector, catDogs: Fix[CatOrDogs]) : CatDogVector = catDogs.value match
case Cat(name, fur, rest) => go(acc :+ Left(Cat(name, fur, ())), rest)
case Dog(name, size, rest) => go(acc :+ Right(Dog(name, size, ())), rest)
case End => acc

go(Vector(), catDogs)

val x =
Cat.of("lilly" , "red" ,
Cat.of("anya" , "red" ,
Cat.of("boris" , "black",
Dog.of("mashka", 3 ,
Cat.of("manya" , "red" ,
End())))))


def main(args: Array[String]) =
println(x.toVector)
println(dropRedCats(x).toVector)

0 comments on commit 756ae34

Please sign in to comment.