From 724e0ca28489c8684460fc3fdd41e3b31d864b1c Mon Sep 17 00:00:00 2001 From: Pascal Weisenburger Date: Fri, 19 Apr 2024 11:09:29 +0200 Subject: [PATCH] break cycle in `computeAsSeenFrom` when type-checking mutually referencing abstract type members --- .../dotty/tools/dotc/core/Denotations.scala | 29 +++++++- tests/neg-deep-subtype/i318.scala | 10 +++ tests/neg/i4368.scala | 16 +---- tests/neg/i4560.scala | 42 +++++++++++ tests/pos/i4560.scala | 70 +++++++++++++++++++ 5 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 tests/neg-deep-subtype/i318.scala create mode 100644 tests/neg/i4560.scala create mode 100644 tests/pos/i4560.scala diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 2418aba1978b..396f5472c7b7 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -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 diff --git a/tests/neg-deep-subtype/i318.scala b/tests/neg-deep-subtype/i318.scala new file mode 100644 index 000000000000..86ae7c673ccd --- /dev/null +++ b/tests/neg-deep-subtype/i318.scala @@ -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 +} diff --git a/tests/neg/i4368.scala b/tests/neg/i4368.scala index 95c342d5fe2a..f440bf977e11 100644 --- a/tests/neg/i4368.scala +++ b/tests/neg/i4368.scala @@ -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 { @@ -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 } @@ -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 -} diff --git a/tests/neg/i4560.scala b/tests/neg/i4560.scala new file mode 100644 index 000000000000..8b529cbb7c6e --- /dev/null +++ b/tests/neg/i4560.scala @@ -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 diff --git a/tests/pos/i4560.scala b/tests/pos/i4560.scala new file mode 100644 index 000000000000..0ed3d5717447 --- /dev/null +++ b/tests/pos/i4560.scala @@ -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 }