Skip to content

Commit

Permalink
Merge pull request #15443 from dotty-staging/fix-15311
Browse files Browse the repository at this point in the history
Handle recursions in isFullyDefined
  • Loading branch information
smarter authored Jun 15, 2022
2 parents 520beb2 + 0e1d53c commit 004a2fd
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 60 deletions.
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,7 @@ trait Applications extends Compatibility {
*/
def convertNewGenericArray(tree: Tree)(using Context): Tree = tree match {
case Apply(TypeApply(tycon, targs@(targ :: Nil)), args) if tycon.symbol == defn.ArrayConstructor =>
fullyDefinedType(tree.tpe, "array", tree.span)
fullyDefinedType(tree.tpe, "array", tree.srcPos)

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

val tparams = newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_))
isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ class ImplicitSearchError(
++ ErrorReporting.matchReductionAddendum(pt)
}

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

// We can extract type arguments from:
// - a function call:
Expand Down
28 changes: 15 additions & 13 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -549,16 +549,16 @@ object Implicits:
override def msg(using Context) = _msg
def explanation(using Context) = msg.toString

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

private def formatReasons =
if reasons.length > 1 then
reasons.mkString("\n\t* ", "\n\t* ", "")
else
private def formatReasons =
if reasons.length > 1 then
reasons.mkString("\n\t* ", "\n\t* ", "")
else
reasons.mkString

def explanation(using Context) = em"Failed to synthesize an instance of type ${clarify(expectedType)}: ${formatReasons}"

end Implicits
Expand Down Expand Up @@ -871,7 +871,7 @@ trait Implicits:
SearchFailure(new SynthesisFailure(errors, formal), span).tree
else
tree.orElse(failed)


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

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

val wideProto = pt.widenExpr
private val wideProto = pt.widenExpr

private val srcPos = ctx.source.atSpan(span)

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

val isNotGiven: Boolean = wildProto.classSymbol == defn.NotGivenClass
private val isNotGiven: Boolean = wildProto.classSymbol == defn.NotGivenClass

private def searchTooLarge(): Boolean = ctx.searchHistory match
case root: SearchRoot =>
Expand All @@ -1170,7 +1172,7 @@ trait Implicits:
if result then
var c = ctx
while c.outer.typer eq ctx.typer do c = c.outer
report.warning(ImplicitSearchTooLargeWarning(limit, h.openSearchPairs), ctx.source.atSpan(span))(using c)
report.warning(ImplicitSearchTooLargeWarning(limit, h.openSearchPairs), srcPos)(using c)
else
h.root.nestedSearches = nestedSearches + 1
result
Expand Down Expand Up @@ -1347,7 +1349,7 @@ trait Implicits:
|the search will fail with a global ambiguity error instead.
|
|Consider using the scala.util.NotGiven class to implement similar functionality.""",
ctx.source.atSpan(span))
srcPos)

/** Compare the length of the baseClasses of two symbols (except for objects,
* where we use the length of the companion class instead if it's bigger).
Expand Down Expand Up @@ -1565,7 +1567,7 @@ trait Implicits:
if cand1.ref eq cand.ref then
lazy val wildTp = wildApprox(tp.widenExpr)
if belowByname && (wildTp <:< wildPt) then
fullyDefinedType(tp, "by-name implicit parameter", span)
fullyDefinedType(tp, "by-name implicit parameter", srcPos)
false
else if prev.typeSize > ptSize || prev.coveringSet != ptCoveringSet then
loop(outer, tp.isByName || belowByname)
Expand Down
74 changes: 41 additions & 33 deletions compiler/src/dotty/tools/dotc/typer/Inferencing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Contexts._, Types._, Flags._, Symbols._
import ProtoTypes._
import NameKinds.{AvoidNameKind, UniqueName}
import util.Spans._
import util.{Stats, SimpleIdentityMap}
import util.{Stats, SimpleIdentityMap, SrcPos}
import Decorators._
import config.Printers.{gadts, typr}
import annotation.tailrec
Expand All @@ -28,11 +28,11 @@ object Inferencing {
* but only if the overall result of `isFullyDefined` is `true`.
* Variables that are successfully minimized do not count as uninstantiated.
*/
def isFullyDefined(tp: Type, force: ForceDegree.Value)(using Context): Boolean = {
def isFullyDefined(tp: Type, force: ForceDegree.Value, handleOverflow: Boolean = false)(using Context): Boolean = {
val nestedCtx = ctx.fresh.setNewTyperState()
val result =
try new IsFullyDefinedAccumulator(force)(using nestedCtx).process(tp)
catch case ex: StackOverflowError =>
catch case ex: RecursionOverflow if handleOverflow =>
false // can happen for programs with illegal recusions, e.g. neg/recursive-lower-constraint.scala
if (result) nestedCtx.typerState.commit()
result
Expand All @@ -49,9 +49,13 @@ object Inferencing {
/** The fully defined type, where all type variables are forced.
* Throws an error if type contains wildcards.
*/
def fullyDefinedType(tp: Type, what: String, span: Span)(using Context): Type =
if (isFullyDefined(tp, ForceDegree.all)) tp
else throw new Error(i"internal error: type of $what $tp is not fully defined, pos = $span") // !!! DEBUG
def fullyDefinedType(tp: Type, what: String, pos: SrcPos)(using Context): Type =
try
if isFullyDefined(tp, ForceDegree.all) then tp
else throw new Error(i"internal error: type of $what $tp is not fully defined, pos = $pos")
catch case ex: RecursionOverflow =>
report.error(ex, pos)
UnspecifiedErrorType

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

private var toMaximize: List[TypeVar] = Nil

def apply(x: Boolean, tp: Type): Boolean = tp.dealias match {
case _: WildcardType | _: ProtoType =>
false
case tvar: TypeVar if !tvar.isInstantiated =>
force.appliesTo(tvar)
&& ctx.typerState.constraint.contains(tvar)
&& {
val direction = instDirection(tvar.origin)
if minimizeSelected then
if direction <= 0 && tvar.hasLowerBound then
def apply(x: Boolean, tp: Type): Boolean =
try tp.dealias match
case _: WildcardType | _: ProtoType =>
false
case tvar: TypeVar if !tvar.isInstantiated =>
force.appliesTo(tvar)
&& ctx.typerState.constraint.contains(tvar)
&& {
val direction = instDirection(tvar.origin)
if minimizeSelected then
if direction <= 0 && tvar.hasLowerBound then
instantiate(tvar, fromBelow = true)
else if direction >= 0 && tvar.hasUpperBound then
instantiate(tvar, fromBelow = false)
// else hold off instantiating unbounded unconstrained variable
else if direction != 0 then
instantiate(tvar, fromBelow = direction < 0)
else if variance >= 0 && (force.ifBottom == IfBottom.ok || tvar.hasLowerBound) then
instantiate(tvar, fromBelow = true)
else if direction >= 0 && tvar.hasUpperBound then
instantiate(tvar, fromBelow = false)
// else hold off instantiating unbounded unconstrained variable
else if direction != 0 then
instantiate(tvar, fromBelow = direction < 0)
else if variance >= 0 && (force.ifBottom == IfBottom.ok || tvar.hasLowerBound) then
instantiate(tvar, fromBelow = true)
else if variance >= 0 && force.ifBottom == IfBottom.fail then
return false
else
toMaximize = tvar :: toMaximize
foldOver(x, tvar)
}
case tp =>
foldOver(x, tp)
}
else if variance >= 0 && force.ifBottom == IfBottom.fail then
return false
else
toMaximize = tvar :: toMaximize
foldOver(x, tvar)
}
case tp =>
reporting.trace(s"IFT $tp") {
foldOver(x, tp)
}
catch case ex: Throwable =>
handleRecursive("check fully defined", tp.show, ex)

def process(tp: Type): Boolean =
// Maximize type vars in the order they were visited before */
Expand Down Expand Up @@ -313,7 +321,7 @@ object Inferencing {
val (tl1, tvars) = constrained(tl, tree)
var tree1 = AppliedTypeTree(tree.withType(tl1), tvars)
tree1.tpe <:< pt
fullyDefinedType(tree1.tpe, "template parent", tree.span)
fullyDefinedType(tree1.tpe, "template parent", tree.srcPos)
tree1
case _ =>
tree
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1467,7 +1467,7 @@ class Namer { typer: Typer =>
else {
if (denot.is(ModuleClass) && denot.sourceModule.isOneOf(GivenOrImplicit))
missingType(denot.symbol, "parent ")(using creationContext)
fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.span)
fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.srcPos)
}
case _ =>
UnspecifiedErrorType.assertingErrorsReported
Expand Down Expand Up @@ -1890,7 +1890,7 @@ class Namer { typer: Typer =>
def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp

def cookedRhsType = dealiasIfUnit(rhsType).deskolemized
def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.span)
def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.srcPos)
//if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType")
if (inherited.exists)
if sym.isInlineVal then lhsType else inherited
Expand Down
11 changes: 6 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
val synthesizedTypeTest: SpecialHandler =
(formal, span) => formal.argInfos match {
case arg1 :: arg2 :: Nil if !defn.isBottomClass(arg2.typeSymbol) =>
val tp1 = fullyDefinedType(arg1, "TypeTest argument", span)
val tp2 = fullyDefinedType(arg2, "TypeTest argument", span).normalized
val srcPos = ctx.source.atSpan(span)
val tp1 = fullyDefinedType(arg1, "TypeTest argument", srcPos)
val tp2 = fullyDefinedType(arg2, "TypeTest argument", srcPos).normalized
val sym2 = tp2.typeSymbol
if tp1 <:< tp2 then
// optimization when we know the typetest will always succeed
Expand Down Expand Up @@ -192,7 +193,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):

formal.argTypes match
case args @ (arg1 :: arg2 :: Nil) =>
List(arg1, arg2).foreach(fullyDefinedType(_, "eq argument", span))
List(arg1, arg2).foreach(fullyDefinedType(_, "eq argument", ctx.source.atSpan(span)))
if canComparePredefined(arg1, arg2)
|| !Implicits.strictEquality && explore(validEqAnyArgs(arg1, arg2))
then withNoErrors(ref(defn.CanEqual_canEqualAny).appliedToTypes(args).withSpan(span))
Expand All @@ -209,7 +210,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
New(defn.ValueOfClass.typeRef.appliedTo(t.tpe), t :: Nil).withSpan(span)
formal.argInfos match
case arg :: Nil =>
fullyDefinedType(arg, "ValueOf argument", span).normalized.dealias match
fullyDefinedType(arg, "ValueOf argument", ctx.source.atSpan(span)).normalized.dealias match
case ConstantType(c: Constant) =>
withNoErrors(success(Literal(c)))
case tp: TypeRef if tp.isRef(defn.UnitClass) =>
Expand Down Expand Up @@ -649,7 +650,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):

formal.argInfos match
case arg :: Nil =>
val manifest = synthesize(fullyDefinedType(arg, "Manifest argument", span), kind, topLevel = true)
val manifest = synthesize(fullyDefinedType(arg, "Manifest argument", ctx.source.atSpan(span)), kind, topLevel = true)
if manifest != EmptyTree then
report.deprecationWarning(
i"""Compiler synthesis of Manifest and OptManifest is deprecated, instead
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1101,7 +1101,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
def noLeaks(t: Tree): Boolean = escapingRefs(t, localSyms).isEmpty
if (noLeaks(tree)) tree
else {
fullyDefinedType(tree.tpe, "block", tree.span)
fullyDefinedType(tree.tpe, "block", tree.srcPos)
var avoidingType = TypeOps.avoid(tree.tpe, localSyms)
val ptDefined = isFullyDefined(pt, ForceDegree.none)
if (ptDefined && !(avoidingType.widenExpr <:< pt)) avoidingType = pt
Expand Down Expand Up @@ -1534,7 +1534,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case _ =>
if tree.isInline then checkInInlineContext("inline match", tree.srcPos)
val sel1 = typedExpr(tree.selector)
val rawSelectorTpe = fullyDefinedType(sel1.tpe, "pattern selector", tree.span)
val rawSelectorTpe = fullyDefinedType(sel1.tpe, "pattern selector", tree.srcPos)
val selType = rawSelectorTpe match
case c: ConstantType if tree.isInline => c
case otherTpe => otherTpe.widen
Expand Down
32 changes: 32 additions & 0 deletions tests/neg/i15311.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-- Error: tests/neg/i15311.scala:16:4 ----------------------------------------------------------------------------------
16 |def test = // error
|^
|Recursion limit exceeded.
|Maybe there is an illegal cyclic reference?
|If that's not the case, you could also try to increase the stacksize using the -Xss JVM option.
|A recurring operation is (inner to outer):
|
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| ...
|
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined food.T
| check fully defined Template[food.T]
17 | eat(ham)
18 | eat(food.self)
18 changes: 18 additions & 0 deletions tests/neg/i15311.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
trait Template[+T <: Template[T]]:
type Clone <: T { type Clone = Template.this.Clone }
val self :Clone

type Food = Template[_]

class Ham extends Template[Ham]:
type Clone = Ham
val self = this

def eat[F <: Template[F]](food :F) :F = food.self.self

val ham = new Ham
val food :Food = ham

def test = // error
eat(ham)
eat(food.self)

0 comments on commit 004a2fd

Please sign in to comment.