Skip to content

Fix #9346: Add fallback on lazy ref cycles #9546

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

Closed
wants to merge 3 commits into from
Closed
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
18 changes: 8 additions & 10 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,8 @@ object Types {
val rinfo = tp.refinedInfo
if (name.isTypeName && !pinfo.isInstanceOf[ClassInfo]) { // simplified case that runs more efficiently
val jointInfo =
if (ctx.base.pendingMemberSearches.contains(name)) pinfo safe_& rinfo
if rinfo.isInstanceOf[TypeAlias] then rinfo
else if (ctx.base.pendingMemberSearches.contains(name)) pinfo safe_& rinfo
else pinfo recoverable_& rinfo
pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo)
}
Expand Down Expand Up @@ -1029,12 +1030,10 @@ object Types {
*/
def recoverable_&(that: Type)(using Context): Type =
try this & that
catch {
case ex: CyclicReference => this safe_& that
// A test case where this happens is tests/pos/i536.scala.
// The & causes a subtype check which calls baseTypeRef again with the same
// superclass.
}
catch case ex: CyclicReference => this safe_& that
// A test case where this happens is tests/pos/i536.scala.
// The & causes a subtype check which calls baseTypeRef again with the same
// superclass.

def | (that: Type)(using Context): Type = {
record("|")
Expand Down Expand Up @@ -2641,10 +2640,9 @@ object Types {
def ref(using Context): Type =
if computed then
if myRef == null then
// if errors were reported previously handle this by throwing a CyclicReference
// instead of crashing immediately. A test case is neg/i6057.scala.
assert(reportCycles || ctx.reporter.errorsReported)
throw CyclicReference(NoDenotation)
// i9346.scala shows that such cycles can arise in normal code
// when trying to compute asSeenFrom on refined types.
else
computed = true
val result = refFn
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Variances.scala
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ object Variances {
def traverse(t: Type): Unit = t match
case t: TypeParamRef if t.binder eq lam =>
lam.typeParams(t.paramNum).storedVariance &= varianceFromInt(variance)
case t: LazyRef =>
try traverse(t.ref)
catch case _: CyclicReference =>
() // can happen with deeply entangled F-bounds, such as in i9364.scala under -Ytest-pickler
case _ =>
traverseChildren(t)
}
Expand Down
28 changes: 28 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,34 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
else
Checking.checkAppliedType(tree)
super.transform(tree)
case tree: RefinedTypeTree =>
//println(i"checking $tree: ${tree.tpe}")
def checkRefinements(tp: Type): Unit = tp match
case RefinedType(parent, name, info) =>
info match
case info: TypeAlias =>
val mbr = parent.member(name)
mbr.info match
case bounds: TypeBounds if !bounds.contains(info) =>
//println(i"falling back to checking F-bounded ${tree.tpe}")
val site = tree.tpe.narrow
val narrowMbr = mbr.asSeenFrom(site)
val owner = mbr.symbol.maybeOwner
val narrowInfo = info.asSeenFrom(site, owner)
val ok = narrowMbr.info match
case bounds: TypeBounds => bounds.contains(narrowInfo)
case _ => false
//println(i"fall back ${narrowMbr.info} $narrowInfo")
if !ok then
report.error(
em"""type alias type $name$info
|out of bounds: $bounds / ${bounds.toString}""", tree.sourcePos)
case _ =>
case _ =>
checkRefinements(parent)
case _ =>
checkRefinements(tree.tpe)
super.transform(tree)
case SingletonTypeTree(ref) =>
Checking.checkRealizable(ref.tpe, ref.posd)
super.transform(tree)
Expand Down
12 changes: 12 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,18 @@ object Checking {
withMode(Mode.AllowLambdaWildcardApply)(checkValidIfApply)
}

/** Check refined type for well-formedness. This means
* - all arguments are within their corresponding bounds
* - if type is a higher-kinded application with wildcard arguments,
* check that it or one of its supertypes can be reduced to a normal application.
* Unreducible applications correspond to general existentials, and we
* cannot handle those.
* @param tree The applied type tree to check
* @param tpt If `tree` is synthesized from a type in a TypeTree,
* the original TypeTree, or EmptyTree otherwise.
def checkRefinedType(tp: RefinedType)
*/

/** Check all applied type trees in inferred type `tpt` for well-formedness */
def checkAppliedTypesIn(tpt: TypeTree)(using Context): Unit =
val checker = new TypeTraverser:
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/i6225.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
object O1 { // error: cannot be instantiated
object O1 {
type A[X] = X
opaque type T = A // error: opaque type alias must be fully applied
}

object O2 {
opaque type A[X] = X
object A { // error: cannot be instantiated
object A {
opaque type T = A // error: opaque type alias must be fully applied
}
}
Expand Down
9 changes: 0 additions & 9 deletions tests/pending/pos/i9346.scala

This file was deleted.

7 changes: 7 additions & 0 deletions tests/pos-deep-subtype/i9346.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
trait Foo {
type Repr[+O] <: Foo {
type Repr[+OO] = Foo.this.Repr[OO]
}

def foo[T](f: Repr[T]): f.Repr[T] = ???
}