Skip to content

Commit 8207b76

Browse files
committed
Further refactoring of stackoverflow handling
This now handles all errors from #4368 to #4372 and also #318.
1 parent 8cc0b81 commit 8207b76

File tree

8 files changed

+192
-22
lines changed

8 files changed

+192
-22
lines changed

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ class TypeApplications(val self: Type) extends AnyVal {
168168
* any type parameter that is-rebound by the refinement.
169169
*/
170170
final def typeParams(implicit ctx: Context): List[TypeParamInfo] = /*>|>*/ track("typeParams") /*<|<*/ {
171-
self match {
171+
try self match {
172172
case self: TypeRef =>
173173
val tsym = self.symbol
174174
if (tsym.isClass) tsym.typeParams
@@ -193,6 +193,9 @@ class TypeApplications(val self: Type) extends AnyVal {
193193
case _ =>
194194
Nil
195195
}
196+
catch {
197+
case ex: Throwable => handleRecursive("type parameters of", self.show, ex)
198+
}
196199
}
197200

198201
/** If `self` is a higher-kinded type, its type parameters, otherwise Nil */

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

+7-2
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
121121
protected def isSubType(tp1: Type, tp2: Type, a: ApproxState): Boolean = {
122122
val saved = approx
123123
this.approx = a
124-
try recur(tp1, tp2) finally this.approx = saved
124+
try recur(tp1, tp2)
125+
catch {
126+
case ex: Throwable => handleRecursive("subtype", i"$tp1 <:< $tp2", ex, weight = 2)
127+
}
128+
finally this.approx = saved
125129
}
126130

127131
protected def isSubType(tp1: Type, tp2: Type): Boolean = isSubType(tp1, tp2, NoApprox)
@@ -161,7 +165,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
161165
try {
162166
pendingSubTypes += p
163167
firstTry
164-
} finally {
168+
}
169+
finally {
165170
pendingSubTypes -= p
166171
}
167172
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package dotty.tools
2+
package dotc
3+
package core
4+
5+
import util.common._
6+
import Types._
7+
import Symbols._
8+
import Flags._
9+
import Names._
10+
import Contexts._
11+
import SymDenotations._
12+
import Denotations._
13+
import Decorators._
14+
import reporting.diagnostic.Message
15+
import reporting.diagnostic.messages._
16+
import ast.untpd
17+
import config.Printers.cyclicErrors
18+
19+
class TypeError(msg: String) extends Exception(msg) {
20+
def this() = this("")
21+
def toMessage(implicit ctx: Context): Message = getMessage
22+
}
23+
24+
class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name]) extends TypeError {
25+
override def toMessage(implicit ctx: Context): Message =
26+
i"malformed type: $pre is not a legal prefix for $denot because it contains abstract type member${if (absMembers.size == 1) "" else "s"} ${absMembers.mkString(", ")}"
27+
}
28+
29+
class MissingType(pre: Type, name: Name) extends TypeError {
30+
private def otherReason(pre: Type)(implicit ctx: Context): String = pre match {
31+
case pre: ThisType if pre.cls.givenSelfType.exists =>
32+
i"\nor the self type of $pre might not contain all transitive dependencies"
33+
case _ => ""
34+
}
35+
36+
override def toMessage(implicit ctx: Context): Message = {
37+
if (ctx.debug) printStackTrace()
38+
i"""cannot resolve reference to type $pre.$name
39+
|the classfile defining the type might be missing from the classpath${otherReason(pre)}"""
40+
}
41+
}
42+
43+
class RecursionOverflow(val op: String, details: => String, previous: Throwable, val weight: Int) extends TypeError {
44+
45+
def explanation = s"$op $details"
46+
47+
private def recursions: List[RecursionOverflow] = {
48+
val nested = previous match {
49+
case previous: RecursionOverflow => previous.recursions
50+
case _ => Nil
51+
}
52+
this :: nested
53+
}
54+
55+
def opsString(rs: List[RecursionOverflow])(implicit ctx: Context): String = {
56+
val maxShown = 20
57+
if (rs.lengthCompare(maxShown) > 0)
58+
i"""${opsString(rs.take(maxShown / 2))}
59+
| ...
60+
|${opsString(rs.takeRight(maxShown / 2))}"""
61+
else
62+
(rs.map(_.explanation): List[String]).mkString("\n ", "\n| ", "")
63+
}
64+
65+
override def toMessage(implicit ctx: Context): Message = {
66+
val mostCommon = recursions.groupBy(_.op).toList.maxBy(_._2.map(_.weight).sum)._2.reverse
67+
s"""Recursion limit exceeded.
68+
|Maybe there is an illegal cyclic reference?
69+
|A recurring operation is (inner to outer):
70+
|${opsString(mostCommon)}""".stripMargin
71+
}
72+
73+
override def fillInStackTrace(): Throwable = this
74+
override def getStackTrace() = previous.getStackTrace()
75+
}
76+
77+
object handleRecursive {
78+
def apply(op: String, details: => String, exc: Throwable, weight: Int = 1): Nothing = exc match {
79+
case _: RecursionOverflow =>
80+
throw new RecursionOverflow(op, details, exc, weight)
81+
case _ =>
82+
var e = exc
83+
while (e != null && !e.isInstanceOf[StackOverflowError]) e = e.getCause
84+
if (e != null) throw new RecursionOverflow(op, details, e, weight)
85+
else throw exc
86+
}
87+
}
88+
89+
class CyclicReference private (val denot: SymDenotation) extends TypeError {
90+
override def toMessage(implicit ctx: Context) = {
91+
val cycleSym = denot.symbol
92+
def errorMsg(msg: Message, cx: Context): Message =
93+
if (cx.mode is Mode.InferringReturnType) {
94+
cx.tree match {
95+
case tree: untpd.DefDef if !tree.tpt.typeOpt.exists =>
96+
OverloadedOrRecursiveMethodNeedsResultType(tree.name)
97+
case tree: untpd.ValDef if !tree.tpt.typeOpt.exists =>
98+
RecursiveValueNeedsResultType(tree.name)
99+
case _ =>
100+
errorMsg(msg, cx.outer)
101+
}
102+
} else msg
103+
104+
if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm)
105+
CyclicReferenceInvolvingImplicit(cycleSym)
106+
else
107+
CyclicReferenceInvolving(denot)
108+
}
109+
}
110+
111+
object CyclicReference {
112+
def apply(denot: SymDenotation)(implicit ctx: Context): CyclicReference = {
113+
val ex = new CyclicReference(denot)
114+
if (!(ctx.mode is Mode.CheckCyclic)) {
115+
cyclicErrors.println(ex.getMessage)
116+
for (elem <- ex.getStackTrace take 200)
117+
cyclicErrors.println(elem.toString)
118+
}
119+
ex
120+
}
121+
}
122+
123+
class MergeError(msg: String, val tp1: Type, val tp2: Type) extends TypeError(msg)

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,15 @@ object Types {
652652
catch {
653653
case ex: Throwable =>
654654
core.println(s"findMember exception for $this member $name, pre = $pre, recCount = $recCount")
655-
throw new RecursionOverflow("find-member", pre, name, ex)
655+
656+
def showPrefixSafely(pre: Type)(implicit ctx: Context): String = pre.stripTypeVar match {
657+
case pre: TermRef => i"${pre.termSymbol.name}."
658+
case pre: TypeRef => i"${pre.typeSymbol.name}#"
659+
case pre: TypeProxy => showPrefixSafely(pre.underlying)
660+
case _ => if (pre.typeSymbol.exists) i"${pre.typeSymbol.name}#" else "."
661+
}
662+
663+
handleRecursive("find-member", i"${showPrefixSafely(pre)}$name", ex)
656664
}
657665
finally {
658666
if (recCount >= Config.LogPendingFindMemberThreshold)

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

+19-15
Original file line numberDiff line numberDiff line change
@@ -82,21 +82,25 @@ class VarianceChecker()(implicit ctx: Context) {
8282
* same is true of the parameters (ValDefs).
8383
*/
8484
def apply(status: Option[VarianceError], tp: Type): Option[VarianceError] = trace(s"variance checking $tp of $base at $variance", variances) {
85-
if (status.isDefined) status
86-
else tp match {
87-
case tp: TypeRef =>
88-
val sym = tp.symbol
89-
if (sym.variance != 0 && base.isContainedIn(sym.owner)) checkVarianceOfSymbol(sym)
90-
else if (sym.isAliasType) this(status, sym.info.bounds.hi)
91-
else foldOver(status, tp)
92-
case tp: MethodOrPoly =>
93-
this(status, tp.resultType) // params will be checked in their TypeDef or ValDef nodes.
94-
case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedVarianceAnnot =>
95-
status
96-
//case tp: ClassInfo =>
97-
// ??? not clear what to do here yet. presumably, it's all checked at local typedefs
98-
case _ =>
99-
foldOver(status, tp)
85+
try
86+
if (status.isDefined) status
87+
else tp match {
88+
case tp: TypeRef =>
89+
val sym = tp.symbol
90+
if (sym.variance != 0 && base.isContainedIn(sym.owner)) checkVarianceOfSymbol(sym)
91+
else if (sym.isAliasType) this(status, sym.info.bounds.hi)
92+
else foldOver(status, tp)
93+
case tp: MethodOrPoly =>
94+
this(status, tp.resultType) // params will be checked in their TypeDef or ValDef nodes.
95+
case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedVarianceAnnot =>
96+
status
97+
//case tp: ClassInfo =>
98+
// ??? not clear what to do here yet. presumably, it's all checked at local typedefs
99+
case _ =>
100+
foldOver(status, tp)
101+
}
102+
catch {
103+
case ex: Throwable => handleRecursive("variance check of", tp.show, ex)
100104
}
101105
}
102106

compiler/test/dotty/tools/dotc/CompilationTests.scala

+1
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ class CompilationTests extends ParallelTesting {
186186
compileFile("tests/neg-custom-args/noimports.scala", defaultOptions.and("-Yno-imports")) +
187187
compileFile("tests/neg-custom-args/noimports2.scala", defaultOptions.and("-Yno-imports")) +
188188
compileFile("tests/neg-custom-args/i3882.scala", allowDeepSubtypes) +
189+
compileFile("tests/neg-custom-args/i4372.scala", allowDeepSubtypes) +
189190
compileFile("tests/neg-custom-args/i1754.scala", allowDeepSubtypes) +
190191
compileFilesInDir("tests/neg-custom-args/isInstanceOf", allowDeepSubtypes and "-Xfatal-warnings") +
191192
compileFile("tests/neg-custom-args/i3627.scala", allowDeepSubtypes)

tests/neg-custom-args/i4372.scala

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object i4372 {
2+
class X[A >: X[_ <: X[_]] <: X[A]]
3+
}

tests/neg/i4368.scala

+26-3
Original file line numberDiff line numberDiff line change
@@ -106,18 +106,16 @@ object Test7 {
106106
type Nat = Fix[Option]#T
107107
}
108108
}
109-
/*
110109
object Test8 {
111110

112111
class A {
113-
type T = B#U
112+
type T = B#U // error: cyclic
114113
}
115114

116115
class B {
117116
type U = A#T
118117
}
119118
}
120-
*/
121119
object Test9 {
122120
trait W {
123121
type A
@@ -147,4 +145,29 @@ object Test9 {
147145
// scalac reports a volatility error, but the dotty equivalent (checkRealizable)
148146
// is checked too late.
149147
}
148+
}
149+
object i4369 {
150+
trait X { self =>
151+
type R <: Z
152+
type Z >: X { type R = self.R; type Z = self.R }
153+
}
154+
class Foo extends X { type R = Foo; type Z = Foo } // error: too deep
155+
}
156+
object i4370 {
157+
class Foo { type R = A } // error: cyclic
158+
type A = List[Foo#R]
159+
}
160+
object i4371 {
161+
class Foo { type A = Boo#B } // error: cyclic
162+
class Boo { type B = Foo#A }
163+
}
164+
object i318 {
165+
trait Y {
166+
type A <: { type T >: B }
167+
type B >: { type T >: A }
168+
}
169+
170+
val y: Y = ???
171+
val a: y.A = ??? // error: too deep
172+
val b: y.B = a // error: too deep
150173
}

0 commit comments

Comments
 (0)