Skip to content

Commit

Permalink
Add GADT approximation in adaptToSubType
Browse files Browse the repository at this point in the history
  • Loading branch information
abgruszecki authored and radeusgd committed Apr 30, 2020
1 parent 6f51dbb commit 89a2104
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 8 deletions.
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/GadtConstraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ sealed abstract class GadtConstraint extends Showable {
def contains(sym: Symbol)(implicit ctx: Context): Boolean

def isEmpty: Boolean
final def nonEmpty: Boolean = !isEmpty

/** See [[ConstraintHandling.approximation]] */
def approximation(sym: Symbol, fromBelow: Boolean)(implicit ctx: Context): Type
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,7 @@ object Erasure {
(stats2.filter(!_.isEmpty), finalCtx)
}

override def adapt(tree: Tree, pt: Type, locked: TypeVars)(using Context): Tree =
override def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean)(using Context): Tree =
trace(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) {
if ctx.phase != ctx.erasurePhase && ctx.phase != ctx.erasurePhase.next then
// this can happen when reading annotations loaded during erasure,
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ class TreeChecker extends Phase with SymTransformer {
override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(using Context): Tree =
tree

override def adapt(tree: Tree, pt: Type, locked: TypeVars)(using Context): Tree = {
override def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean)(using Context): Tree = {
def isPrimaryConstructorReturn =
ctx.owner.isPrimaryConstructor && pt.isRef(ctx.owner.owner) && tree.tpe.isRef(defn.UnitClass)
def infoStr(tp: Type) = tp match {
Expand Down
69 changes: 69 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Inferencing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import collection.mutable
import scala.annotation.internal.sharable
import scala.annotation.threadUnsafe

import config.Printers.debug

object Inferencing {

import tpd._
Expand Down Expand Up @@ -162,6 +164,73 @@ object Inferencing {
)
}

def approximateGADT(tp: Type)(implicit ctx: Context): Type = {
val map = new IsFullyDefinedAccumulator2
val res = map(tp)
assert(!map.failed)
debug.println(i"approximateGADT( $tp ) = $res // {${tp.toString}}")
res
}

private class IsFullyDefinedAccumulator2(implicit ctx: Context) extends TypeMap {

var failed = false

private def instantiate(tvar: TypeVar, fromBelow: Boolean): Type = {
val inst = tvar.instantiate(fromBelow)
typr.println(i"forced instantiation of ${tvar.origin} = $inst")
inst
}

private def instDirection2(sym: Symbol)(implicit ctx: Context): Int = {
val constrained = ctx.gadt.fullBounds(sym)
val original = sym.info.bounds
val cmp = ctx.typeComparer
val approxBelow =
if (!cmp.isSubTypeWhenFrozen(constrained.lo, original.lo)) 1 else 0
val approxAbove =
if (!cmp.isSubTypeWhenFrozen(original.hi, constrained.hi)) 1 else 0
approxAbove - approxBelow
}

private[this] var toMaximize: Boolean = false

def apply(tp: Type): Type = tp.dealias match {
case tp @ TypeRef(qual, nme) if (qual eq NoPrefix) && ctx.gadt.contains(tp.symbol) =>
val sym = tp.symbol
val res =
ctx.gadt.approximation(sym, fromBelow = variance < 0)

debug.println(i"approximated $tp ~~ $res")

res

case _: WildcardType | _: ProtoType =>
failed = true
NoType

case tp =>
mapOver(tp)
}

// private class UpperInstantiator(implicit ctx: Context) extends TypeAccumulator[Unit] {
// def apply(x: Unit, tp: Type): Unit = {
// tp match {
// case tvar: TypeVar if !tvar.isInstantiated =>
// instantiate(tvar, fromBelow = false)
// case _ =>
// }
// foldOver(x, tp)
// }
// }

def process(tp: Type): Type = {
val res = apply(tp)
// if (res && toMaximize) new UpperInstantiator().apply((), tp)
res
}
}

/** For all type parameters occurring in `tp`:
* If the bounds of `tp` in the current constraint are equal wrt =:=,
* instantiate the type parameter to the lower bound's approximation
Expand Down
58 changes: 52 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import transform.TypeUtils._
import reporting.trace
import Nullables.{NotNullInfo, given _}
import NullOpsDecorator._
import config.Printers.debug

object Typer {

Expand Down Expand Up @@ -2771,20 +2772,23 @@ class Typer extends Namer
* If all this fails, error
* Parameters as for `typedUnadapted`.
*/
def adapt(tree: Tree, pt: Type, locked: TypeVars)(using Context): Tree =
trace(i"adapting $tree to $pt", typr, show = true) {
def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean = true)(using Context): Tree = {
val last = Thread.currentThread.getStackTrace()(2).toString;
trace/*.force*/(i"adapting (tryGadtHealing=$tryGadtHealing) $tree to $pt\n{callsite: $last}", typr, show = true) {
record("adapt")
adapt1(tree, pt, locked)
adapt1(tree, pt, locked, tryGadtHealing)
}
}

final def adapt(tree: Tree, pt: Type)(using Context): Tree =
adapt(tree, pt, ctx.typerState.ownedVars)

private def adapt1(tree: Tree, pt: Type, locked: TypeVars)(using Context): Tree = {
private def adapt1(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean)(using Context): Tree = {
// assert(pt.exists && !pt.isInstanceOf[ExprType])
assert(pt.exists && !pt.isInstanceOf[ExprType] || ctx.reporter.errorsReported)
def methodStr = err.refStr(methPart(tree).tpe)

def readapt(tree: Tree)(using Context) = adapt(tree, pt, locked)
def readapt(tree: Tree, shouldTryGadtHealing: Boolean = tryGadtHealing)(using Context) = adapt(tree, pt, locked, shouldTryGadtHealing)
def readaptSimplified(tree: Tree)(using Context) = readapt(simplify(tree, pt, locked))

def missingArgs(mt: MethodType) = {
Expand Down Expand Up @@ -3239,16 +3243,19 @@ class Typer extends Namer
}

def adaptToSubType(wtp: Type): Tree = {
debug.println("adaptToSubType")
debug.println("// try converting a constant to the target type")
// try converting a constant to the target type
val folded = ConstFold(tree, pt)
if (folded ne tree)
return adaptConstant(folded, folded.tpe.asInstanceOf[ConstantType])

// Try to capture wildcards in type
debug.println("// Try to capture wildcards in type")
val captured = captureWildcards(wtp)
if (captured `ne` wtp)
return readapt(tree.cast(captured))

debug.println("// drop type if prototype is Unit")
// drop type if prototype is Unit
if (pt isRef defn.UnitClass) {
// local adaptation makes sure every adapted tree conforms to its pt
Expand All @@ -3258,6 +3265,7 @@ class Typer extends Namer
return tpd.Block(tree1 :: Nil, Literal(Constant(())))
}

debug.println("// convert function literal to SAM closure")
// convert function literal to SAM closure
tree match {
case closure(Nil, id @ Ident(nme.ANON_FUN), _)
Expand All @@ -3275,6 +3283,28 @@ class Typer extends Namer
case _ =>
}

debug.println("// try GADT approximation")
val foo = Inferencing.approximateGADT(wtp)
debug.println(
i"""
foo = $foo
pt.isInstanceOf[SelectionProto] = ${pt.isInstanceOf[SelectionProto]}
ctx.gadt.nonEmpty = ${ctx.gadt.nonEmpty}
pt.isMatchedBy = ${
if (pt.isInstanceOf[SelectionProto])
pt.asInstanceOf[SelectionProto].isMatchedBy(foo).toString
else
"<not a SelectionProto>"
}
"""
)
pt match {
case pt: SelectionProto if ctx.gadt.nonEmpty && pt.isMatchedBy(foo) =>
return tpd.Typed(tree, TypeTree(foo))
case _ => ;
}

debug.println("// try an extension method in scope")
// try an extension method in scope
pt match {
case SelectionProto(name, mbrType, _, _) =>
Expand All @@ -3292,17 +3322,33 @@ class Typer extends Namer
val app = tryExtension(using nestedCtx)
if (!app.isEmpty && !nestedCtx.reporter.hasErrors) {
nestedCtx.typerState.commit()
debug.println("returning ext meth in scope")
return ExtMethodApply(app)
}
case _ =>
}

debug.println("// try an implicit conversion")
// try an implicit conversion
val prevConstraint = ctx.typerState.constraint
def recover(failure: SearchFailureType) =
{
debug.println("recover")
if (isFullyDefined(wtp, force = ForceDegree.all) &&
ctx.typerState.constraint.ne(prevConstraint)) readapt(tree)
// else if ({
// debug.println(i"tryGadtHealing=$tryGadtHealing && \n\tctx.gadt.nonEmpty=${ctx.gadt.nonEmpty}")
// tryGadtHealing && ctx.gadt.nonEmpty
// })
// {
// debug.println("here")
// readapt(
// tree = tpd.Typed(tree, TypeTree(Inferencing.approximateGADT(wtp))),
// shouldTryGadtHealing = false,
// )
// }
else err.typeMismatch(tree, pt, failure)
}
if ctx.mode.is(Mode.ImplicitsEnabled) && tree.typeOpt.isValueType then
if pt.isRef(defn.AnyValClass) || pt.isRef(defn.ObjectClass) then
ctx.error(em"the result of an implicit conversion must be more specific than $pt", tree.sourcePos)
Expand Down

0 comments on commit 89a2104

Please sign in to comment.