Skip to content

Commit 0d885dc

Browse files
committed
Redo handling of closures without relying on pre-existing maps
1 parent a7dfefb commit 0d885dc

34 files changed

+261
-260
lines changed

compiler/src/dotty/tools/dotc/cc/CaptureOps.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@ object ccConfig:
3333
inline val allowUnsoundMaps = false
3434

3535
/** If enabled, use a special path in recheckClosure for closures
36-
* that are eta expansions. This can improve some error messages.
36+
* to compare the result tpt of the anonymous functon with the expected
37+
* result type. This can narrow the scope of error messages.
38+
*/
39+
inline val preTypeClosureResults = false
40+
41+
/** If this and `preTypeClosureResults` are both enabled, disable `preTypeClosureResults`
42+
* for eta expansions. This can improve some error messages.
3743
*/
3844
inline val handleEtaExpansionsSpecially = true
3945

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ class CheckCaptures extends Recheck, SymTransformer:
318318
* upper approximation.
319319
*/
320320
private def interpolate(tp: Type, sym: Symbol, startingVariance: Int = 1)(using Context): Unit =
321-
321+
322322
object variances extends TypeTraverser:
323323
variance = startingVariance
324324
val varianceOfVar = EqHashMap[CaptureSet.Var, Int]()
@@ -335,7 +335,7 @@ class CheckCaptures extends Recheck, SymTransformer:
335335
traverse(rinfo)
336336
case _ =>
337337
traverseChildren(t)
338-
338+
339339
val interpolator = new TypeTraverser:
340340
override def traverse(t: Type) = t match
341341
case t @ CapturingType(parent, refs) =>
@@ -349,7 +349,7 @@ class CheckCaptures extends Recheck, SymTransformer:
349349
traverse(rinfo)
350350
case _ =>
351351
traverseChildren(t)
352-
352+
353353
variances.traverse(tp)
354354
interpolator.traverse(tp)
355355
end interpolate
@@ -947,28 +947,51 @@ class CheckCaptures extends Recheck, SymTransformer:
947947
* { def $anonfun(...) = ...; closure($anonfun, ...)}
948948
*/
949949
override def recheckClosureBlock(mdef: DefDef, expr: Closure, pt: Type)(using Context): Type =
950+
951+
def matchParams(paramss: List[ParamClause], pt: Type): Unit =
952+
//println(i"match $mdef against $pt")
953+
paramss match
954+
case params :: paramss1 => pt match
955+
case defn.PolyFunctionOf(poly: PolyType) =>
956+
assert(params.hasSameLengthAs(poly.paramInfos))
957+
matchParams(paramss1, poly.instantiate(params.map(_.symbol.typeRef)))
958+
case FunctionOrMethod(argTypes, resType) =>
959+
assert(params.hasSameLengthAs(argTypes), i"$mdef vs $pt, ${params}")
960+
for (argType, param) <- argTypes.lazyZip(params) do
961+
//println(i"compare $argType against $param")
962+
checkConformsExpr(argType, root.freshToCap(param.asInstanceOf[ValDef].tpt.nuType), param)
963+
if ccConfig.preTypeClosureResults && !(isEtaExpansion(mdef) && ccConfig.handleEtaExpansionsSpecially) then
964+
// Check whether the closure's result conforms to the expected type
965+
// This constrains parameter types of the closure which can give better
966+
// error messages.
967+
// But if the closure is an eta expanded method reference it's better to not constrain
968+
// its internals early since that would give error messages in generated code
969+
// which are less intelligible. An example is the line `a = x` in
970+
// neg-custom-args/captures/vars.scala. That's why this code is conditioned.
971+
// to apply only to closures that are not eta expansions.
972+
assert(paramss1.isEmpty)
973+
val respt = root.resultToFresh:
974+
pt match
975+
case defn.RefinedFunctionOf(rinfo) =>
976+
val paramTypes = params.map(_.asInstanceOf[ValDef].tpt.nuType)
977+
rinfo.instantiate(paramTypes)
978+
case _ =>
979+
resType
980+
val res = root.resultToFresh(mdef.tpt.nuType)
981+
// We need to open existentials here in order not to get vars mixed up in them
982+
// We do the proper check with existentials when we are finished with the closure block.
983+
capt.println(i"pre-check closure $expr of type $res against $respt")
984+
checkConformsExpr(res, respt, expr)
985+
case _ =>
986+
case Nil =>
987+
950988
openClosures = (mdef.symbol, pt) :: openClosures
989+
// openClosures is needed for errors but currently makes no difference
990+
// TODO follow up on this
951991
try
952-
// Constrain closure's parameters and result from the expected type before
953-
// rechecking the body.
954-
val res = recheckClosure(expr, pt, forceDependent = true)
955-
if !(isEtaExpansion(mdef) && ccConfig.handleEtaExpansionsSpecially) then
956-
// Check whether the closure's results conforms to the expected type
957-
// This constrains parameter types of the closure which can give better
958-
// error messages.
959-
// But if the closure is an eta expanded method reference it's better to not constrain
960-
// its internals early since that would give error messages in generated code
961-
// which are less intelligible. An example is the line `a = x` in
962-
// neg-custom-args/captures/vars.scala. That's why this code is conditioned.
963-
// to apply only to closures that are not eta expansions.
964-
val res1 = root.resultToFresh(res) // TODO: why deep = true?
965-
val pt1 = root.resultToFresh(pt)
966-
// We need to open existentials here in order not to get vars mixed up in them
967-
// We do the proper check with existentials when we are finished with the closure block.
968-
capt.println(i"pre-check closure $expr of type $res1 against $pt1")
969-
checkConformsExpr(res1, pt1, expr)
992+
matchParams(mdef.paramss, pt)
970993
recheckDef(mdef, mdef.symbol)
971-
res
994+
recheckClosure(expr, pt, forceDependent = true)
972995
finally
973996
openClosures = openClosures.tail
974997
end recheckClosureBlock

compiler/src/dotty/tools/dotc/cc/Setup.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import collection.mutable
2121
import CCState.*
2222
import dotty.tools.dotc.util.NoSourcePosition
2323
import CheckCaptures.CheckerAPI
24+
import NamerOps.methodType
2425

2526
/** Operations accessed from CheckCaptures */
2627
trait SetupAPI:
@@ -704,6 +705,23 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
704705
// If there's a change in the signature, update the info of `sym`
705706
if sym.exists && signatureChanges then
706707
val updatedInfo =
708+
if ccConfig.newScheme then
709+
def newInfo = root.toResultInResults(report.error(_, tree.srcPos)):
710+
if sym.is(Method) then methodType(sym.paramSymss, localReturnType)
711+
else tree.tpt.nuType
712+
if tree.tpt.isInstanceOf[InferredTypeTree]
713+
&& !sym.is(Param) && !sym.is(ParamAccessor)
714+
then
715+
val prevInfo = sym.info
716+
new LazyType:
717+
def complete(denot: SymDenotation)(using Context) =
718+
assert(ctx.phase == thisPhase.next, i"$sym")
719+
capt.println(i"forcing $sym, printing = ${ctx.mode.is(Mode.Printing)}")
720+
sym.info = prevInfo // set info provisionally so we can analyze the symbol in recheck
721+
completeDef(tree, sym, this)
722+
sym.info = newInfo
723+
else newInfo
724+
else
707725
val newInfo =
708726
root.toResultInResults(report.error(_, tree.srcPos)):
709727
integrateRT(sym.info, sym.paramSymss, localReturnType, Nil, Nil)

tests/neg-custom-args/captures/box-adapt-boxing.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
trait Cap
22

33
def main(io: Cap^, fs: Cap^): Unit = {
4-
val test1: Unit -> Unit = _ => {
4+
val test1: Unit -> Unit = _ => { // error
55
type Op = [T] -> (T ->{io} Unit) -> Unit
66
val f: (Cap^{io}) -> Unit = ???
77
val op: Op = ???
8-
op[Cap^{io}](f) // error
8+
op[Cap^{io}](f)
99
// expected type of f: {io} (box {io} Cap) -> Unit
1010
// actual type: ({io} Cap) -> Unit
1111
// adapting f to the expected type will also

tests/neg-custom-args/captures/box-adapt-cases.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/box-adapt-cases.scala:15:10 ------------------------------
99
15 | x.value(cap => cap.use()) // error
1010
| ^^^^^^^^^^^^^^^^
11-
| Found: (cap: box Cap^?) ->{io} Int
11+
| Found: (cap: box Cap^{io}) ->{io} Int
1212
| Required: (cap: box Cap^{io}) -> Int
1313
|
1414
| longer explanation available when compiling with `-explain`

tests/neg-custom-args/captures/byname.check

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,21 @@
66
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:10:6 ----------------------------------------
77
10 | h(f2()) // error
88
| ^^^^
9-
| Found: Int ->{cap1} Int
10-
| Required: Int ->? Int
9+
| Found: () ?->{cap1} Int ->{cap1} Int
10+
| Required: () ?=> Int ->{cap2} Int
1111
|
1212
| longer explanation available when compiling with `-explain`
13-
-- Error: tests/neg-custom-args/captures/byname.scala:19:5 -------------------------------------------------------------
13+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:19:5 ----------------------------------------
1414
19 | h(g()) // error
1515
| ^^^
16-
| reference (cap2 : Cap) is not included in the allowed capture set {cap1}
17-
| of an enclosing function literal with expected type () ?->{cap1} I
18-
-- Error: tests/neg-custom-args/captures/byname.scala:22:12 ------------------------------------------------------------
16+
| Found: () ?->{cap2} I^?
17+
| Required: () ?->{cap1} I
18+
|
19+
| longer explanation available when compiling with `-explain`
20+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:22:5 ----------------------------------------
1921
22 | h2(() => g())() // error
20-
| ^^^
21-
| reference (cap2 : Cap) is not included in the allowed capture set {cap1}
22-
| of an enclosing function literal with expected type () ->{cap1} I
22+
| ^^^^^^^^^
23+
| Found: () ->{cap2} I^?
24+
| Required: () ->{cap1} I
25+
|
26+
| longer explanation available when compiling with `-explain`

tests/neg-custom-args/captures/capt1.check

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
-- Error: tests/neg-custom-args/captures/capt1.scala:5:11 --------------------------------------------------------------
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:5:2 ------------------------------------------
22
5 | () => if x == null then y else y // error
3-
| ^
4-
| reference (x : C^) is not included in the allowed capture set {}
5-
| of an enclosing function literal with expected type () -> C
6-
-- Error: tests/neg-custom-args/captures/capt1.scala:8:11 --------------------------------------------------------------
3+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
| Found: () ->{x} C^?
5+
| Required: () -> C
6+
|
7+
| longer explanation available when compiling with `-explain`
8+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:8:2 ------------------------------------------
79
8 | () => if x == null then y else y // error
8-
| ^
9-
| reference (x : C^) is not included in the allowed capture set {}
10-
| of an enclosing function literal with expected type Matchable
10+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11+
| Found: () ->{x} C^?
12+
| Required: Matchable
13+
|
14+
| longer explanation available when compiling with `-explain`
1115
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:15:2 -----------------------------------------
1216
15 | def f(y: Int) = if x == null then y else y // error
1317
| ^
@@ -38,11 +42,13 @@
3842
| ^^^^^^^^^
3943
| Type variable X of method h cannot be instantiated to () -> box C^ since
4044
| the part box C^ of that type captures the root capability `cap`.
41-
-- Error: tests/neg-custom-args/captures/capt1.scala:36:30 -------------------------------------------------------------
45+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:36:24 ----------------------------------------
4246
36 | val z2 = h[() -> Cap](() => x) // error // error
43-
| ^
44-
| reference (x : C^) is not included in the allowed capture set {}
45-
| of an enclosing function literal with expected type () -> box C^
47+
| ^^^^^^^
48+
| Found: () ->{x} box C^{x}
49+
| Required: () -> box C^
50+
|
51+
| longer explanation available when compiling with `-explain`
4652
-- Error: tests/neg-custom-args/captures/capt1.scala:38:13 -------------------------------------------------------------
4753
38 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error
4854
| ^^^^^^^^^^^^^^^^^^^^^^^
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
-- Error: tests/neg-custom-args/captures/dcs-tvar.scala:6:15 -----------------------------------------------------------
22
6 | () => runOps(xs) // error
33
| ^^
4-
| reference xs* is not included in the allowed capture set {}
5-
| of an enclosing function literal with expected type () -> Unit
4+
| Local reach capability xs* leaks into capture scope of method f.
5+
| To allow this, the parameter xs should be declared with a @use annotation
66
-- Error: tests/neg-custom-args/captures/dcs-tvar.scala:9:15 -----------------------------------------------------------
77
9 | () => runOps(xs) // error
88
| ^^
9-
| reference xs* is not included in the allowed capture set {}
10-
| of an enclosing function literal with expected type () -> Unit
9+
| Local reach capability xs* leaks into capture scope of method g.
10+
| To allow this, the parameter xs should be declared with a @use annotation

tests/neg-custom-args/captures/delayedRunops.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
-- Error: tests/neg-custom-args/captures/delayedRunops.scala:17:13 -----------------------------------------------------
22
17 | runOps(ops1) // error
33
| ^^^^
4-
| reference ops* is not included in the allowed capture set {}
5-
| of an enclosing function literal with expected type () -> Unit
4+
| Local reach capability ops* leaks into capture scope of method delayedRunOps1.
5+
| To allow this, the parameter ops should be declared with a @use annotation
66
-- Error: tests/neg-custom-args/captures/delayedRunops.scala:29:13 -----------------------------------------------------
77
29 | runOps(ops1) // error
88
| ^^^^
9-
| reference ops* is not included in the allowed capture set {}
10-
| of an enclosing function literal with expected type () -> Unit
9+
| Local reach capability ops* leaks into capture scope of method delayedRunOps3.
10+
| To allow this, the parameter ops should be declared with a @use annotation
1111
-- Error: tests/neg-custom-args/captures/delayedRunops.scala:22:16 -----------------------------------------------------
1212
22 | val ops1: List[() => Unit] = ops // error
1313
| ^^^^^^^^^^^^^^^^

tests/neg-custom-args/captures/depfun-reach.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:12:27 ---------------------------------
2+
12 | List(() => op.foreach((f,g) => { f(); g() })) // error (???)
3+
| ^^^^^^^^^^^^^^^^^^^^
4+
| Found: (x$1: (box () ->? Unit, box () ->? Unit)^?) ->? Unit
5+
| Required: (x$1: (box () ->{op*} Unit, box () ->{op*} Unit)) => Unit
6+
|
7+
| longer explanation available when compiling with `-explain`
18
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:19:4 ----------------------------------
29
19 | op // error
310
| ^^

0 commit comments

Comments
 (0)