Skip to content

Fix #4466: Update contexts of FunProto when something else is tried #4871

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 6 commits into from
Aug 7, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {

def applyOverloaded(receiver: Tree, method: TermName, args: List[Tree], targs: List[Type], expectedType: Type, isAnnotConstructor: Boolean = false)(implicit ctx: Context): Tree = {
val typer = ctx.typer
val proto = new FunProtoTyped(args, expectedType, typer)
val proto = new FunProtoTyped(args, expectedType)(typer)
val denot = receiver.tpe.member(method)
assert(denot.exists, i"no member $receiver . $method, members = ${receiver.tpe.decls}")
val selected =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
": " ~ toText(tp.memberProto) ~ " }"
case tp: ViewProto =>
return toText(tp.argType) ~ " ?=>? " ~ toText(tp.resultType)
case tp @ FunProto(args, resultType, _) =>
case tp @ FunProto(args, resultType) =>
val argsText = args match {
case dummyTreeOfType(tp) :: Nil if !(tp isRef defn.NullClass) => "null: " ~ toText(tp)
case _ => toTextGlobal(args, ", ")
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 @@ -500,7 +500,7 @@ object Erasure {
val Apply(fun, args) = tree
if (fun.symbol == defn.cbnArg)
typedUnadapted(args.head, pt)
else typedExpr(fun, FunProto(args, pt, this)) match {
else typedExpr(fun, FunProto(args, pt)(this)) match {
case fun1: Apply => // arguments passed in prototype were already passed
fun1
case fun1 =>
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = {

def realApply(implicit ctx: Context): Tree = track("realApply") {
val originalProto = new FunProto(tree.args, IgnoredProto(pt), this)(argCtx(tree))
val originalProto = new FunProto(tree.args, IgnoredProto(pt))(this)(argCtx(tree))
val fun1 = typedExpr(tree.fun, originalProto)

// Warning: The following lines are dirty and fragile. We record that auto-tupling was demanded as
Expand Down Expand Up @@ -1365,7 +1365,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
alts filter (isApplicable(_, argTypes, resultType))

val candidates = pt match {
case pt @ FunProto(args, resultType, _) =>
case pt @ FunProto(args, resultType) =>
val numArgs = args.length
val normArgs = args.mapConserve {
case Block(Nil, expr) => expr
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,7 @@ trait Implicits { self: Typer =>

lazy val funProto = fullProto match {
case proto: ViewProto =>
FunProto(untpd.TypedSplice(dummyTreeOfType(proto.argType)) :: Nil, proto.resultType, self)
FunProto(untpd.TypedSplice(dummyTreeOfType(proto.argType)) :: Nil, proto.resultType)(self)
case proto => proto
}

Expand Down
83 changes: 40 additions & 43 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -210,28 +210,38 @@ object ProtoTypes {

trait ApplyingProto extends ProtoType

class FunProtoState {

/** The list of typed arguments, if all arguments are typed */
var typedArgs: List[Tree] = Nil

/** A map in which typed arguments can be stored to be later integrated in `typedArgs`. */
var typedArg: SimpleIdentityMap[untpd.Tree, Tree] = SimpleIdentityMap.Empty

/** A map recording the typer states and constraints in which arguments stored in myTypedArg were typed */
var evalState: SimpleIdentityMap[untpd.Tree, (TyperState, Constraint)] = SimpleIdentityMap.Empty

/** The tupled version of this prototype, if it has been computed */
var tupled: Type = NoType

/** If true, the application of this prototype was canceled. */
var toDrop: Boolean = false
}

/** A prototype for expressions that appear in function position
*
* [](args): resultType
*/
case class FunProto(args: List[untpd.Tree], resType: Type, typer: Typer)(implicit ctx: Context)
case class FunProto(args: List[untpd.Tree], resType: Type)(typer: Typer, state: FunProtoState = new FunProtoState)(implicit ctx: Context)
extends UncachedGroundType with ApplyingProto {
private var myTypedArgs: List[Tree] = Nil

override def resultType(implicit ctx: Context) = resType

/** A map in which typed arguments can be stored to be later integrated in `typedArgs`. */
private var myTypedArg: SimpleIdentityMap[untpd.Tree, Tree] = SimpleIdentityMap.Empty

/** A map recording the typer states and constraints in which arguments stored in myTypedArg were typed */
private var evalState: SimpleIdentityMap[untpd.Tree, (TyperState, Constraint)] = SimpleIdentityMap.Empty

def isMatchedBy(tp: Type)(implicit ctx: Context) =
typer.isApplicable(tp, Nil, unforcedTypedArgs, resultType)

def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer) =
if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this
else new FunProto(args, resultType, typer)
else new FunProto(args, resultType)(typer)

override def notApplied = WildcardType

Expand All @@ -244,19 +254,19 @@ object ProtoTypes {
* @return True if all arguments have types (in particular, no types were forgotten).
*/
def allArgTypesAreCurrent()(implicit ctx: Context): Boolean = {
evalState foreachBinding { (arg, tstateConstr) =>
state.evalState foreachBinding { (arg, tstateConstr) =>
if ((tstateConstr._1.uncommittedAncestor.constraint `ne` ctx.typerState.constraint) ||
tstateConstr._2.isRetracted) {
typr.println(i"need to invalidate $arg / ${myTypedArg(arg)}, ${tstateConstr._2}, current = ${ctx.typerState.constraint}")
myTypedArg = myTypedArg.remove(arg)
evalState = evalState.remove(arg)
typr.println(i"need to invalidate $arg / ${state.typedArg(arg)}, ${tstateConstr._2}, current = ${ctx.typerState.constraint}")
state.typedArg = state.typedArg.remove(arg)
state.evalState = state.evalState.remove(arg)
Copy link
Member

Choose a reason for hiding this comment

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

This wasn't done before either, but shouldn't state.typedArgs be reset to an empty map too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe. But I only wanted to do the minimum change here. We have to come back to this at some point.

}
}
myTypedArg.size == args.length
state.typedArg.size == args.length
}

private def cacheTypedArg(arg: untpd.Tree, typerFn: untpd.Tree => Tree, force: Boolean)(implicit ctx: Context): Tree = {
var targ = myTypedArg(arg)
var targ = state.typedArg(arg)
if (targ == null) {
if (!force && untpd.functionWithUnknownParamType(arg).isDefined)
// If force = false, assume ? rather than reporting an error.
Expand All @@ -270,8 +280,8 @@ object ProtoTypes {
// typerstate if there are no errors. If we also omitted the next two lines
// when warning were emitted, `pos/t1756.scala` would fail when run with -feature.
// It would produce an orphan type parameter for CI when pickling.
myTypedArg = myTypedArg.updated(arg, targ)
evalState = evalState.updated(arg, (ctx.typerState, ctx.typerState.constraint))
state.typedArg = state.typedArg.updated(arg, targ)
state.evalState = state.evalState.updated(arg, (ctx.typerState, ctx.typerState.constraint))
}
}
}
Expand All @@ -285,9 +295,9 @@ object ProtoTypes {
* "missing parameter type" error
*/
private def typedArgs(force: Boolean): List[Tree] = {
if (myTypedArgs.size != args.length)
myTypedArgs = args.mapconserve(cacheTypedArg(_, typer.typed(_), force))
myTypedArgs
if (state.typedArgs.size != args.length)
state.typedArgs = args.mapconserve(cacheTypedArg(_, typer.typed(_), force))
state.typedArgs
}

def typedArgs: List[Tree] = typedArgs(force = true)
Expand All @@ -306,24 +316,19 @@ object ProtoTypes {
* @pre `arg` has been typed before
*/
def typeOfArg(arg: untpd.Tree)(implicit ctx: Context): Type =
myTypedArg(arg).tpe

private var myTupled: Type = NoType
state.typedArg(arg).tpe

/** The same proto-type but with all arguments combined in a single tuple */
def tupled: FunProto = myTupled match {
def tupled: FunProto = state.tupled match {
case pt: FunProto =>
pt
case _ =>
myTupled = new FunProto(untpd.Tuple(args) :: Nil, resultType, typer)
state.tupled = new FunProto(untpd.Tuple(args) :: Nil, resultType)(typer)
tupled
}

/** Somebody called the `tupled` method of this prototype */
def isTupled: Boolean = myTupled.isInstanceOf[FunProto]

/** If true, the application of this prototype was canceled. */
private var toDrop: Boolean = false
def isTupled: Boolean = state.tupled.isInstanceOf[FunProto]

/** Cancel the application of this prototype. This can happen for a nullary
* application `f()` if `f` refers to a symbol that exists both in parameterless
Expand All @@ -333,10 +338,10 @@ object ProtoTypes {
*/
def markAsDropped() = {
assert(args.isEmpty)
toDrop = true
state.toDrop = true
}

def isDropped: Boolean = toDrop
def isDropped: Boolean = state.toDrop

override def toString = s"FunProto(${args mkString ","} => $resultType)"

Expand All @@ -350,23 +355,15 @@ object ProtoTypes {

override def withContext(newCtx: Context) =
if (newCtx `eq` ctx) this
else {
val result = new FunProto(args, resType, typer)(newCtx)
result.myTypedArgs = myTypedArgs
result.myTypedArg = myTypedArg
result.evalState = evalState
result.myTupled = myTupled
result.toDrop = toDrop
result
}
else new FunProto(args, resType)(typer, state)(newCtx)
Copy link
Member

Choose a reason for hiding this comment

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

The idea of cloning FunProtos has promise, but we need to make sure that all clones refer to the same core state.

In what situation are multiple clones of the same FunProto live at the same time, and why is it necessary that they share the same state?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I noted we had problems with auto-tupling otherwise. The other danger is that we have FP1, infer a type argument, then go to FP2, infer another argument, then decide this is a blocker so fall back to FP1 and try something else afterwards. In this case the computed type argument of FP2 is lost.

}


/** A prototype for expressions that appear in function position
*
* [](args): resultType, where args are known to be typed
*/
class FunProtoTyped(args: List[tpd.Tree], resultType: Type, typer: Typer)(implicit ctx: Context) extends FunProto(args, resultType, typer)(ctx) {
class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer)(implicit ctx: Context) extends FunProto(args, resultType)(typer)(ctx) {
override def typedArgs = args
override def withContext(ctx: Context) = this
}
Expand Down Expand Up @@ -405,7 +402,7 @@ object ProtoTypes {
}

class UnapplyFunProto(argType: Type, typer: Typer)(implicit ctx: Context) extends FunProto(
untpd.TypedSplice(dummyTreeOfType(argType))(ctx) :: Nil, WildcardType, typer)
untpd.TypedSplice(dummyTreeOfType(argType))(ctx) :: Nil, WildcardType)(typer)

/** A prototype for expressions [] that are type-parameterized:
*
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 @@ -870,7 +870,7 @@ class Typer extends Namer
expr1.tpe
case _ =>
val protoArgs = args map (_ withType WildcardType)
val callProto = FunProto(protoArgs, WildcardType, this)
val callProto = FunProto(protoArgs, WildcardType)(this)
val expr1 = typedExpr(expr, callProto)
fnBody = cpy.Apply(fnBody)(untpd.TypedSplice(expr1), args)
expr1.tpe
Expand Down Expand Up @@ -2054,7 +2054,7 @@ class Typer extends Namer
tryInsertImplicitOnQualifier(tree, pt.withContext(ctx), locked).getOrElse(fallBack)

pt match {
case pt @ FunProto(Nil, _, _)
case pt @ FunProto(Nil, _)
if tree.symbol.allOverriddenSymbols.exists(_.info.isNullaryMethod) &&
tree.getAttachment(DroppedEmptyArgs).isEmpty =>
tree.putAttachment(DroppedEmptyArgs, ())
Expand Down