-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Changes from 5 commits
319d478
f4266b4
7a446fa
6edb091
8b21081
2e708a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -168,15 +168,19 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => | |
* to be used as initializers of trait parameters if the target of the call | ||
* is a trait. | ||
*/ | ||
def transformConstructor(tree: Tree): (Tree, List[Tree]) = { | ||
val Apply(sel @ Select(New(_), nme.CONSTRUCTOR), args) = tree | ||
val (callArgs, initArgs) = if (tree.symbol.owner.is(Trait)) (Nil, args) else (args, Nil) | ||
(superRef(tree.symbol, tree.pos).appliedToArgs(callArgs), initArgs) | ||
def transformConstructor(tree: Tree): (Tree, List[Tree]) = tree match { | ||
case Block(stats, expr) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, if this works, doesn't it mean that we could very easily support early initializers (for the Scala 2 mode) ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's still a way to go if we want to bring back early initializers. We'd also have to map definitions in the block back to definitions of the class fields. |
||
val (scall, inits) = transformConstructor(expr) | ||
(cpy.Block(tree)(stats, scall), inits) | ||
case _ => | ||
val Apply(sel @ Select(New(_), nme.CONSTRUCTOR), args) = tree | ||
val (callArgs, initArgs) = if (tree.symbol.owner.is(Trait)) (Nil, args) else (args, Nil) | ||
(superRef(tree.symbol, tree.pos).appliedToArgs(callArgs), initArgs) | ||
} | ||
|
||
val superCallsAndArgs = ( | ||
for (p <- impl.parents if p.symbol.isConstructor) | ||
yield p.symbol.owner -> transformConstructor(p) | ||
for (p <- impl.parents; constr = stripBlock(p).symbol if constr.isConstructor) | ||
yield constr.owner -> transformConstructor(p) | ||
).toMap | ||
val superCalls = superCallsAndArgs.mapValues(_._1) | ||
val initArgs = superCallsAndArgs.mapValues(_._2) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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[this] 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[this] 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[this] 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 | ||
|
||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This wasn't done before either, but shouldn't There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
@@ -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)) | ||
} | ||
} | ||
} | ||
|
@@ -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) | ||
|
@@ -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[this] 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[this] 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 | ||
|
@@ -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)" | ||
|
||
|
@@ -347,15 +352,20 @@ object ProtoTypes { | |
ta(ta.foldOver(x, typedArgs.tpes), resultType) | ||
|
||
override def deepenProto(implicit ctx: Context) = derivedFunProto(args, resultType.deepenProto, typer) | ||
|
||
override def withContext(newCtx: Context) = | ||
if (newCtx `eq` ctx) this | ||
else new FunProto(args, resType)(typer, state)(newCtx) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
|
||
/** A prototype for implicitly inferred views: | ||
|
@@ -392,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: | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
class A(x: Int)(y: String = "hi") | ||
|
||
object Test { | ||
def f() = 22 | ||
|
||
class B extends A(f())() | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
class Foo { | ||
def apply(bar: Bar[Int]): Unit = () | ||
def apply(i: Int): Unit = () | ||
} | ||
|
||
class Bar[X](x: X) | ||
|
||
class Main { | ||
val foo = new Foo | ||
foo(new Bar(0)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
object Foo { | ||
case class Bar(map: Map[String, String]) | ||
|
||
object Bar { | ||
def apply(str: String): Bar = ??? | ||
} | ||
|
||
Bar(Map("A" -> "B")) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a documentation comment.