Skip to content

Commit 961cae9

Browse files
oderskyBlaisorblade
authored andcommitted
Add test for cyclic references involving members of inherited and intersected types
Test every template and every type intersection for cycles in its members. The tests are done as early possible without causing spurious cycles, which for intersections means PostTyper, unfortunately. Still missing: Do the test also for members reachable in common value definitions.
1 parent 4be1409 commit 961cae9

File tree

5 files changed

+104
-2
lines changed

5 files changed

+104
-2
lines changed

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4579,8 +4579,15 @@ object Types {
45794579
if (ctx.debug) printStackTrace()
45804580
}
45814581

4582+
def showPrefixSafely(pre: Type)(implicit ctx: Context): String = pre.stripTypeVar match {
4583+
case pre: TermRef => i"${pre.termSymbol.name}."
4584+
case pre: TypeRef => i"${pre.typeSymbol.name}#"
4585+
case pre: TypeProxy => showPrefixSafely(pre.underlying)
4586+
case _ => if (pre.typeSymbol.exists) i"${pre.typeSymbol.name}#" else "."
4587+
}
4588+
45824589
class CyclicFindMember(pre: Type, name: Name)(implicit ctx: Context) extends TypeError(
4583-
i"""member search with prefix $pre too deep.
4590+
i"""member search for ${showPrefixSafely(pre)}$name too deep.
45844591
|searches, from inner to outer: .${ctx.pendingMemberSearches}% .%""")
45854592

45864593
private def otherReason(pre: Type)(implicit ctx: Context): String = pre match {

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import scala.collection.mutable
66
import core._
77
import typer.Checking
88
import Types._, Contexts._, Names._, Flags._, DenotTransformers._
9-
import SymDenotations._, StdNames._, Annotations._, Trees._
9+
import SymDenotations._, StdNames._, Annotations._, Trees._, Scopes._
1010
import Decorators._
1111
import Symbols._, SymUtils._
1212
import reporting.diagnostic.messages.{ImportRenamedTwice, NotAMember, SuperCallsNotAllowedInline}
@@ -283,6 +283,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
283283
case tpe => tpe
284284
}
285285
)
286+
case tree: AndTypeTree =>
287+
// Ideally, this should be done by Typer, but we run into cyclic references
288+
// when trying to typecheck self types which are intersections.
289+
Checking.checkNonCyclicInherited(tree.tpe, tree.left.tpe :: tree.right.tpe :: Nil, EmptyScope, tree.pos)
290+
super.transform(tree)
286291
case Import(expr, selectors) =>
287292
val exprTpe = expr.tpe
288293
val seen = mutable.Set.empty[Name]

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Symbols._
1313
import Trees._
1414
import TreeInfo._
1515
import ProtoTypes._
16+
import Scopes._
1617
import CheckRealizable._
1718
import ErrorReporting.errorTree
1819

@@ -321,6 +322,36 @@ object Checking {
321322
checkTree((), refinement)
322323
}
323324

325+
/** Check type members inherited from different `parents` of `joint` type for cycles,
326+
* unless a type with the same name aleadry appears in `decls`.
327+
* @return true iff no cycles were detected
328+
*/
329+
def checkNonCyclicInherited(joint: Type, parents: List[Type], decls: Scope, pos: Position)(implicit ctx: Context): Unit = {
330+
def qualifies(sym: Symbol) = sym.name.isTypeName && !sym.is(Private)
331+
val abstractTypeNames =
332+
for (parent <- parents; mbr <- parent.abstractTypeMembers if qualifies(mbr.symbol))
333+
yield mbr.name.asTypeName
334+
335+
for (name <- abstractTypeNames)
336+
try {
337+
val mbr = joint.member(name)
338+
mbr.info match {
339+
case bounds: TypeBounds =>
340+
val res = checkNonCyclic(mbr.symbol, bounds, reportErrors = true).isError
341+
if (res)
342+
println(i"cyclic ${mbr.symbol}, $bounds -> $res")
343+
res
344+
case _ =>
345+
false
346+
}
347+
}
348+
catch {
349+
case ex: CyclicFindMember =>
350+
ctx.error(em"cyclic reference involving type $name", pos)
351+
true
352+
}
353+
}
354+
324355
/** Check that symbol's definition is well-formed. */
325356
def checkWellFormed(sym: Symbol)(implicit ctx: Context): Unit = {
326357
def fail(msg: Message) = ctx.error(msg, sym.pos)
@@ -520,6 +551,9 @@ trait Checking {
520551
def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type =
521552
Checking.checkNonCyclic(sym, info, reportErrors)
522553

554+
def checkNonCyclicInherited(joint: Type, parents: List[Type], decls: Scope, pos: Position)(implicit ctx: Context): Unit =
555+
Checking.checkNonCyclicInherited(joint, parents, decls, pos)
556+
523557
/** Check that Java statics and packages can only be used in selections.
524558
*/
525559
def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = {
@@ -906,6 +940,7 @@ trait ReChecking extends Checking {
906940
trait NoChecking extends ReChecking {
907941
import tpd._
908942
override def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = info
943+
override def checkNonCyclicInherited(joint: Type, parents: List[Type], decls: Scope, pos: Position)(implicit ctx: Context): Unit = ()
909944
override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree
910945
override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = ()
911946
override def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,6 +1546,8 @@ class Typer extends Namer
15461546
cls, isRequired, cdef.pos)
15471547
}
15481548

1549+
checkNonCyclicInherited(cls.thisType, cls.classParents, cls.info.decls, cdef.pos)
1550+
15491551
// check value class constraints
15501552
checkDerivedValueClass(cls, body1)
15511553

tests/run/returning.scala

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package scala.util.control {
2+
3+
object NonLocalReturns {
4+
5+
class ReturnThrowable[T] extends ControlThrowable {
6+
private var myResult: T = _
7+
def throwReturn(result: T): Nothing = {
8+
myResult = result
9+
throw this
10+
}
11+
def result: T = myResult
12+
}
13+
14+
def throwReturn[T](result: T)(implicit returner: ReturnThrowable[T]): Nothing =
15+
returner.throwReturn(result)
16+
17+
def returning[T](op: implicit ReturnThrowable[T] => T): T = {
18+
val returner = new ReturnThrowable[T]
19+
try op(returner)
20+
catch {
21+
case ex: ReturnThrowable[_] =>
22+
if (ex `eq` returner) ex.result.asInstanceOf[T] else throw ex
23+
}
24+
}
25+
}
26+
}
27+
28+
object Test extends App {
29+
30+
import scala.util.control.NonLocalReturns._
31+
import scala.collection.mutable.ListBuffer
32+
33+
def has(xs: List[Int], elem: Int) =
34+
returning {
35+
for (x <- xs)
36+
if (x == elem) throwReturn(true)
37+
false
38+
}
39+
40+
def takeUntil(xs: List[Int], elem: Int) =
41+
returning {
42+
var buf = new ListBuffer[Int]
43+
for (x <- xs)
44+
yield {
45+
if (x == elem) throwReturn(buf.toList)
46+
buf += x
47+
x
48+
}
49+
}
50+
51+
assert(has(List(1, 2, 3), 2))
52+
assert(takeUntil(List(1, 2, 3), 3) == List(1, 2))
53+
}

0 commit comments

Comments
 (0)