Skip to content

Commit 004a2fd

Browse files
authored
Merge pull request #15443 from dotty-staging/fix-15311
Handle recursions in isFullyDefined
2 parents 520beb2 + 0e1d53c commit 004a2fd

File tree

9 files changed

+121
-60
lines changed

9 files changed

+121
-60
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,7 @@ trait Applications extends Compatibility {
11181118
*/
11191119
def convertNewGenericArray(tree: Tree)(using Context): Tree = tree match {
11201120
case Apply(TypeApply(tycon, targs@(targ :: Nil)), args) if tycon.symbol == defn.ArrayConstructor =>
1121-
fullyDefinedType(tree.tpe, "array", tree.span)
1121+
fullyDefinedType(tree.tpe, "array", tree.srcPos)
11221122

11231123
def newGenericArrayCall =
11241124
ref(defn.DottyArraysModule)
@@ -1346,7 +1346,7 @@ trait Applications extends Compatibility {
13461346
val ownType =
13471347
if (selType <:< unapplyArgType) {
13481348
unapp.println(i"case 1 $unapplyArgType ${ctx.typerState.constraint}")
1349-
fullyDefinedType(unapplyArgType, "pattern selector", tree.span)
1349+
fullyDefinedType(unapplyArgType, "pattern selector", tree.srcPos)
13501350
selType.dropAnnot(defn.UncheckedAnnot) // need to drop @unchecked. Just because the selector is @unchecked, the pattern isn't.
13511351
}
13521352
else {
@@ -1577,7 +1577,7 @@ trait Applications extends Compatibility {
15771577
// `isSubType` as a TypeVar might get constrained by a TypeRef it's
15781578
// part of.
15791579
val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType)
1580-
fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.span)
1580+
fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.srcPos)
15811581

15821582
val tparams = newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_))
15831583
isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ class ImplicitSearchError(
257257
++ ErrorReporting.matchReductionAddendum(pt)
258258
}
259259

260-
private def formatMsg(shortForm: String)(headline: String = shortForm) = arg match
260+
private def formatMsg(shortForm: String)(headline: String = shortForm) = arg match
261261
case arg: Trees.SearchFailureIdent[?] =>
262262
arg.tpe match
263263
case _: NoMatchingImplicits => headline
@@ -318,7 +318,7 @@ class ImplicitSearchError(
318318
case _ => Nil
319319
}
320320
def resolveTypes(targs: List[tpd.Tree])(using Context) =
321-
targs.map(a => Inferencing.fullyDefinedType(a.tpe, "type argument", a.span))
321+
targs.map(a => Inferencing.fullyDefinedType(a.tpe, "type argument", a.srcPos))
322322

323323
// We can extract type arguments from:
324324
// - a function call:

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -549,16 +549,16 @@ object Implicits:
549549
override def msg(using Context) = _msg
550550
def explanation(using Context) = msg.toString
551551

552-
/** A search failure type for failed synthesis of terms for special types */
552+
/** A search failure type for failed synthesis of terms for special types */
553553
class SynthesisFailure(reasons: List[String], val expectedType: Type) extends SearchFailureType:
554554
def argument = EmptyTree
555555

556-
private def formatReasons =
557-
if reasons.length > 1 then
558-
reasons.mkString("\n\t* ", "\n\t* ", "")
559-
else
556+
private def formatReasons =
557+
if reasons.length > 1 then
558+
reasons.mkString("\n\t* ", "\n\t* ", "")
559+
else
560560
reasons.mkString
561-
561+
562562
def explanation(using Context) = em"Failed to synthesize an instance of type ${clarify(expectedType)}: ${formatReasons}"
563563

564564
end Implicits
@@ -871,7 +871,7 @@ trait Implicits:
871871
SearchFailure(new SynthesisFailure(errors, formal), span).tree
872872
else
873873
tree.orElse(failed)
874-
874+
875875

876876
/** Search an implicit argument and report error if not found */
877877
def implicitArgTree(formal: Type, span: Span)(using Context): Tree = {
@@ -1149,15 +1149,17 @@ trait Implicits:
11491149

11501150
private def isCoherent = pt.isRef(defn.CanEqualClass)
11511151

1152-
val wideProto = pt.widenExpr
1152+
private val wideProto = pt.widenExpr
1153+
1154+
private val srcPos = ctx.source.atSpan(span)
11531155

11541156
/** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */
1155-
val wildProto: Type =
1157+
private val wildProto: Type =
11561158
if argument.isEmpty then wildApprox(pt)
11571159
else ViewProto(wildApprox(argument.tpe.widen.normalized), wildApprox(pt))
11581160
// Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though.
11591161

1160-
val isNotGiven: Boolean = wildProto.classSymbol == defn.NotGivenClass
1162+
private val isNotGiven: Boolean = wildProto.classSymbol == defn.NotGivenClass
11611163

11621164
private def searchTooLarge(): Boolean = ctx.searchHistory match
11631165
case root: SearchRoot =>
@@ -1170,7 +1172,7 @@ trait Implicits:
11701172
if result then
11711173
var c = ctx
11721174
while c.outer.typer eq ctx.typer do c = c.outer
1173-
report.warning(ImplicitSearchTooLargeWarning(limit, h.openSearchPairs), ctx.source.atSpan(span))(using c)
1175+
report.warning(ImplicitSearchTooLargeWarning(limit, h.openSearchPairs), srcPos)(using c)
11741176
else
11751177
h.root.nestedSearches = nestedSearches + 1
11761178
result
@@ -1347,7 +1349,7 @@ trait Implicits:
13471349
|the search will fail with a global ambiguity error instead.
13481350
|
13491351
|Consider using the scala.util.NotGiven class to implement similar functionality.""",
1350-
ctx.source.atSpan(span))
1352+
srcPos)
13511353

13521354
/** Compare the length of the baseClasses of two symbols (except for objects,
13531355
* where we use the length of the companion class instead if it's bigger).
@@ -1565,7 +1567,7 @@ trait Implicits:
15651567
if cand1.ref eq cand.ref then
15661568
lazy val wildTp = wildApprox(tp.widenExpr)
15671569
if belowByname && (wildTp <:< wildPt) then
1568-
fullyDefinedType(tp, "by-name implicit parameter", span)
1570+
fullyDefinedType(tp, "by-name implicit parameter", srcPos)
15691571
false
15701572
else if prev.typeSize > ptSize || prev.coveringSet != ptCoveringSet then
15711573
loop(outer, tp.isByName || belowByname)

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

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Contexts._, Types._, Flags._, Symbols._
88
import ProtoTypes._
99
import NameKinds.{AvoidNameKind, UniqueName}
1010
import util.Spans._
11-
import util.{Stats, SimpleIdentityMap}
11+
import util.{Stats, SimpleIdentityMap, SrcPos}
1212
import Decorators._
1313
import config.Printers.{gadts, typr}
1414
import annotation.tailrec
@@ -28,11 +28,11 @@ object Inferencing {
2828
* but only if the overall result of `isFullyDefined` is `true`.
2929
* Variables that are successfully minimized do not count as uninstantiated.
3030
*/
31-
def isFullyDefined(tp: Type, force: ForceDegree.Value)(using Context): Boolean = {
31+
def isFullyDefined(tp: Type, force: ForceDegree.Value, handleOverflow: Boolean = false)(using Context): Boolean = {
3232
val nestedCtx = ctx.fresh.setNewTyperState()
3333
val result =
3434
try new IsFullyDefinedAccumulator(force)(using nestedCtx).process(tp)
35-
catch case ex: StackOverflowError =>
35+
catch case ex: RecursionOverflow if handleOverflow =>
3636
false // can happen for programs with illegal recusions, e.g. neg/recursive-lower-constraint.scala
3737
if (result) nestedCtx.typerState.commit()
3838
result
@@ -49,9 +49,13 @@ object Inferencing {
4949
/** The fully defined type, where all type variables are forced.
5050
* Throws an error if type contains wildcards.
5151
*/
52-
def fullyDefinedType(tp: Type, what: String, span: Span)(using Context): Type =
53-
if (isFullyDefined(tp, ForceDegree.all)) tp
54-
else throw new Error(i"internal error: type of $what $tp is not fully defined, pos = $span") // !!! DEBUG
52+
def fullyDefinedType(tp: Type, what: String, pos: SrcPos)(using Context): Type =
53+
try
54+
if isFullyDefined(tp, ForceDegree.all) then tp
55+
else throw new Error(i"internal error: type of $what $tp is not fully defined, pos = $pos")
56+
catch case ex: RecursionOverflow =>
57+
report.error(ex, pos)
58+
UnspecifiedErrorType
5559

5660
/** Instantiate selected type variables `tvars` in type `tp` in a special mode:
5761
* 1. If a type variable is constrained from below (i.e. constraint bound != given lower bound)
@@ -171,33 +175,37 @@ object Inferencing {
171175

172176
private var toMaximize: List[TypeVar] = Nil
173177

174-
def apply(x: Boolean, tp: Type): Boolean = tp.dealias match {
175-
case _: WildcardType | _: ProtoType =>
176-
false
177-
case tvar: TypeVar if !tvar.isInstantiated =>
178-
force.appliesTo(tvar)
179-
&& ctx.typerState.constraint.contains(tvar)
180-
&& {
181-
val direction = instDirection(tvar.origin)
182-
if minimizeSelected then
183-
if direction <= 0 && tvar.hasLowerBound then
178+
def apply(x: Boolean, tp: Type): Boolean =
179+
try tp.dealias match
180+
case _: WildcardType | _: ProtoType =>
181+
false
182+
case tvar: TypeVar if !tvar.isInstantiated =>
183+
force.appliesTo(tvar)
184+
&& ctx.typerState.constraint.contains(tvar)
185+
&& {
186+
val direction = instDirection(tvar.origin)
187+
if minimizeSelected then
188+
if direction <= 0 && tvar.hasLowerBound then
189+
instantiate(tvar, fromBelow = true)
190+
else if direction >= 0 && tvar.hasUpperBound then
191+
instantiate(tvar, fromBelow = false)
192+
// else hold off instantiating unbounded unconstrained variable
193+
else if direction != 0 then
194+
instantiate(tvar, fromBelow = direction < 0)
195+
else if variance >= 0 && (force.ifBottom == IfBottom.ok || tvar.hasLowerBound) then
184196
instantiate(tvar, fromBelow = true)
185-
else if direction >= 0 && tvar.hasUpperBound then
186-
instantiate(tvar, fromBelow = false)
187-
// else hold off instantiating unbounded unconstrained variable
188-
else if direction != 0 then
189-
instantiate(tvar, fromBelow = direction < 0)
190-
else if variance >= 0 && (force.ifBottom == IfBottom.ok || tvar.hasLowerBound) then
191-
instantiate(tvar, fromBelow = true)
192-
else if variance >= 0 && force.ifBottom == IfBottom.fail then
193-
return false
194-
else
195-
toMaximize = tvar :: toMaximize
196-
foldOver(x, tvar)
197-
}
198-
case tp =>
199-
foldOver(x, tp)
200-
}
197+
else if variance >= 0 && force.ifBottom == IfBottom.fail then
198+
return false
199+
else
200+
toMaximize = tvar :: toMaximize
201+
foldOver(x, tvar)
202+
}
203+
case tp =>
204+
reporting.trace(s"IFT $tp") {
205+
foldOver(x, tp)
206+
}
207+
catch case ex: Throwable =>
208+
handleRecursive("check fully defined", tp.show, ex)
201209

202210
def process(tp: Type): Boolean =
203211
// Maximize type vars in the order they were visited before */
@@ -313,7 +321,7 @@ object Inferencing {
313321
val (tl1, tvars) = constrained(tl, tree)
314322
var tree1 = AppliedTypeTree(tree.withType(tl1), tvars)
315323
tree1.tpe <:< pt
316-
fullyDefinedType(tree1.tpe, "template parent", tree.span)
324+
fullyDefinedType(tree1.tpe, "template parent", tree.srcPos)
317325
tree1
318326
case _ =>
319327
tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,7 +1467,7 @@ class Namer { typer: Typer =>
14671467
else {
14681468
if (denot.is(ModuleClass) && denot.sourceModule.isOneOf(GivenOrImplicit))
14691469
missingType(denot.symbol, "parent ")(using creationContext)
1470-
fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.span)
1470+
fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.srcPos)
14711471
}
14721472
case _ =>
14731473
UnspecifiedErrorType.assertingErrorsReported
@@ -1890,7 +1890,7 @@ class Namer { typer: Typer =>
18901890
def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp
18911891

18921892
def cookedRhsType = dealiasIfUnit(rhsType).deskolemized
1893-
def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.span)
1893+
def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.srcPos)
18941894
//if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType")
18951895
if (inherited.exists)
18961896
if sym.isInlineVal then lhsType else inherited

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
5353
val synthesizedTypeTest: SpecialHandler =
5454
(formal, span) => formal.argInfos match {
5555
case arg1 :: arg2 :: Nil if !defn.isBottomClass(arg2.typeSymbol) =>
56-
val tp1 = fullyDefinedType(arg1, "TypeTest argument", span)
57-
val tp2 = fullyDefinedType(arg2, "TypeTest argument", span).normalized
56+
val srcPos = ctx.source.atSpan(span)
57+
val tp1 = fullyDefinedType(arg1, "TypeTest argument", srcPos)
58+
val tp2 = fullyDefinedType(arg2, "TypeTest argument", srcPos).normalized
5859
val sym2 = tp2.typeSymbol
5960
if tp1 <:< tp2 then
6061
// optimization when we know the typetest will always succeed
@@ -192,7 +193,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
192193

193194
formal.argTypes match
194195
case args @ (arg1 :: arg2 :: Nil) =>
195-
List(arg1, arg2).foreach(fullyDefinedType(_, "eq argument", span))
196+
List(arg1, arg2).foreach(fullyDefinedType(_, "eq argument", ctx.source.atSpan(span)))
196197
if canComparePredefined(arg1, arg2)
197198
|| !Implicits.strictEquality && explore(validEqAnyArgs(arg1, arg2))
198199
then withNoErrors(ref(defn.CanEqual_canEqualAny).appliedToTypes(args).withSpan(span))
@@ -209,7 +210,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
209210
New(defn.ValueOfClass.typeRef.appliedTo(t.tpe), t :: Nil).withSpan(span)
210211
formal.argInfos match
211212
case arg :: Nil =>
212-
fullyDefinedType(arg, "ValueOf argument", span).normalized.dealias match
213+
fullyDefinedType(arg, "ValueOf argument", ctx.source.atSpan(span)).normalized.dealias match
213214
case ConstantType(c: Constant) =>
214215
withNoErrors(success(Literal(c)))
215216
case tp: TypeRef if tp.isRef(defn.UnitClass) =>
@@ -649,7 +650,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
649650

650651
formal.argInfos match
651652
case arg :: Nil =>
652-
val manifest = synthesize(fullyDefinedType(arg, "Manifest argument", span), kind, topLevel = true)
653+
val manifest = synthesize(fullyDefinedType(arg, "Manifest argument", ctx.source.atSpan(span)), kind, topLevel = true)
653654
if manifest != EmptyTree then
654655
report.deprecationWarning(
655656
i"""Compiler synthesis of Manifest and OptManifest is deprecated, instead

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,7 +1101,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
11011101
def noLeaks(t: Tree): Boolean = escapingRefs(t, localSyms).isEmpty
11021102
if (noLeaks(tree)) tree
11031103
else {
1104-
fullyDefinedType(tree.tpe, "block", tree.span)
1104+
fullyDefinedType(tree.tpe, "block", tree.srcPos)
11051105
var avoidingType = TypeOps.avoid(tree.tpe, localSyms)
11061106
val ptDefined = isFullyDefined(pt, ForceDegree.none)
11071107
if (ptDefined && !(avoidingType.widenExpr <:< pt)) avoidingType = pt
@@ -1534,7 +1534,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
15341534
case _ =>
15351535
if tree.isInline then checkInInlineContext("inline match", tree.srcPos)
15361536
val sel1 = typedExpr(tree.selector)
1537-
val rawSelectorTpe = fullyDefinedType(sel1.tpe, "pattern selector", tree.span)
1537+
val rawSelectorTpe = fullyDefinedType(sel1.tpe, "pattern selector", tree.srcPos)
15381538
val selType = rawSelectorTpe match
15391539
case c: ConstantType if tree.isInline => c
15401540
case otherTpe => otherTpe.widen

tests/neg/i15311.check

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
-- Error: tests/neg/i15311.scala:16:4 ----------------------------------------------------------------------------------
2+
16 |def test = // error
3+
|^
4+
|Recursion limit exceeded.
5+
|Maybe there is an illegal cyclic reference?
6+
|If that's not the case, you could also try to increase the stacksize using the -Xss JVM option.
7+
|A recurring operation is (inner to outer):
8+
|
9+
| check fully defined food.T
10+
| check fully defined food.T
11+
| check fully defined food.T
12+
| check fully defined food.T
13+
| check fully defined food.T
14+
| check fully defined food.T
15+
| check fully defined food.T
16+
| check fully defined food.T
17+
| check fully defined food.T
18+
| check fully defined food.T
19+
| ...
20+
|
21+
| check fully defined food.T
22+
| check fully defined food.T
23+
| check fully defined food.T
24+
| check fully defined food.T
25+
| check fully defined food.T
26+
| check fully defined food.T
27+
| check fully defined food.T
28+
| check fully defined food.T
29+
| check fully defined food.T
30+
| check fully defined Template[food.T]
31+
17 | eat(ham)
32+
18 | eat(food.self)

tests/neg/i15311.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
trait Template[+T <: Template[T]]:
2+
type Clone <: T { type Clone = Template.this.Clone }
3+
val self :Clone
4+
5+
type Food = Template[_]
6+
7+
class Ham extends Template[Ham]:
8+
type Clone = Ham
9+
val self = this
10+
11+
def eat[F <: Template[F]](food :F) :F = food.self.self
12+
13+
val ham = new Ham
14+
val food :Food = ham
15+
16+
def test = // error
17+
eat(ham)
18+
eat(food.self)

0 commit comments

Comments
 (0)