Skip to content

Commit 3f6a5f4

Browse files
committed
Fix #1503 - be careful where to insert an apply.
`apply` nodes should not be inserted in the result parts of a block, if-then-else, match, or try. Instead they should be added to the surrounding statement.
1 parent c420b4c commit 3f6a5f4

File tree

5 files changed

+50
-23
lines changed

5 files changed

+50
-23
lines changed

src/dotty/tools/dotc/core/Types.scala

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -927,15 +927,25 @@ object Types {
927927
def narrow(implicit ctx: Context): TermRef =
928928
TermRef(NoPrefix, ctx.newSkolem(this))
929929

930-
/** Useful for diagnsotics: The underlying type if this type is a type proxy,
930+
/** Useful for diagnsotics: The underlying type if this type is a type proxy,
931931
* otherwise NoType
932932
*/
933933
def underlyingIfProxy(implicit ctx: Context) = this match {
934934
case this1: TypeProxy => this1.underlying
935935
case _ => NoType
936936
}
937937

938-
// ----- Normalizing typerefs over refined types ----------------------------
938+
/** A "weak" prototype is a FunProto or a PolyProto that applies to a tree
939+
* properly containing the current expression. In that case, we should not
940+
* try to insert an apply to make a tree conform. The apply should be inserted
941+
* on the enclosing tree that is the function part of the actual application.
942+
*/
943+
def isWeakProto: Boolean = false
944+
945+
/** Overridden in FunProto and PolyProto */
946+
def weakenProto: Type = this
947+
948+
// ----- Normalizing typerefs over refined types ----------------------------
939949

940950
/** If this normalizes* to a refinement type that has a refinement for `name` (which might be followed
941951
* by other refinements), and the refined info is a type alias, return the alias,

src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
140140
": " ~ toText(tp.memberProto) ~ " }"
141141
case tp: ViewProto =>
142142
return toText(tp.argType) ~ " ?=>? " ~ toText(tp.resultType)
143-
case tp @ FunProto(args, resultType, _) =>
143+
case tp @ FunProto(args, resultType, _, _) =>
144144
val argsText = args match {
145145
case dummyTreeOfType(tp) :: Nil if !(tp isRef defn.NullClass) => "null: " ~ toText(tp)
146146
case _ => toTextGlobal(args, ", ")

src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
652652

653653
/** Overridden in ReTyper to handle primitive operations that can be generated after erasure */
654654
protected def handleUnexpectedFunType(tree: untpd.Apply, fun: Tree)(implicit ctx: Context): Tree =
655-
throw new Error(s"unexpected type.\n fun = $fun,\n methPart(fun) = ${methPart(fun)},\n methPart(fun).tpe = ${methPart(fun).tpe},\n tpe = ${fun.tpe}")
655+
throw new Error(i"unexpected type.\n fun = $fun,\n methPart(fun) = ${methPart(fun)},\n methPart(fun).tpe = ${methPart(fun).tpe},\n tpe = ${fun.tpe}")
656656

657657
def typedNamedArgs(args: List[untpd.Tree])(implicit ctx: Context) =
658658
for (arg @ NamedArg(id, argtpt) <- args) yield {
@@ -1168,7 +1168,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
11681168
alts filter (isApplicable(_, argTypes, resultType))
11691169

11701170
val candidates = pt match {
1171-
case pt @ FunProto(args, resultType, _) =>
1171+
case pt @ FunProto(args, resultType, _, _) =>
11721172
val numArgs = args.length
11731173
val normArgs = args.mapConserve {
11741174
case Block(Nil, expr) => expr
@@ -1225,7 +1225,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
12251225
}
12261226
}
12271227

1228-
case pt @ PolyProto(targs1, pt1) =>
1228+
case pt @ PolyProto(targs1, pt1, _) =>
12291229
assert(targs.isEmpty)
12301230
val alts1 = alts filter pt.isMatchedBy
12311231
resolveOverloaded(alts1, pt1, targs1)

src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ object ProtoTypes {
163163
*
164164
* [](args): resultType
165165
*/
166-
case class FunProto(args: List[untpd.Tree], resType: Type, typer: Typer)(implicit ctx: Context)
166+
case class FunProto(args: List[untpd.Tree], resType: Type, typer: Typer,
167+
override val isWeakProto: Boolean = false)(implicit ctx: Context)
167168
extends UncachedGroundType with ApplyingProto {
168169
private var myTypedArgs: List[Tree] = Nil
169170

@@ -182,6 +183,9 @@ object ProtoTypes {
182183
if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this
183184
else new FunProto(args, resultType, typer)
184185

186+
override def weakenProto =
187+
if (isWeakProto) this else new FunProto(args, resultType, typer, isWeakProto = true)
188+
185189
/** Forget the types of any arguments that have been typed producing a constraint in a
186190
* typer state that is not yet committed into the one of the current context `ctx`.
187191
* This is necessary to avoid "orphan" PolyParams that are referred to from
@@ -243,7 +247,10 @@ object ProtoTypes {
243247
/** Somebody called the `tupled` method of this prototype */
244248
def isTupled: Boolean = myTupled.isInstanceOf[FunProto]
245249

246-
override def toString = s"FunProto(${args mkString ","} => $resultType)"
250+
override def toString = {
251+
val weak = if (isWeakProto) "/weak" else ""
252+
s"FunProto(${args mkString ","} => $resultType$weak)"
253+
}
247254

248255
def map(tm: TypeMap)(implicit ctx: Context): FunProto =
249256
derivedFunProto(args, tm(resultType), typer)
@@ -303,7 +310,8 @@ object ProtoTypes {
303310
*
304311
* [] [targs] resultType
305312
*/
306-
case class PolyProto(targs: List[Type], resType: Type) extends UncachedGroundType with ProtoType {
313+
case class PolyProto(targs: List[Type], resType: Type, override val isWeakProto: Boolean = false)
314+
extends UncachedGroundType with ProtoType {
307315

308316
override def resultType(implicit ctx: Context) = resType
309317

@@ -319,6 +327,9 @@ object ProtoTypes {
319327
if ((targs eq this.targs) && (resType eq this.resType)) this
320328
else PolyProto(targs, resType)
321329

330+
override def weakenProto =
331+
if (isWeakProto) this else new PolyProto(targs, resType, isWeakProto = true)
332+
322333
def map(tm: TypeMap)(implicit ctx: Context): PolyProto =
323334
derivedPolyProto(targs mapConserve tm, tm(resultType))
324335

src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
572572
def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context) = track("typedBlock") {
573573
val exprCtx = index(tree.stats)
574574
val stats1 = typedStats(tree.stats, ctx.owner)
575-
val expr1 = typedExpr(tree.expr, pt)(exprCtx)
575+
val expr1 = typedExpr(tree.expr, pt.weakenProto)(exprCtx)
576576
ensureNoLocalRefs(
577577
assignType(cpy.Block(tree)(stats1, expr1), stats1, expr1), pt, localSyms(stats1))
578578
}
@@ -619,8 +619,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
619619

620620
def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = track("typedIf") {
621621
val cond1 = typed(tree.cond, defn.BooleanType)
622-
val thenp1 = typed(tree.thenp, pt)
623-
val elsep1 = typed(tree.elsep orElse (untpd.unitLiteral withPos tree.pos), pt)
622+
val thenp1 = typed(tree.thenp, pt.weakenProto)
623+
val elsep1 = typed(tree.elsep orElse (untpd.unitLiteral withPos tree.pos), pt.weakenProto)
624624
val thenp2 :: elsep2 :: Nil = harmonize(thenp1 :: elsep1 :: Nil)
625625
assignType(cpy.If(tree)(cond1, thenp2, elsep2), thenp2, elsep2)
626626
}
@@ -793,7 +793,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
793793
val selType = widenForMatchSelector(
794794
fullyDefinedType(sel1.tpe, "pattern selector", tree.pos))
795795

796-
val cases1 = typedCases(tree.cases, selType, pt)
796+
val cases1 = typedCases(tree.cases, selType, pt.weakenProto)
797797
val cases2 = harmonize(cases1).asInstanceOf[List[CaseDef]]
798798
assignType(cpy.Match(tree)(sel1, cases2), cases2)
799799
}
@@ -920,8 +920,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
920920
}
921921

922922
def typedTry(tree: untpd.Try, pt: Type)(implicit ctx: Context): Try = track("typedTry") {
923-
val expr1 = typed(tree.expr, pt)
924-
val cases1 = typedCases(tree.cases, defn.ThrowableType, pt)
923+
val expr1 = typed(tree.expr, pt.weakenProto)
924+
val cases1 = typedCases(tree.cases, defn.ThrowableType, pt.weakenProto)
925925
val finalizer1 = typed(tree.finalizer, defn.UnitType)
926926
val expr2 :: cases2x = harmonize(expr1 :: cases1)
927927
val cases2 = cases2x.asInstanceOf[List[CaseDef]]
@@ -1525,18 +1525,24 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
15251525
* 2nd strategy: If tree is a select `qual.name`, try to insert an implicit conversion
15261526
* around the qualifier part `qual` so that the result conforms to the expected type
15271527
* with wildcard result type.
1528+
*
1529+
* If `pt` is a weak prototype, only try to insert the implicit on qualifier. If that
1530+
* fails, succeed anyway with the idea that the `apply` can still be inserted further out.
1531+
* where the actual application is.
15281532
*/
15291533
def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType)(fallBack: (Tree, TyperState) => Tree)(implicit ctx: Context): Tree =
1530-
tryEither { implicit ctx =>
1531-
val sel = typedSelect(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt)
1532-
if (sel.tpe.isError) sel else adapt(sel, pt)
1533-
} { (failedTree, failedState) =>
1534-
tryInsertImplicitOnQualifier(tree, pt).getOrElse(fallBack(failedTree, failedState))
1535-
}
1534+
if (pt.isWeakProto)
1535+
tryInsertImplicitOnQualifier(tree, pt).getOrElse(tree)
1536+
else
1537+
tryEither { implicit ctx =>
1538+
val sel = typedSelect(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt)
1539+
if (sel.tpe.isError) sel else adapt(sel, pt)
1540+
} { (failedTree, failedState) =>
1541+
tryInsertImplicitOnQualifier(tree, pt).getOrElse(fallBack(failedTree, failedState))
1542+
}
15361543

15371544
/** If this tree is a select node `qual.name`, try to insert an implicit conversion
1538-
* `c` around `qual` so that `c(qual).name` conforms to `pt`. If that fails
1539-
* return `tree` itself.
1545+
* `c` around `qual` so that `c(qual).name` conforms to `pt`.
15401546
*/
15411547
def tryInsertImplicitOnQualifier(tree: Tree, pt: Type)(implicit ctx: Context): Option[Tree] = ctx.traceIndented(i"try insert impl on qualifier $tree $pt") {
15421548
tree match {

0 commit comments

Comments
 (0)