Skip to content

Break cycle in computeAsSeenFrom when type-checking mutually referencing abstract type members #20236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1080,7 +1080,34 @@ object Denotations {

type AsSeenFromResult = SingleDenotation

protected def computeAsSeenFrom(pre: Type)(using Context): SingleDenotation = {
private var seenFromPre: Type | Null = null
private var seenFromIntermediateResult: SingleDenotation | Null = null

private inline def breakSeenFromCycles(pre: Type)(inline body: SingleDenotation)(using Context) =
if (seenFromPre eq pre) && symbol.isAbstractType then
if seenFromIntermediateResult == null then
seenFromIntermediateResult = newLikeThis(symbol, info, pre, isRefinedMethod)
seenFromIntermediateResult.nn
else
val previousPre = seenFromPre
val previousIntermediateResult = seenFromIntermediateResult

seenFromPre = pre
seenFromIntermediateResult = null

try
val result = body
if seenFromIntermediateResult != null then
seenFromIntermediateResult.nn.myInfo = result.info
seenFromIntermediateResult.nn
else
result
finally
seenFromIntermediateResult = previousIntermediateResult
seenFromPre = previousPre
end breakSeenFromCycles

protected def computeAsSeenFrom(pre: Type)(using Context): SingleDenotation = breakSeenFromCycles(pre) {
val symbol = this.symbol
val owner = this match {
case thisd: SymDenotation => thisd.owner
Expand Down
10 changes: 10 additions & 0 deletions tests/neg-deep-subtype/i318.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
object i318 {
trait Y {
type A <: { type T >: B }
type B >: { type T >: A }
}

val y: Y = ???
val a: y.A = ???
val b: y.B = a // error: type mismatch
}
16 changes: 3 additions & 13 deletions tests/neg/i4368.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ object Test2 {
type A
type B = z.A
}
trait Z extends X with Y // error: cyclic
trait Z extends X with Y { z: W => } // error: cyclic
}

object Test3 {
Expand All @@ -41,7 +41,7 @@ object Test3 {
}

object App {
type Z = X with Y
type Z = X & Y
val z: Z = z
val a: z.A = a // error: too deep
}
Expand Down Expand Up @@ -155,19 +155,9 @@ object i4369 {
}
object i4370 {
class Foo { type R = A }
type A = List[Foo#R] // error: cyclic
type A = List[Foo#R] // error: type mismatch
}
object i4371 {
class Foo { type A = Boo#B } // error: cyclic
class Boo { type B = Foo#A }
}
object i318 {
trait Y {
type A <: { type T >: B }
type B >: { type T >: A }
}

val y: Y = ???
val a: y.A = ??? // error: too deep
val b: y.B = a // error: too deep
}
42 changes: 42 additions & 0 deletions tests/neg/i4560.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
object Test1:
trait Test[T]

trait Abstract[T]:
type Foo <: Any { type Tie <: Test[Bar] & Test[o.Nested] }
type Bar <: Any { type Tie <: Test[Foo] }

object o:
type Nested <: Foo { type Tie <: Test[Bar] & Test[Nested] & Test[OtherNested] }
type OtherNested <: Any { type Tie <: Test[Nested] }

trait AbstractIntermediate[T] extends Abstract[T]:
type Bar <: Any { type Tie <: Test[Foo]; type U <: T }

trait ConcreteIntermediate extends AbstractIntermediate[Int]:
type Foo <: Any { type Tie <: Test[Bar] & Test[o.Nested] & Test[o.OtherNested]; type U <: Int }

object concrete extends ConcreteIntermediate:
type SuperBar
type Foo <: Any { type Tie <: Test[Bar] & Test[o.Nested] & Test[o.OtherNested]; type U <: Int }

val a: Foo = ???
val b: a.U = ???
val c: String = b // error: type mismatch

val x: Bar = ???
val y: x.U = ???
val z: String = y // error: type mismatch


object Test2:
class C0: // error: cyclic
type T <: C1#T // error: cyclic

class C1:
type T <: C2#T // error: cyclic // error: cyclic

class C2:
type T <: C3#T // error: cyclic // error: cyclic

class C3: // error: cyclic
type T <: C0#T // error: cyclic // error: cyclic
70 changes: 70 additions & 0 deletions tests/pos/i4560.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
object Test1:
trait SimpleTrait:
type A <: { type T = B }
type B <: A

trait SimpleSubTrait extends SimpleTrait:
val v: A


object Test2:
trait Trait:
type A <: { type T = B }
type B <: { type S = A }

trait SubTrait extends Trait:
val v: A

class XA:
type T = XB

class XB:
type S = XA

class Foo extends Trait:
type A = XA
type B = XB


object Test3:
trait Test[T]

trait Abstract[T]:
type Foo <: Any { type Tie <: Test[Bar] & Test[o.Nested] }
type Bar <: Any { type Tie <: Test[Foo] }

object o:
type Nested <: Foo { type Tie <: Test[Bar] & Test[Nested] & Test[OtherNested] }
type OtherNested <: Any { type Tie <: Test[Nested] }

trait AbstractIntermediate[T] extends Abstract[T]:
type Bar <: Any { type Tie <: Test[Foo]; type U <: T }

trait ConcreteIntermediate extends AbstractIntermediate[Int]:
type Foo <: Any { type Tie <: Test[Bar] & Test[o.Nested] & Test[o.OtherNested]; type U <: Int }

object concrete extends ConcreteIntermediate:
type SuperFoo
type Foo <: SuperFoo { type Tie <: Test[Bar] & Test[o.Nested] & Test[o.OtherNested]; type U <: Int }

val a: Foo = ???
val b: a.U = ???
val c: Int = b

val x: Bar = ???
val y: x.U = ???
val z: Int = y


object Test4:
class C0:
type T <: { type X <: C1#T }

class C1:
type T <: { type X <: C2#T }

class C2:
type T <: { type X <: C3#T }

class C3:
type T <: { type X <: C0#T }
Loading