Skip to content

More tweaks to type inference #1482

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

Merged
merged 4 commits into from
Sep 4, 2016
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
3 changes: 3 additions & 0 deletions src/dotty/tools/dotc/core/Constraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ abstract class Constraint extends Showable {
/** The uninstantiated typevars of this constraint */
def uninstVars: collection.Seq[TypeVar]

/** The weakest constraint that subsumes both this constraint and `other` */
def & (other: Constraint)(implicit ctx: Context): Constraint

/** Check that no constrained parameter contains itself as a bound */
def checkNonCyclic()(implicit ctx: Context): Unit

Expand Down
44 changes: 42 additions & 2 deletions src/dotty/tools/dotc/core/OrderingConstraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import annotation.tailrec

object OrderingConstraint {

type ArrayValuedMap[T] = SimpleMap[GenericType, Array[T]]

/** The type of `OrderingConstraint#boundsMap` */
type ParamBounds = SimpleMap[GenericType, Array[Type]]
type ParamBounds = ArrayValuedMap[Type]

/** The type of `OrderingConstraint#lowerMap`, `OrderingConstraint#upperMap` */
type ParamOrdering = SimpleMap[GenericType, Array[List[PolyParam]]]
type ParamOrdering = ArrayValuedMap[List[PolyParam]]

/** A new constraint with given maps */
private def newConstraint(boundsMap: ParamBounds, lowerMap: ParamOrdering, upperMap: ParamOrdering)(implicit ctx: Context) : OrderingConstraint = {
Expand Down Expand Up @@ -495,6 +497,44 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
}
}

def & (other: Constraint)(implicit ctx: Context) = {
def merge[T](m1: ArrayValuedMap[T], m2: ArrayValuedMap[T], join: (T, T) => T): ArrayValuedMap[T] = {
var merged = m1
def mergeArrays(xs1: Array[T], xs2: Array[T]) = {
val xs = xs1.clone
for (i <- xs.indices) xs(i) = join(xs1(i), xs2(i))
xs
}
m2.foreachBinding { (poly, xs2) =>
merged = merged.updated(poly,
if (m1.contains(poly)) mergeArrays(m1(poly), xs2) else xs2)
}
merged
}

def mergeParams(ps1: List[PolyParam], ps2: List[PolyParam]) =
(ps1 /: ps2)((ps1, p2) => if (ps1.contains(p2)) ps1 else p2 :: ps1)

def mergeEntries(e1: Type, e2: Type): Type = e1 match {
case e1: TypeBounds =>
e2 match {
case e2: TypeBounds => e1 & e2
case _ if e1 contains e2 => e2
case _ => mergeError
}
case _ if e1 eq e2 => e1
case _ => mergeError
}

def mergeError = throw new AssertionError(i"cannot merge $this with $other")

val that = other.asInstanceOf[OrderingConstraint]
new OrderingConstraint(
merge(this.boundsMap, that.boundsMap, mergeEntries),
merge(this.lowerMap, that.lowerMap, mergeParams),
merge(this.upperMap, that.upperMap, mergeParams))
}

override def checkClosed()(implicit ctx: Context): Unit = {
def isFreePolyParam(tp: Type) = tp match {
case PolyParam(binder: GenericType, _) => !contains(binder)
Expand Down
34 changes: 13 additions & 21 deletions src/dotty/tools/dotc/core/TyperState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,10 @@ class TyperState(r: Reporter) extends DotClass with Showable {
/** Commit state so that it gets propagated to enclosing context */
def commit()(implicit ctx: Context): Unit = unsupported("commit")

/** The typer state has already been committed */
def isCommitted: Boolean = false

/** Optionally, if this is a mutable typerstate, it's creator state */
def parent: Option[TyperState] = None

/** The closest ancestor of this typer state (including possibly this typer state itself)
* which is not yet committed, or which does not have a parent.
*/
def uncommittedAncestor: TyperState =
if (!isCommitted || !parent.isDefined) this
else parent.get.uncommittedAncestor
def uncommittedAncestor: TyperState = this

/** Make type variable instances permanent by assigning to `inst` field if
* type variable instantiation cannot be retracted anymore. Then, remove
Expand All @@ -96,7 +88,8 @@ extends TyperState(r) {

override def reporter = myReporter

private var myConstraint: Constraint = previous.constraint
private val previousConstraint = previous.constraint
private var myConstraint: Constraint = previousConstraint

override def constraint = myConstraint
override def constraint_=(c: Constraint)(implicit ctx: Context) = {
Expand All @@ -109,7 +102,6 @@ extends TyperState(r) {
override def ephemeral = myEphemeral
override def ephemeral_=(x: Boolean): Unit = { myEphemeral = x }


override def fresh(isCommittable: Boolean): TyperState =
new MutableTyperState(this, new StoreReporter(reporter), isCommittable)

Expand All @@ -120,6 +112,11 @@ extends TyperState(r) {
isCommittable &&
(!previous.isInstanceOf[MutableTyperState] || previous.isGlobalCommittable)

private var isCommitted = false

override def uncommittedAncestor: TyperState =
if (isCommitted) previous.uncommittedAncestor else this

/** Commit typer state so that its information is copied into current typer state
* In addition (1) the owning state of undetermined or temporarily instantiated
* type variables changes from this typer state to the current one. (2) Variables
Expand All @@ -128,25 +125,20 @@ extends TyperState(r) {
*/
override def commit()(implicit ctx: Context) = {
val targetState = ctx.typerState
assert(targetState eq previous)
assert(isCommittable)
targetState.constraint = constraint
targetState.constraint =
if (targetState.constraint eq previousConstraint) constraint
else targetState.constraint & constraint
Copy link
Member

@smarter smarter Aug 31, 2016

Choose a reason for hiding this comment

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

The commit message of 3664854 explains why merging is needed but this is tricky enough that it should also be explained in a comment here.

constraint foreachTypeVar { tvar =>
if (tvar.owningState eq this)
tvar.owningState = targetState
}
targetState.ephemeral = ephemeral
targetState.ephemeral |= ephemeral
targetState.gc()
reporter.flush()
myIsCommitted = true
isCommitted = true
}

private var myIsCommitted = false

override def isCommitted: Boolean = myIsCommitted

override def parent = Some(previous)

override def gc()(implicit ctx: Context): Unit = {
val toCollect = new mutable.ListBuffer[GenericType]
constraint foreachTypeVar { tvar =>
Expand Down
3 changes: 2 additions & 1 deletion src/dotty/tools/dotc/typer/Inferencing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ object Inferencing {
def apply(x: Boolean, tp: Type): Boolean = tp.dealias match {
case _: WildcardType | _: ProtoType =>
false
case tvar: TypeVar if !tvar.isInstantiated =>
case tvar: TypeVar
if !tvar.isInstantiated && ctx.typerState.constraint.contains(tvar) =>
force.appliesTo(tvar) && {
val direction = instDirection(tvar.origin)
if (direction != 0) {
Expand Down
6 changes: 5 additions & 1 deletion src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
}

def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(implicit ctx: Context): SeqLiteral = track("typedSeqLiteral") {
val proto1 = pt.elemType orElse WildcardType
val proto1 = pt.elemType match {
case NoType => WildcardType
case bounds: TypeBounds => WildcardType(bounds)
case elemtp => elemtp
}
val elems1 = tree.elems mapconserve (typed(_, proto1))
val proto2 = // the computed type of the `elemtpt` field
if (!tree.elemtpt.isEmpty) WildcardType
Expand Down
8 changes: 0 additions & 8 deletions tests/pending/pos/isApplicableSafe.scala

This file was deleted.

54 changes: 54 additions & 0 deletions tests/pos/isApplicableSafe.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import reflect.ClassTag

// The same problems arise in real arrays.
class A {

class Array[T]
object Array {
def apply[T: ClassTag](xs: T*): Array[T] = ???
def apply(x: Int, xs: Int*): Array[Int] = ???
}

// Any of Array[List[Symbol]], List[Array[Symbol]], or List[List[Symbol]] compile.
var xs: Array[Array[Symbol]] = _
var ys: Array[Map[Symbol, Set[Symbol]]] = _

//xs = Array(Array())
// gives:
//
// isApplicableSafe.scala:15: error: type mismatch:
// found : A.this.Array[Nothing]
// required: A.this.Array[Symbol]
// xs = Array(Array())
//
// Here's the sequence of events that leads to this problem:
//
// 1. the outer Array.apply is overloaded, so we need to typecheck the inner one
// without an expected prototype
Copy link
Member

Choose a reason for hiding this comment

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

What if instead of giving up when this fails, we tried to re-typecheck the inner call using every possible overload? Of course this would have to be done carefully to avoid ending up like the Swift typechecker which apparently gives up when trying to compile let a: Double = -(1 + 2) + -(3 + 4) + 5 because of overloading and constraint resolutions: https://www.cocoawithlove.com/blog/2016/07/12/type-checker-issues.html

On a more general note, it seems that there is a tension in Scala caused by overloading: on one hand it significantly reduces the power of type inference, on the other hand it makes some APIs much nicer to use. So either we figure out how to get overloading to play well with type inference, or we come up with a more principled alternative to overloading that helps us keep our APIs nice.

Copy link
Contributor Author

@odersky odersky Aug 27, 2016

Choose a reason for hiding this comment

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

Yeah, I am very nervous about trying all combinations. That typically leads to blowup in compile times. Then you either become too restrictive and can't even typecheck trivial expressions or your compile times go through the roof. In both cases there's little a programmer can do.

//
// 2. The inner Array.apply needs a ClassTag, so we need to instantiate
// its type variable, and the best instantiation is Nothing.
//
// To prevent this, we'd need to do several things:
//
// 1. Pass argument types lazily into the isApplicable call in resolveOverloaded,
// so that we can call constrainResult before any arguments are evaluated.
//
// 2. This is still not enough because the result type is initially an IgnoredProto.
// (because an implicit might have to be inserted around the call, so we cannot
// automatically assume that the call result is a subtype of the expected type).
// Hence, we need to somehow create a closure in constrainResult that does the
// comparison with the real expected result type "on demand".
//
// 3. When instantiating a type variable we need to categorize that some instantiations
// are suspicous (e.g. scalac avoids instantiating to Nothing). In these
// circumstances we should try to excute the delayed constrainResult closures
// in order to get a better instance type.
//
// Quite a lot of work. It's looking really complicated to fix this.


ys = Array(Map(), Map())

val zs = Array(Map())
}