Skip to content

Commit a98903c

Browse files
committed
Treat by-name closures specially in recheck
A by-name Closure node, which is produced by phase ElimByName gets a target type to indicate it's a contextual zero parameter closure. But for the purposes of rechecking and capture checking, it needs to be treated like a function. In particular the type of the closure needs to be derived from the result type of the anonymous function. Fixes #21920
1 parent ff73a2f commit a98903c

File tree

6 files changed

+62
-21
lines changed

6 files changed

+62
-21
lines changed

compiler/src/dotty/tools/dotc/transform/ElimByName.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ class ElimByName extends MiniPhase, InfoTransformer:
103103
Closure(meth,
104104
_ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase),
105105
targetType = defn.ByNameFunction(argType)
106+
// Note: this will forget any captures on the original by-name type
107+
// But that's not a problem since we treat these closures specially
108+
// anyway during recheck.
106109
).withSpan(arg.span)
107110

108111
private def isByNameRef(tree: Tree)(using Context): Boolean =

compiler/src/dotty/tools/dotc/transform/Recheck.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,10 @@ abstract class Recheck extends Phase, SymTransformer:
387387
def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean = false)(using Context): Type =
388388
if tree.tpt.isEmpty then
389389
tree.meth.tpe.widen.toFunctionType(tree.meth.symbol.is(JavaDefined), alwaysDependent = forceDependent)
390+
else if defn.isByNameFunction(tree.tpt.tpe) then
391+
val mt @ MethodType(Nil) = tree.meth.tpe.widen: @unchecked
392+
val cmt = ContextualMethodType(Nil, Nil, mt.resultType)
393+
cmt.toFunctionType(alwaysDependent = forceDependent)
390394
else
391395
recheck(tree.tpt)
392396

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:10:6 ----------------------------------------
2+
10 | h(f2()) // error
3+
| ^^^^
4+
| Found: (x$0: Int) ->{cap1} Int
5+
| Required: (x$0: Int) ->? Int
6+
|
7+
| longer explanation available when compiling with `-explain`
18
-- Error: tests/neg-custom-args/captures/byname.scala:19:5 -------------------------------------------------------------
29
19 | h(g()) // error
310
| ^^^
@@ -8,22 +15,3 @@
815
| ^^^
916
| reference (cap2 : Cap^) is not included in the allowed capture set {cap1}
1017
| of an enclosing function literal with expected type () ->{cap1} I
11-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:4:2 -----------------------------------------
12-
4 | def f() = if cap1 == cap1 then g else g // error
13-
| ^
14-
| Found: ((x$0: Int) ->{cap2} Int)^{}
15-
| Required: Int -> Int
16-
|
17-
| Note that the expected type Int ->{} Int
18-
| is the previously inferred result type of method test
19-
| which is also the type seen in separately compiled sources.
20-
| The new inferred type ((x$0: Int) ->{cap2} Int)^{}
21-
| must conform to this type.
22-
5 | def g(x: Int) = if cap2 == cap2 then 1 else x
23-
6 | def g2(x: Int) = if cap1 == cap1 then 1 else x
24-
7 | def f2() = if cap1 == cap1 then g2 else g2
25-
8 | def h(ff: => Int ->{cap2} Int) = ff
26-
9 | h(f())
27-
10 | h(f2())
28-
|
29-
| longer explanation available when compiling with `-explain`

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
class Cap extends caps.Capability
22

33
def test(cap1: Cap, cap2: Cap) =
4-
def f() = if cap1 == cap1 then g else g // error
4+
def f() = if cap1 == cap1 then g else g
55
def g(x: Int) = if cap2 == cap2 then 1 else x
66
def g2(x: Int) = if cap1 == cap1 then 1 else x
77
def f2() = if cap1 == cap1 then g2 else g2
88
def h(ff: => Int ->{cap2} Int) = ff
99
h(f())
10-
h(f2())
10+
h(f2()) // error
1111

1212
class I
1313

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21920.scala:34:34 ---------------------------------------
2+
34 | val cell: Cell[File] = File.open(f => Cell(Seq(f))) // error
3+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
| Found: Cell[box File^{f, f²}]{val head: () ?->? IterableOnce[box File^{f, f²}]^?}^?
5+
| Required: Cell[File]
6+
|
7+
| where: f is a reference to a value parameter
8+
| f² is a reference to a value parameter
9+
|
10+
| longer explanation available when compiling with `-explain`
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import language.experimental.captureChecking
2+
3+
trait Iterator[+A] extends IterableOnce[A]:
4+
self: Iterator[A]^ =>
5+
def next(): A
6+
7+
trait IterableOnce[+A] extends Any:
8+
def iterator: Iterator[A]^{this}
9+
10+
final class Cell[A](head: => IterableOnce[A]^):
11+
def headIterator: Iterator[A]^{this} = head.iterator
12+
13+
class File private ():
14+
private var closed = false
15+
16+
def close() = closed = true
17+
18+
def read() =
19+
assert(!closed, "File closed")
20+
1
21+
22+
object File:
23+
def open[T](f: File^ => T): T =
24+
val file = File()
25+
try
26+
f(file)
27+
finally
28+
file.close()
29+
30+
object Seq:
31+
def apply[A](xs: A*): IterableOnce[A] = ???
32+
33+
@main def Main() =
34+
val cell: Cell[File] = File.open(f => Cell(Seq(f))) // error
35+
val file = cell.headIterator.next()
36+
file.read()

0 commit comments

Comments
 (0)