Skip to content

Commit

Permalink
Treat by-name closures specially in recheck
Browse files Browse the repository at this point in the history
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
  • Loading branch information
odersky committed Nov 11, 2024
1 parent 4c8bd42 commit e3c91f3
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 21 deletions.
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/ElimByName.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ class ElimByName extends MiniPhase, InfoTransformer:
Closure(meth,
_ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase),
targetType = defn.ByNameFunction(argType)
// Note: this will forget any captures on the original by-name type
// But that's not a problem since we treat these closures specially
// anyway during recheck.
).withSpan(arg.span)

private def isByNameRef(tree: Tree)(using Context): Boolean =
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/Recheck.scala
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,10 @@ abstract class Recheck extends Phase, SymTransformer:
def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean = false)(using Context): Type =
if tree.tpt.isEmpty then
tree.meth.tpe.widen.toFunctionType(tree.meth.symbol.is(JavaDefined), alwaysDependent = forceDependent)
else if defn.isByNameFunction(tree.tpt.tpe) then
val mt @ MethodType(Nil) = tree.meth.tpe.widen: @unchecked
val cmt = ContextualMethodType(Nil, Nil, mt.resultType)
cmt.toFunctionType(alwaysDependent = forceDependent)
else
recheck(tree.tpt)

Expand Down
26 changes: 7 additions & 19 deletions tests/neg-custom-args/captures/byname.check
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:10:6 ----------------------------------------
10 | h(f2()) // error
| ^^^^
| Found: (x$0: Int) ->{cap1} Int
| Required: (x$0: Int) ->? Int
|
| longer explanation available when compiling with `-explain`
-- Error: tests/neg-custom-args/captures/byname.scala:19:5 -------------------------------------------------------------
19 | h(g()) // error
| ^^^
Expand All @@ -8,22 +15,3 @@
| ^^^
| reference (cap2 : Cap^) is not included in the allowed capture set {cap1}
| of an enclosing function literal with expected type () ->{cap1} I
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:4:2 -----------------------------------------
4 | def f() = if cap1 == cap1 then g else g // error
| ^
| Found: ((x$0: Int) ->{cap2} Int)^{}
| Required: Int -> Int
|
| Note that the expected type Int ->{} Int
| is the previously inferred result type of method test
| which is also the type seen in separately compiled sources.
| The new inferred type ((x$0: Int) ->{cap2} Int)^{}
| must conform to this type.
5 | def g(x: Int) = if cap2 == cap2 then 1 else x
6 | def g2(x: Int) = if cap1 == cap1 then 1 else x
7 | def f2() = if cap1 == cap1 then g2 else g2
8 | def h(ff: => Int ->{cap2} Int) = ff
9 | h(f())
10 | h(f2())
|
| longer explanation available when compiling with `-explain`
4 changes: 2 additions & 2 deletions tests/neg-custom-args/captures/byname.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
class Cap extends caps.Capability

def test(cap1: Cap, cap2: Cap) =
def f() = if cap1 == cap1 then g else g // error
def f() = if cap1 == cap1 then g else g
def g(x: Int) = if cap2 == cap2 then 1 else x
def g2(x: Int) = if cap1 == cap1 then 1 else x
def f2() = if cap1 == cap1 then g2 else g2
def h(ff: => Int ->{cap2} Int) = ff
h(f())
h(f2())
h(f2()) // error

class I

Expand Down
10 changes: 10 additions & 0 deletions tests/neg-custom-args/captures/i21920.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21920.scala:34:34 ---------------------------------------
34 | val cell: Cell[File] = File.open(f => Cell(Seq(f))) // error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| Found: Cell[box File^{f, f²}]{val head: () ?->? IterableOnce[box File^{f, f²}]^?}^?
| Required: Cell[File]
|
| where: f is a reference to a value parameter
| f² is a reference to a value parameter
|
| longer explanation available when compiling with `-explain`
36 changes: 36 additions & 0 deletions tests/neg-custom-args/captures/i21920.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import language.experimental.captureChecking

trait Iterator[+A] extends IterableOnce[A]:
self: Iterator[A]^ =>
def next(): A

trait IterableOnce[+A] extends Any:
def iterator: Iterator[A]^{this}

final class Cell[A](head: => IterableOnce[A]^):
def headIterator: Iterator[A]^{this} = head.iterator

class File private ():
private var closed = false

def close() = closed = true

def read() =
assert(!closed, "File closed")
1

object File:
def open[T](f: File^ => T): T =
val file = File()
try
f(file)
finally
file.close()

object Seq:
def apply[A](xs: A*): IterableOnce[A] = ???

@main def Main() =
val cell: Cell[File] = File.open(f => Cell(Seq(f))) // error
val file = cell.headIterator.next()
file.read()

0 comments on commit e3c91f3

Please sign in to comment.