Skip to content

Commit 5965190

Browse files
committed
Fix #8861: Fix handling of closure results
1 parent 39b9011 commit 5965190

File tree

7 files changed

+91
-44
lines changed

7 files changed

+91
-44
lines changed

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -196,11 +196,11 @@ class Namer { typer: Typer =>
196196

197197
import untpd._
198198

199-
val TypedAhead: Property.Key[tpd.Tree] = new Property.Key
200-
val ExpandedTree: Property.Key[untpd.Tree] = new Property.Key
199+
val TypedAhead : Property.Key[tpd.Tree] = new Property.Key
200+
val ExpandedTree : Property.Key[untpd.Tree] = new Property.Key
201201
val ExportForwarders: Property.Key[List[tpd.MemberDef]] = new Property.Key
202-
val SymOfTree: Property.Key[Symbol] = new Property.Key
203-
val Deriver: Property.Key[typer.Deriver] = new Property.Key
202+
val SymOfTree : Property.Key[Symbol] = new Property.Key
203+
val Deriver : Property.Key[typer.Deriver] = new Property.Key
204204

205205
/** A partial map from unexpanded member and pattern defs and to their expansions.
206206
* Populated during enterSyms, emptied during typer.
@@ -1409,26 +1409,30 @@ class Namer { typer: Typer =>
14091409
* the corresponding parameter where bound parameters are replaced by
14101410
* Wildcards.
14111411
*/
1412-
def rhsProto = sym.asTerm.name collect {
1413-
case DefaultGetterName(original, idx) =>
1414-
val meth: Denotation =
1415-
if (original.isConstructorName && (sym.owner.is(ModuleClass)))
1416-
sym.owner.companionClass.info.decl(nme.CONSTRUCTOR)
1417-
else
1418-
ctx.defContext(sym).denotNamed(original)
1419-
def paramProto(paramss: List[List[Type]], idx: Int): Type = paramss match {
1420-
case params :: paramss1 =>
1421-
if (idx < params.length) wildApprox(params(idx))
1422-
else paramProto(paramss1, idx - params.length)
1423-
case nil =>
1424-
WildcardType
1425-
}
1426-
val defaultAlts = meth.altsWith(_.hasDefaultParams)
1427-
if (defaultAlts.length == 1)
1428-
paramProto(defaultAlts.head.info.widen.paramInfoss, idx)
1429-
else
1430-
WildcardType
1431-
} getOrElse WildcardType
1412+
def rhsProto = mdef.tpt.getAttachment(Typer.RhsProto)
1413+
.getOrElse {{
1414+
sym.asTerm.name collect {
1415+
case DefaultGetterName(original, idx) =>
1416+
val meth: Denotation =
1417+
if (original.isConstructorName && (sym.owner.is(ModuleClass)))
1418+
sym.owner.companionClass.info.decl(nme.CONSTRUCTOR)
1419+
else
1420+
ctx.defContext(sym).denotNamed(original)
1421+
def paramProto(paramss: List[List[Type]], idx: Int): Type = paramss match {
1422+
case params :: paramss1 =>
1423+
if (idx < params.length) wildApprox(params(idx))
1424+
else paramProto(paramss1, idx - params.length)
1425+
case nil =>
1426+
WildcardType
1427+
}
1428+
val defaultAlts = meth.altsWith(_.hasDefaultParams)
1429+
if (defaultAlts.length == 1)
1430+
paramProto(defaultAlts.head.info.widen.paramInfoss, idx)
1431+
else
1432+
WildcardType
1433+
}
1434+
}.getOrElse(WildcardType)
1435+
}
14321436

14331437
// println(s"final inherited for $sym: ${inherited.toString}") !!!
14341438
// println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}")
@@ -1443,7 +1447,12 @@ class Namer { typer: Typer =>
14431447
val tp1 = tp.widenTermRefExpr.simplified match
14441448
case ctp: ConstantType if isInlineVal => ctp
14451449
case ref: TypeRef if ref.symbol.is(ModuleClass) => tp
1446-
case tp => tp.widenUnion
1450+
case tp =>
1451+
if true then ctx.typeComparer.widenInferred(tp, rhsProto)
1452+
else rhsProto match {
1453+
case OrType(_, _) | WildcardType(TypeBounds(_, OrType(_, _))) => tp.widen
1454+
case _ => tp.widenUnion
1455+
}
14471456
tp1.dropRepeatedAnnot
14481457
}
14491458

@@ -1496,26 +1505,13 @@ class Namer { typer: Typer =>
14961505
val tpe = tpFun(paramss.head)
14971506
if (isFullyDefined(tpe, ForceDegree.none)) tpe
14981507
else typedAheadExpr(mdef.rhs, tpe).tpe
1499-
case TypedSplice(tpt: TypeTree) if !isFullyDefined(tpt.tpe, ForceDegree.none) =>
1500-
val rhsType = typedAheadExpr(mdef.rhs, tpt.tpe).tpe
1501-
mdef match {
1502-
case mdef: DefDef if mdef.name == nme.ANON_FUN =>
1503-
val hygienicType = avoid(rhsType, paramss.flatten)
1504-
if (!hygienicType.isValueType || !(hygienicType <:< tpt.tpe))
1505-
ctx.error(i"return type ${tpt.tpe} of lambda cannot be made hygienic;\n" +
1506-
i"it is not a supertype of the hygienic type $hygienicType", mdef.sourcePos)
1507-
//println(i"lifting $rhsType over $paramss -> $hygienicType = ${tpt.tpe}")
1508-
//println(TypeComparer.explained { implicit ctx => hygienicType <:< tpt.tpe })
1509-
case _ =>
1510-
}
1511-
WildcardType
15121508
case _ =>
15131509
WildcardType
15141510
}
1515-
val memTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe)
1511+
val mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe)
15161512
if (ctx.explicitNulls && mdef.mods.is(JavaDefined))
1517-
JavaNullInterop.nullifyMember(sym, memTpe, mdef.mods.isAllOf(JavaEnumValue))
1518-
else memTpe
1513+
JavaNullInterop.nullifyMember(sym, mbrTpe, mdef.mods.isAllOf(JavaEnumValue))
1514+
else mbrTpe
15191515
}
15201516

15211517
/** The type signature of a DefDef with given symbol */

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ object Typer {
7777
*/
7878
private val DroppedEmptyArgs = new Property.Key[Unit]
7979

80+
/** An attachment on the untyped TypeTree() that is the result type of a desugared
81+
* anonymous function. It communicates the expected type of the closure's right-hand side.
82+
*/
83+
val RhsProto: Property.Key[Type] = new Property.StickyKey
84+
8085
/** An attachment that indicates a failed conversion or extension method
8186
* search was tried on a tree. This will in some cases be reported in error messages
8287
*/
@@ -1158,7 +1163,20 @@ class Typer extends Namer
11581163
else cpy.ValDef(param)(
11591164
tpt = untpd.TypeTree(
11601165
inferredParamType(param, protoFormal(i)).translateFromRepeated(toArray = false)))
1161-
desugar.makeClosure(inferredParams, fnBody, resultTpt, isContextual)
1166+
val anonfunTpt = resultTpt match
1167+
case untpd.TypedSplice(tpt: TypeTree) if !isFullyDefined(tpt.tpe, ForceDegree.none) =>
1168+
// If the expected result type is not fully defined, do not use it as the anonymous
1169+
// function result type, since this would risk polluting constraints for variables
1170+
// in that result type with references to local parameters. See i8861.scala for a
1171+
// problematic case. Instead, keep a sanitized version where variables are replaced by
1172+
// wildcards in a RhsProto attachment. It will be picked up by Namer
1173+
// as the expected type of the closure's body.
1174+
untpd.TypeTree()
1175+
.withSpan(tpt.span)
1176+
.withAttachment(RhsProto, wildApprox(tpt.tpe))
1177+
case _ =>
1178+
resultTpt
1179+
desugar.makeClosure(inferredParams, fnBody, anonfunTpt, isContextual)
11621180
}
11631181
typed(desugared, pt)
11641182
}

tests/neg/i6565.scala renamed to tests/pos/i6565.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ lazy val ok: Lifted[String] = { // ok despite map returning a union
1212
point("a").map(_ => if true then "foo" else error) // ok
1313
}
1414

15-
lazy val bad: Lifted[String] = { // found Lifted[Object]
16-
point("a").flatMap(_ => point("b").map(_ => if true then "foo" else error)) // error
15+
lazy val nowAlsoOK: Lifted[String] = {
16+
point("a").flatMap(_ => point("b").map(_ => if true then "foo" else error)) // now also OK
1717
}

tests/run/i8861.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
hello
2+
hello

tests/run/i8861.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
object Test {
2+
sealed trait Container { s =>
3+
type A
4+
def visit[R](int: IntV & s.type => R, str: StrV & s.type => R): R
5+
}
6+
final class IntV extends Container { s =>
7+
type A = Int
8+
val i: Int = 42
9+
def visit[R](int: IntV & s.type => R, str: StrV & s.type => R): R = int(this)
10+
}
11+
final class StrV extends Container { s =>
12+
type A = String
13+
val t: String = "hello"
14+
def visit[R](int: IntV & s.type => R, str: StrV & s.type => R): R = str(this)
15+
}
16+
17+
def minimalOk[R](c: Container { type A = R }): R = c.visit[R](
18+
int = vi => vi.i : vi.A,
19+
str = vs => vs.t : vs.A
20+
)
21+
def minimalFail[M](c: Container { type A = M }): M = c.visit(
22+
int = vi => vi.i : vi.A,
23+
str = vs => vs.t : vs.A // error
24+
)
25+
26+
def main(args: Array[String]): Unit = {
27+
val e: Container { type A = String } = new StrV
28+
println(minimalOk(e)) // this one prints "hello"
29+
println(minimalFail(e)) // this one fails with ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer
30+
}
31+
}

0 commit comments

Comments
 (0)