Skip to content

Implement creator applications #6084

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 17 commits into from
Mar 28, 2019
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
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
case _ => Nil
}

/** Is tree a path? */
def isPath(tree: Tree): Boolean = unsplice(tree) match {
case Ident(_) | This(_) | Super(_, _) => true
case Select(qual, _) => isPath(qual)
case _ => false
}

/** Is tree a self constructor call this(...)? I.e. a call to a constructor of the
* same object?
*/
Expand Down
20 changes: 14 additions & 6 deletions compiler/src/dotty/tools/dotc/core/Constraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ abstract class Constraint extends Showable {

type This <: Constraint

/** Does the constraint's domain contain the type parameters of `pt`? */
def contains(pt: TypeLambda): Boolean
/** Does the constraint's domain contain the type parameters of `tl`? */
def contains(tl: TypeLambda): Boolean

/** Does the constraint's domain contain the type parameter `param`? */
def contains(param: TypeParamRef): Boolean
Expand Down Expand Up @@ -106,14 +106,22 @@ abstract class Constraint extends Showable {
*/
def replace(param: TypeParamRef, tp: Type)(implicit ctx: Context): This

/** Is entry associated with `pt` removable? This is the case if
/** Is entry associated with `tl` removable? This is the case if
* all type parameters of the entry are associated with type variables
* which have their `inst` fields set.
*/
def isRemovable(pt: TypeLambda): Boolean
def isRemovable(tl: TypeLambda): Boolean

/** A new constraint with all entries coming from `pt` removed. */
def remove(pt: TypeLambda)(implicit ctx: Context): This
/** A new constraint with all entries coming from `tl` removed. */
def remove(tl: TypeLambda)(implicit ctx: Context): This

/** A new constraint with entry `tl` renamed to a fresh type lambda */
def rename(tl: TypeLambda)(implicit ctx: Context): This

/** The given `tl` in case it is not contained in this constraint,
* a fresh copy of `tl` otherwise.
*/
def ensureFresh(tl: TypeLambda)(implicit ctx: Context): TypeLambda

/** The type lambdas constrained by this constraint */
def domainLambdas: List[TypeLambda]
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ object NameOps {
def isSetterName: Boolean = name endsWith str.SETTER_SUFFIX
def isScala2LocalSuffix: Boolean = testSimple(_.endsWith(" "))
def isSelectorName: Boolean = testSimple(n => n.startsWith("_") && n.drop(1).forall(_.isDigit))
def isAnonymousClassName: Boolean = name.startsWith(str.ANON_CLASS)
def isAnonymousFunctionName: Boolean = name.startsWith(str.ANON_FUN)

/** Is name a variable name? */
def isVariableName: Boolean = testSimple { n =>
Expand Down
56 changes: 54 additions & 2 deletions compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import collection.mutable
import printing.Printer
import printing.Texts._
import config.Config
import config.Printers.constr
import reflect.ClassTag
import annotation.tailrec
import annotation.internal.sharable
Expand Down Expand Up @@ -503,6 +504,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
}

def & (other: Constraint, otherHasErrors: Boolean)(implicit ctx: Context): OrderingConstraint = {

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]) = {
Expand All @@ -527,21 +529,71 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
case (e1: TypeBounds, e2: TypeBounds) => e1 & e2
case (e1: TypeBounds, _) if e1 contains e2 => e2
case (_, e2: TypeBounds) if e2 contains e1 => e1
case (tv1: TypeVar, tv2: TypeVar) if tv1.instanceOpt eq tv2.instanceOpt => e1
case (tv1: TypeVar, tv2: TypeVar) if tv1 eq tv2 => e1
case _ =>
if (otherHasErrors)
e1
else
throw new AssertionError(i"cannot merge $this with $other, mergeEntries($e1, $e2) failed")
}

val that = other.asInstanceOf[OrderingConstraint]
/** Ensure that constraint `c` does not associate different TypeVars for the
* same type lambda than this constraint. Do this by renaming type lambdas
* in `c` where necessary.
*/
def ensureNotConflicting(c: OrderingConstraint): OrderingConstraint = {
def hasConflictingTypeVarsFor(tl: TypeLambda) =
this.typeVarOfParam(tl.paramRefs(0)) ne c.typeVarOfParam(tl.paramRefs(0))
// Note: Since TypeVars are allocated in bulk for each type lambda, we only
// have to check the first one to find out if some of them are different.
val conflicting = c.domainLambdas.find(tl =>
this.contains(tl) && hasConflictingTypeVarsFor(tl))
conflicting match {
case Some(tl) => ensureNotConflicting(c.rename(tl))
Copy link
Member

Choose a reason for hiding this comment

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

I'm not completely sold on after-the-fact renaming being the best approach. What if instead TypeLambda had a var inUse: Boolean set to true when added to a constraint and set back to false when it's removed from the constraint ? Then we can make sure to use different TypeLambdas in different constraints live at the same time, it's a slight memory increase but should be much less expensive than doing deep replacements when merging constraints.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, possibly. I need to think this over.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, in fact that would not work. The problem is that we create constraints and then forget about them. So a lambda might never be removed from a constraint.

case None => c
}
}

val that = ensureNotConflicting(other.asInstanceOf[OrderingConstraint])

new OrderingConstraint(
merge(this.boundsMap, that.boundsMap, mergeEntries),
merge(this.lowerMap, that.lowerMap, mergeParams),
merge(this.upperMap, that.upperMap, mergeParams))
}.reporting(res => i"constraint merge $this with $other = $res", constr)

def rename(tl: TypeLambda)(implicit ctx: Context): OrderingConstraint = {
assert(contains(tl))
val tl1 = ensureFresh(tl)
def swapKey[T](m: ArrayValuedMap[T]) = m.remove(tl).updated(tl1, m(tl))
var current = newConstraint(swapKey(boundsMap), swapKey(lowerMap), swapKey(upperMap))
def subst[T <: Type](x: T): T = x.subst(tl, tl1).asInstanceOf[T]
current.foreachParam {(p, i) =>
current = boundsLens.map(this, current, p, i, subst)
current = lowerLens.map(this, current, p, i, _.map(subst))
current = upperLens.map(this, current, p, i, _.map(subst))
}
current.foreachTypeVar { tvar =>
val TypeParamRef(binder, n) = tvar.origin
if (binder eq tl) tvar.setOrigin(tl1.paramRefs(n))
}
constr.println(i"renamd $this to $current")
current
}

def ensureFresh(tl: TypeLambda)(implicit ctx: Context): TypeLambda =
if (contains(tl)) {
var paramInfos = tl.paramInfos
if (tl.isInstanceOf[HKLambda]) {
// HKLambdas are hash-consed, need to create an artificial difference by adding
// a LazyRef to a bound.
val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos
paramInfos = TypeBounds(lo, LazyRef(_ => hi)) :: pinfos1
}
ensureFresh(tl.newLikeThis(tl.paramNames, paramInfos, tl.resultType))
}
else tl

override def checkClosed()(implicit ctx: Context): Unit = {
def isFreeTypeParamRef(tp: Type) = tp match {
case TypeParamRef(binder: TypeLambda, _) => !contains(binder)
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -484,13 +484,13 @@ object SymDenotations {

/** Is this symbol an anonymous class? */
final def isAnonymousClass(implicit ctx: Context): Boolean =
isClass && (initial.name startsWith str.ANON_CLASS)
isClass && initial.name.isAnonymousClassName

final def isAnonymousFunction(implicit ctx: Context): Boolean =
this.symbol.is(Method) && (initial.name startsWith str.ANON_FUN)
this.symbol.is(Method) && initial.name.isAnonymousFunctionName

final def isAnonymousModuleVal(implicit ctx: Context): Boolean =
this.symbol.is(ModuleVal) && (initial.name startsWith str.ANON_CLASS)
this.symbol.is(ModuleVal) && initial.name.isAnonymousClassName

/** Is this a synthetic method that represents conversions between representations of a value class
* These methods are generated in ExtensionMethods
Expand Down
21 changes: 16 additions & 5 deletions compiler/src/dotty/tools/dotc/core/TyperState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ import Contexts._
import util.{SimpleIdentityMap, SimpleIdentitySet}
import reporting._
import config.Config
import config.Printers.constr
import collection.mutable
import java.lang.ref.WeakReference
import util.Stats
import Decorators._

import scala.annotation.internal.sharable

object TyperState {
@sharable private var nextId: Int = 0
}

class TyperState(previous: TyperState /* | Null */) {
class TyperState(private val previous: TyperState /* | Null */) {

Stats.record("typerState")

Expand Down Expand Up @@ -143,10 +145,11 @@ class TyperState(previous: TyperState /* | Null */) {
def commit()(implicit ctx: Context): Unit = {
Stats.record("typerState.commit")
val targetState = ctx.typerState
if (constraint ne targetState.constraint)
constr.println(i"committing $this to $targetState, fromConstr = $constraint, toConstr = ${targetState.constraint}")
assert(isCommittable)
targetState.constraint =
if (targetState.constraint eq previousConstraint) constraint
else targetState.constraint & (constraint, otherHasErrors = reporter.errorsReported)
if (targetState.constraint eq previousConstraint) targetState.constraint = constraint
else targetState.mergeConstraintWith(this)
constraint foreachTypeVar { tvar =>
if (tvar.owningState.get eq this) tvar.owningState = new WeakReference(targetState)
}
Expand All @@ -156,6 +159,9 @@ class TyperState(previous: TyperState /* | Null */) {
isCommitted = true
}

def mergeConstraintWith(that: TyperState)(implicit ctx: Context): Unit =
constraint = constraint & (that.constraint, otherHasErrors = that.reporter.errorsReported)

/** Make type variable instances permanent by assigning to `inst` field if
* type variable instantiation cannot be retracted anymore. Then, remove
* no-longer needed constraint entries.
Expand All @@ -176,7 +182,12 @@ class TyperState(previous: TyperState /* | Null */) {
constraint = constraint.remove(poly)
}

override def toString: String = s"TS[$id]"
override def toString: String = {
def ids(state: TyperState): List[String] =
s"${state.id}${if (state.isCommittable) "" else "X"}" ::
(if (state.previous == null) Nil else ids(state.previous))
s"TS[${ids(this).mkString(", ")}]"
}

def stateChainStr: String = s"$this${if (previous == null) "" else previous.stateChainStr}"
}
Expand Down
9 changes: 8 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3667,7 +3667,14 @@ object Types {
* `owningTree` and `owner` are used to determine whether a type-variable can be instantiated
* at some given point. See `Inferencing#interpolateUndetVars`.
*/
final class TypeVar(val origin: TypeParamRef, creatorState: TyperState) extends CachedProxyType with ValueType {
final class TypeVar(private var _origin: TypeParamRef, creatorState: TyperState) extends CachedProxyType with ValueType {

def origin: TypeParamRef = _origin

/** Set origin to new parameter. Called if we merge two conflicting constraints.
* See OrderingConstraint#merge, OrderingConstraint#rename
*/
def setOrigin(p: TypeParamRef) = _origin = p

/** The permanent instance type of the variable, or NoType is none is given yet */
private[this] var myInst: Type = NoType
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Denotations._
import SymDenotations._
import StdNames.{nme, tpnme}
import ast.{Trees, untpd}
import typer.{Implicits, Namer}
import typer.{Implicits, Namer, Applications}
import typer.ProtoTypes._
import Trees._
import TypeApplications._
Expand Down Expand Up @@ -578,6 +578,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
else keywordStr("'{") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
case Splice(tree) =>
keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
case tree: Applications.IntegratedTypeArgs =>
toText(tree.app) ~ Str("(with integrated type args)").provided(ctx.settings.YprintDebug.value)
case Thicket(trees) =>
"Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}"
case _ =>
Expand Down
18 changes: 11 additions & 7 deletions compiler/src/dotty/tools/dotc/reporting/Reporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,19 @@ trait Reporting { this: Context =>
def strictWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = {
val fullPos = addInlineds(pos)
if (this.settings.strict.value) error(msg, fullPos)
else reportWarning(new ExtendMessage(() => msg)(_ + "\n(This would be an error under strict mode)").warning(fullPos))
else reportWarning(
new ExtendMessage(() => msg)(_ + "\n(This would be an error under strict mode)")
.warning(fullPos))
}

def error(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = {
reporter.report(new Error(msg, addInlineds(pos)))
}

def errorOrMigrationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = {
def error(msg: => Message, pos: SourcePosition = NoSourcePosition, sticky: Boolean = false): Unit = {
val fullPos = addInlineds(pos)
if (ctx.scala2Mode) migrationWarning(msg, fullPos) else error(msg, fullPos)
reporter.report(if (sticky) new StickyError(msg, fullPos) else new Error(msg, fullPos))
}

def errorOrMigrationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit =
if (ctx.scala2Mode) migrationWarning(msg, pos) else error(msg, pos)

def restrictionError(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit =
reporter.report {
new ExtendMessage(() => msg)(m => s"Implementation restriction: $m").error(addInlineds(pos))
Expand Down Expand Up @@ -203,6 +204,9 @@ abstract class Reporter extends interfaces.ReporterResult {
/** All errors reported by this reporter (ignoring outer reporters) */
def allErrors: List[Error] = errors

/** Were sticky errors reported? Overridden in StoreReporter. */
def hasStickyErrors: Boolean = false

/** Have errors been reported by this reporter, or in the
* case where this is a StoreReporter, by an outer reporter?
*/
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class StoreReporter(outer: Reporter) extends Reporter {
override def hasUnreportedErrors: Boolean =
outer != null && infos != null && infos.exists(_.isInstanceOf[Error])

override def hasStickyErrors: Boolean =
infos != null && infos.exists(_.isInstanceOf[StickyError])

override def removeBufferedMessages(implicit ctx: Context): List[MessageContainer] =
if (infos != null) try infos.toList finally infos = null
else Nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ object messages {
pos: SourcePosition
) extends MessageContainer(msgFn, pos, ERROR)

/** A sticky error is an error that should not be hidden by backtracking and
* trying some alternative path. Typcially, errors issued after catching
* a TypeError exception are sticky.
*/
class StickyError(
msgFn: => Message,
pos: SourcePosition
) extends Error(msgFn, pos)

class Warning(
msgFn: => Message,
pos: SourcePosition
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder
case ex: TypeError =>
// See neg/i1750a for an example where a cyclic error can arise.
// The root cause in this example is an illegal "override" of an inner trait
ctx.error(ex.toMessage, csym.sourcePos)
ctx.error(ex.toMessage, csym.sourcePos, sticky = true)
defn.ObjectType :: Nil
}
if (ValueClasses.isDerivedValueClass(csym)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ abstract class MacroTransform extends Phase {
}
catch {
case ex: TypeError =>
ctx.error(ex.toMessage, tree.sourcePos)
ctx.error(ex.toMessage, tree.sourcePos, sticky = true)
tree
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/MegaPhase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase {
}
catch {
case ex: TypeError =>
ctx.error(ex.toMessage, tree.sourcePos)
ctx.error(ex.toMessage, tree.sourcePos, sticky = true)
tree
}
if (tree.isInstanceOf[NameTree]) goNamed(tree, start) else goUnnamed(tree, start)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ object OverridingPairs {
case ex: TypeError =>
// See neg/i1750a for an example where a cyclic error can arise.
// The root cause in this example is an illegal "override" of an inner trait
ctx.error(ex.toMessage, base.sourcePos)
ctx.error(ex.toMessage, base.sourcePos, sticky = true)
}
} else {
curEntry = curEntry.prev
Expand Down
Loading