-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Make sure arguments are evaluated in the correct typer state. #1460
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 all commits
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 |
---|---|---|
|
@@ -541,24 +541,13 @@ trait Applications extends Compatibility { self: Typer with Dynamic => | |
|
||
def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { | ||
|
||
/** Try same application with an implicit inserted around the qualifier of the function | ||
* part. Return an optional value to indicate success. | ||
*/ | ||
def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] = | ||
tryInsertImplicitOnQualifier(fun1, proto) flatMap { fun2 => | ||
tryEither { implicit ctx => | ||
Some(typedApply( | ||
cpy.Apply(tree)(untpd.TypedSplice(fun2), proto.typedArgs map untpd.TypedSplice), | ||
pt)): Option[Tree] | ||
} { (_, _) => None } | ||
} | ||
|
||
def realApply(implicit ctx: Context): Tree = track("realApply") { | ||
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 | ||
// a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application. | ||
// a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application, | ||
// until, possibly, we have to fall back to insert an implicit on the qualifier. | ||
// This crucially relies on he fact that `proto` is used only in a single call of `adapt`, | ||
// otherwise we would get possible cross-talk between different `adapt` calls using the same | ||
// prototype. A cleaner alternative would be to return a modified prototype from `adapt` together with | ||
|
@@ -574,6 +563,32 @@ trait Applications extends Compatibility { self: Typer with Dynamic => | |
if (!constrainResult(fun1.tpe.widen, proto.derivedFunProto(resultType = pt))) | ||
typr.println(i"result failure for $tree with type ${fun1.tpe.widen}, expected = $pt") | ||
|
||
/** Type application where arguments come from prototype, and no implicits are inserted */ | ||
def simpleApply(fun1: Tree, proto: FunProto)(implicit ctx: Context): Tree = | ||
methPart(fun1).tpe match { | ||
case funRef: TermRef => | ||
val app = | ||
if (proto.allArgTypesAreCurrent()) | ||
new ApplyToTyped(tree, fun1, funRef, proto.typedArgs, pt) | ||
else | ||
new ApplyToUntyped(tree, fun1, funRef, proto, pt)(argCtx(tree)) | ||
convertNewGenericArray(ConstFold(app.result)) | ||
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. Some of this code is duplicated from 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. As far as I can see, the code was moved out of typedApply, not duplicated. |
||
case _ => | ||
handleUnexpectedFunType(tree, fun1) | ||
} | ||
|
||
/** Try same application with an implicit inserted around the qualifier of the function | ||
* part. Return an optional value to indicate success. | ||
*/ | ||
def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] = | ||
tryInsertImplicitOnQualifier(fun1, proto) flatMap { fun2 => | ||
tryEither { | ||
implicit ctx => Some(simpleApply(fun2, proto)): Option[Tree] | ||
} { | ||
(_, _) => None | ||
} | ||
} | ||
|
||
fun1.tpe match { | ||
case ErrorType => tree.withType(ErrorType) | ||
case TryDynamicCallType => | ||
|
@@ -583,23 +598,20 @@ trait Applications extends Compatibility { self: Typer with Dynamic => | |
case _ => | ||
handleUnexpectedFunType(tree, fun1) | ||
} | ||
case _ => methPart(fun1).tpe match { | ||
case funRef: TermRef => | ||
tryEither { implicit ctx => | ||
val app = | ||
if (proto.argsAreTyped) new ApplyToTyped(tree, fun1, funRef, proto.typedArgs, pt) | ||
else new ApplyToUntyped(tree, fun1, funRef, proto, pt)(argCtx(tree)) | ||
val result = app.result | ||
convertNewGenericArray(ConstFold(result)) | ||
} { (failedVal, failedState) => | ||
case _ => | ||
tryEither { | ||
implicit ctx => simpleApply(fun1, proto) | ||
} { | ||
(failedVal, failedState) => | ||
def fail = { failedState.commit(); failedVal } | ||
// Try once with original prototype and once (if different) with tupled one. | ||
// The reason we need to try both is that the decision whether to use tupled | ||
// or not was already taken but might have to be revised when an implicit | ||
// is inserted on the qualifier. | ||
tryWithImplicitOnQualifier(fun1, originalProto).getOrElse( | ||
if (proto eq originalProto) fail | ||
else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail)) | ||
} | ||
case _ => | ||
handleUnexpectedFunType(tree, fun1) | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
@@ -611,7 +623,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => | |
* | ||
* { val xs = es; e' = e' + args } | ||
*/ | ||
def typedOpAssign: Tree = track("typedOpAssign") { | ||
def typedOpAssign: Tree = track("typedOpAssign") { | ||
val Apply(Select(lhs, name), rhss) = tree | ||
val lhs1 = typedExpr(lhs) | ||
val liftedDefs = new mutable.ListBuffer[Tree] | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
trait Ring[T <: Ring[T]] { | ||
def +(that: T): T | ||
def *(that: T): T | ||
} | ||
|
||
class A extends Ring[A] { | ||
def +(that: A) = new A | ||
def *(that: A) = new A | ||
} | ||
|
||
class Poly[C <: Ring[C]](val c: C) extends Ring[Poly[C]] { | ||
def +(that: Poly[C]) = new Poly(this.c + that.c) | ||
def *(that: Poly[C]) = new Poly(this.c*that.c) | ||
} | ||
|
||
object Test extends App { | ||
|
||
implicit def coef2poly[CI <: Ring[CI]](c: CI): Poly[CI] = new Poly(c) | ||
|
||
val a = new A | ||
val x = new Poly(new A) | ||
|
||
println(x + a) // works | ||
println(a + x) // works | ||
|
||
val y = new Poly(new Poly(new A)) | ||
|
||
println(x + y*x) // works | ||
println(x*y + x) // works | ||
println(y*x + x) // works | ||
|
||
println(x + x*y) // failed before, first with type error, after that was fixed with "orphan poly parameter CI". | ||
} |
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.
proposal: instead of storing the direct parent, lazily-cache
uncommittedAncestor
and test-update it every time it's updated: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.
Good point. I'll roll that into the next PR.