Skip to content

Backport "Fix cyclic check, regardless of definition order" to 3.3 LTS #216

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

Merged
merged 1 commit into from
Apr 8, 2025
Merged
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
32 changes: 25 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,16 @@ object Checking {
*/
def checkInfo(tp: Type): Type = tp match {
case tp @ TypeAlias(alias) =>
tp.derivedAlias(checkPart(alias, "alias"))
val lo1 = atVariance(-1)(checkPart(alias, "alias"))
val hi1 = checkUpper(alias, "alias")
if lo1 eq hi1 then
tp.derivedAlias(lo1)
else
tp.derivedTypeBounds(lo1, hi1)
case tp @ MatchAlias(alias) =>
tp.derivedAlias(checkUpper(alias, "match"))
tp.derivedAlias(atVariance(0)(checkUpper(alias, "match")))
case tp @ TypeBounds(lo, hi) =>
tp.derivedTypeBounds(checkPart(lo, "lower bound"), checkUpper(hi, "upper bound"))
tp.derivedTypeBounds(atVariance(-1)(checkPart(lo, "lower bound")), checkUpper(hi, "upper bound"))
case _ =>
tp
}
Expand All @@ -299,12 +304,12 @@ object Checking {
case tp: TermRef =>
this(tp.info)
mapOver(tp)
case tp @ AppliedType(tycon, args) =>
tp.derivedAppliedType(this(tycon), args.mapConserve(this(_, nestedCycleOK, nestedCycleOK)))
case tp @ RefinedType(parent, name, rinfo) =>
tp.derivedRefinedType(this(parent), name, this(rinfo, nestedCycleOK, nestedCycleOK))
case tp: RecType =>
tp.rebind(this(tp.parent))
case tp: LazyRef =>
tp
case tp @ TypeRef(pre, _) =>
try {
// A prefix is interesting if it might contain (transitively) a reference
Expand Down Expand Up @@ -337,14 +342,17 @@ object Checking {

if isInteresting(pre) then
CyclicReference.trace(i"explore ${tp.symbol} for cyclic references"):
val pre1 = this(pre, false, false)
val pre1 = atVariance(variance max 0)(this(pre, false, false))
if locked.contains(tp)
|| tp.symbol.infoOrCompleter.isInstanceOf[NoCompleter]
&& tp.symbol == sym
then
throw CyclicReference(tp.symbol)
locked += tp
try
if tp.symbol.isOpaqueAlias then
if tp.symbol.infoOrCompleter.isInstanceOf[NoCompleter] then
; // skip checking info (and avoid forcing the symbol with .isOpaqueAlias/etc)
else if tp.symbol.isOpaqueAlias then
checkInfo(TypeAlias(tp.translucentSuperType))
else if !tp.symbol.isClass then
checkInfo(tp.info)
Expand All @@ -362,6 +370,16 @@ object Checking {
}
case _ => mapOver(tp)
}

override def mapArg(arg: Type, tparam: ParamInfo): Type =
val varianceDiff = variance != tparam.paramVarianceSign
atVariance(variance * tparam.paramVarianceSign):
// Using tests/pos/i22257.scala as an example,
// if we consider FP's lower-bound of Fixed[Node]
// than `Node` is a type argument in contravariant
// position, while the type parameter is covariant.
val nestedCycleOK1 = nestedCycleOK || variance != 0 && varianceDiff
this(arg, nestedCycleOK, nestedCycleOK1)
}

/** Under -Yrequire-targetName, if `sym` has an operator name, check that it has a
Expand Down
6 changes: 3 additions & 3 deletions tests/neg/i4368.scala
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ object Test6 {

object Test7 {
class Fix[F[_]] {
class Foo { type R >: F[T] <: F[T] } // error: cyclic
class Foo { type R >: F[T] <: F[T] } // error
type T = F[Foo#R]
}

Expand Down Expand Up @@ -149,9 +149,9 @@ object Test9 {
object i4369 {
trait X { self =>
type R <: Z
type Z >: X { type R = self.R; type Z = self.R } // error: cyclic // error: cyclic // error: cyclic
type Z >: X { type R = self.R; type Z = self.R } // error: cyclic
}
class Foo extends X { type R = Foo; type Z = Foo }
class Foo extends X { type R = Foo; type Z = Foo } // error
}
object i4370 {
class Foo { type R = A }
Expand Down
17 changes: 15 additions & 2 deletions tests/neg/i4369c.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
trait X { self =>
type R <: Z
type Z >: X { type R = self.R; type Z = self.R } // error // error // error
type Z >:
X { // error
type R = // was-error
self.R
type Z = // was-error
self.R
}
}

class Foo // error
extends X {
type R =
Foo
type Z =
Foo
}
class Foo extends X { type R = Foo; type Z = Foo }
2 changes: 1 addition & 1 deletion tests/neg/toplevel-cyclic/defs_1.scala
Original file line number Diff line number Diff line change
@@ -1 +1 @@
type A = B
type A = B // error: recursion limit exceeded
2 changes: 1 addition & 1 deletion tests/neg/toplevel-cyclic/moredefs_1.scala
Original file line number Diff line number Diff line change
@@ -1 +1 @@
type B = A // error: recursion limit exceeded
type B = A
52 changes: 52 additions & 0 deletions tests/pos/i22257.fixed.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

object Scaffold {

trait Arrow
object Arrow {
trait Outbound extends Arrow
}

trait NodeKOrGraphK {}

trait NodeK extends NodeKOrGraphK {

type FBound <: Induction

protected def getInduction: Seq[FBound]
}

trait Induction {
def arrow: Arrow
def node: NodeK
}

object Induction {

trait FP[+N <: NodeK] extends Induction { // short for "fixed point"
def node: N
}
}

trait GraphK extends NodeKOrGraphK {

type Batch[+T] <: Iterable[T]

type _Node <: NodeK

def entries: Batch[_Node]
}

trait Topology {

type Node = NodeK { type FBound <: Topology.this.FBound }
trait Node_ extends NodeK {
type FBound = Topology.this.FBound
}

type FP = Induction.FP[Node]
type FBound <: FP

type Graph = GraphK { type _Node <: Node }
}

}
52 changes: 52 additions & 0 deletions tests/pos/i22257.orig.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

object Scaffold {

trait Arrow
object Arrow {
trait Outbound extends Arrow
}

trait NodeKOrGraphK {}

trait NodeK extends NodeKOrGraphK {

type FBound <: Induction

protected def getInduction: Seq[FBound]
}

trait Induction {
def arrow: Arrow
def node: NodeK
}

object Induction {

trait FP[+N <: NodeK] extends Induction { // short for "fixed point"
def node: N
}
}

trait GraphK extends NodeKOrGraphK {

type Batch[+T] <: Iterable[T]

type _Node <: NodeK

def entries: Batch[_Node]
}

trait Topology {

type FP = Induction.FP[Node]
type FBound <: FP

type Node = NodeK { type FBound <: Topology.this.FBound }
trait Node_ extends NodeK {
type FBound = Topology.this.FBound
}

type Graph = GraphK { type _Node <: Node }
}

}
26 changes: 26 additions & 0 deletions tests/pos/i22257.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
trait NodeK { type FBound }
trait Fixed[+N <: NodeK]

type Bound1 <: FP1
type FP1 = Fixed[Node1]
type Node1 = NodeK { type FBound <: Bound1 } // was-error

type FP2 = Fixed[Node2] // was-error
type Bound2 <: FP2
type Node2 = NodeK { type FBound <: Bound2 }

type Node3 = NodeK { type FBound <: Bound3 }
type FP3 = Fixed[Node3]
type Bound3 <: FP3

type Bound4 <: FP4
type Node4 = NodeK { type FBound <: Bound4 } // was-error
type FP4 = Fixed[Node4]

type FP5 = Fixed[Node5] // was-error
type Node5 = NodeK { type FBound <: Bound5 }
type Bound5 <: FP5

type Node6 = NodeK { type FBound <: Bound6 }
type Bound6 <: FP6
type FP6 = Fixed[Node6]