Skip to content
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

Handle recursions in isFullyDefined #15443

Merged
merged 1 commit into from
Jun 15, 2022
Merged
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
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 @@ -1333,7 +1333,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 @@ -1564,7 +1564,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 = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just re-read the diff and the "how I fixed it" comment (because I'm looking at handleRecursive and trace) and shouldn't the default here be true and fullDefinedType pass true so that it can do it's special handling? My assumption is that we would want all other calls of isFullyDefined to continue to return false instead of throwing.

Or are do we want to throw because isFullyDefined is call transitively from IsFullyDefinedAccumulator and we want those handled by the handleRecursive you added? My point is handleOverflow seems to never be non-default, was that for future possible uses?

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)