Skip to content

Commit

Permalink
Fix #8861: Fix handling of closure results
Browse files Browse the repository at this point in the history
  • Loading branch information
odersky committed May 5, 2020
1 parent 39b9011 commit 5965190
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 44 deletions.
78 changes: 37 additions & 41 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,11 @@ class Namer { typer: Typer =>

import untpd._

val TypedAhead: Property.Key[tpd.Tree] = new Property.Key
val ExpandedTree: Property.Key[untpd.Tree] = new Property.Key
val TypedAhead : Property.Key[tpd.Tree] = new Property.Key
val ExpandedTree : Property.Key[untpd.Tree] = new Property.Key
val ExportForwarders: Property.Key[List[tpd.MemberDef]] = new Property.Key
val SymOfTree: Property.Key[Symbol] = new Property.Key
val Deriver: Property.Key[typer.Deriver] = new Property.Key
val SymOfTree : Property.Key[Symbol] = new Property.Key
val Deriver : Property.Key[typer.Deriver] = new Property.Key

/** A partial map from unexpanded member and pattern defs and to their expansions.
* Populated during enterSyms, emptied during typer.
Expand Down Expand Up @@ -1409,26 +1409,30 @@ class Namer { typer: Typer =>
* the corresponding parameter where bound parameters are replaced by
* Wildcards.
*/
def rhsProto = sym.asTerm.name collect {
case DefaultGetterName(original, idx) =>
val meth: Denotation =
if (original.isConstructorName && (sym.owner.is(ModuleClass)))
sym.owner.companionClass.info.decl(nme.CONSTRUCTOR)
else
ctx.defContext(sym).denotNamed(original)
def paramProto(paramss: List[List[Type]], idx: Int): Type = paramss match {
case params :: paramss1 =>
if (idx < params.length) wildApprox(params(idx))
else paramProto(paramss1, idx - params.length)
case nil =>
WildcardType
}
val defaultAlts = meth.altsWith(_.hasDefaultParams)
if (defaultAlts.length == 1)
paramProto(defaultAlts.head.info.widen.paramInfoss, idx)
else
WildcardType
} getOrElse WildcardType
def rhsProto = mdef.tpt.getAttachment(Typer.RhsProto)
.getOrElse {{
sym.asTerm.name collect {
case DefaultGetterName(original, idx) =>
val meth: Denotation =
if (original.isConstructorName && (sym.owner.is(ModuleClass)))
sym.owner.companionClass.info.decl(nme.CONSTRUCTOR)
else
ctx.defContext(sym).denotNamed(original)
def paramProto(paramss: List[List[Type]], idx: Int): Type = paramss match {
case params :: paramss1 =>
if (idx < params.length) wildApprox(params(idx))
else paramProto(paramss1, idx - params.length)
case nil =>
WildcardType
}
val defaultAlts = meth.altsWith(_.hasDefaultParams)
if (defaultAlts.length == 1)
paramProto(defaultAlts.head.info.widen.paramInfoss, idx)
else
WildcardType
}
}.getOrElse(WildcardType)
}

// println(s"final inherited for $sym: ${inherited.toString}") !!!
// println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}")
Expand All @@ -1443,7 +1447,12 @@ class Namer { typer: Typer =>
val tp1 = tp.widenTermRefExpr.simplified match
case ctp: ConstantType if isInlineVal => ctp
case ref: TypeRef if ref.symbol.is(ModuleClass) => tp
case tp => tp.widenUnion
case tp =>
if true then ctx.typeComparer.widenInferred(tp, rhsProto)
else rhsProto match {
case OrType(_, _) | WildcardType(TypeBounds(_, OrType(_, _))) => tp.widen
case _ => tp.widenUnion
}
tp1.dropRepeatedAnnot
}

Expand Down Expand Up @@ -1496,26 +1505,13 @@ class Namer { typer: Typer =>
val tpe = tpFun(paramss.head)
if (isFullyDefined(tpe, ForceDegree.none)) tpe
else typedAheadExpr(mdef.rhs, tpe).tpe
case TypedSplice(tpt: TypeTree) if !isFullyDefined(tpt.tpe, ForceDegree.none) =>
val rhsType = typedAheadExpr(mdef.rhs, tpt.tpe).tpe
mdef match {
case mdef: DefDef if mdef.name == nme.ANON_FUN =>
val hygienicType = avoid(rhsType, paramss.flatten)
if (!hygienicType.isValueType || !(hygienicType <:< tpt.tpe))
ctx.error(i"return type ${tpt.tpe} of lambda cannot be made hygienic;\n" +
i"it is not a supertype of the hygienic type $hygienicType", mdef.sourcePos)
//println(i"lifting $rhsType over $paramss -> $hygienicType = ${tpt.tpe}")
//println(TypeComparer.explained { implicit ctx => hygienicType <:< tpt.tpe })
case _ =>
}
WildcardType
case _ =>
WildcardType
}
val memTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe)
val mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe)
if (ctx.explicitNulls && mdef.mods.is(JavaDefined))
JavaNullInterop.nullifyMember(sym, memTpe, mdef.mods.isAllOf(JavaEnumValue))
else memTpe
JavaNullInterop.nullifyMember(sym, mbrTpe, mdef.mods.isAllOf(JavaEnumValue))
else mbrTpe
}

/** The type signature of a DefDef with given symbol */
Expand Down
20 changes: 19 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ object Typer {
*/
private val DroppedEmptyArgs = new Property.Key[Unit]

/** An attachment on the untyped TypeTree() that is the result type of a desugared
* anonymous function. It communicates the expected type of the closure's right-hand side.
*/
val RhsProto: Property.Key[Type] = new Property.StickyKey

/** An attachment that indicates a failed conversion or extension method
* search was tried on a tree. This will in some cases be reported in error messages
*/
Expand Down Expand Up @@ -1158,7 +1163,20 @@ class Typer extends Namer
else cpy.ValDef(param)(
tpt = untpd.TypeTree(
inferredParamType(param, protoFormal(i)).translateFromRepeated(toArray = false)))
desugar.makeClosure(inferredParams, fnBody, resultTpt, isContextual)
val anonfunTpt = resultTpt match
case untpd.TypedSplice(tpt: TypeTree) if !isFullyDefined(tpt.tpe, ForceDegree.none) =>
// If the expected result type is not fully defined, do not use it as the anonymous
// function result type, since this would risk polluting constraints for variables
// in that result type with references to local parameters. See i8861.scala for a
// problematic case. Instead, keep a sanitized version where variables are replaced by
// wildcards in a RhsProto attachment. It will be picked up by Namer
// as the expected type of the closure's body.
untpd.TypeTree()
.withSpan(tpt.span)
.withAttachment(RhsProto, wildApprox(tpt.tpe))
case _ =>
resultTpt
desugar.makeClosure(inferredParams, fnBody, anonfunTpt, isContextual)
}
typed(desugared, pt)
}
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/i6565.scala → tests/pos/i6565.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ lazy val ok: Lifted[String] = { // ok despite map returning a union
point("a").map(_ => if true then "foo" else error) // ok
}

lazy val bad: Lifted[String] = { // found Lifted[Object]
point("a").flatMap(_ => point("b").map(_ => if true then "foo" else error)) // error
lazy val nowAlsoOK: Lifted[String] = {
point("a").flatMap(_ => point("b").map(_ => if true then "foo" else error)) // now also OK
}
2 changes: 2 additions & 0 deletions tests/run/i8861.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
hello
hello
31 changes: 31 additions & 0 deletions tests/run/i8861.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
object Test {
sealed trait Container { s =>
type A
def visit[R](int: IntV & s.type => R, str: StrV & s.type => R): R
}
final class IntV extends Container { s =>
type A = Int
val i: Int = 42
def visit[R](int: IntV & s.type => R, str: StrV & s.type => R): R = int(this)
}
final class StrV extends Container { s =>
type A = String
val t: String = "hello"
def visit[R](int: IntV & s.type => R, str: StrV & s.type => R): R = str(this)
}

def minimalOk[R](c: Container { type A = R }): R = c.visit[R](
int = vi => vi.i : vi.A,
str = vs => vs.t : vs.A
)
def minimalFail[M](c: Container { type A = M }): M = c.visit(
int = vi => vi.i : vi.A,
str = vs => vs.t : vs.A // error
)

def main(args: Array[String]): Unit = {
val e: Container { type A = String } = new StrV
println(minimalOk(e)) // this one prints "hello"
println(minimalFail(e)) // this one fails with ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer
}
}

0 comments on commit 5965190

Please sign in to comment.