From e3c91f35614ba032fd24114c6e91248858620d2e Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 11 Nov 2024 13:20:44 +0100 Subject: [PATCH] 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 --- .../tools/dotc/transform/ElimByName.scala | 3 ++ .../dotty/tools/dotc/transform/Recheck.scala | 4 +++ tests/neg-custom-args/captures/byname.check | 26 ++++---------- tests/neg-custom-args/captures/byname.scala | 4 +-- tests/neg-custom-args/captures/i21920.check | 10 ++++++ tests/neg-custom-args/captures/i21920.scala | 36 +++++++++++++++++++ 6 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 tests/neg-custom-args/captures/i21920.check create mode 100644 tests/neg-custom-args/captures/i21920.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala index eca3928569f1..0bf2dc107ce9 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala @@ -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 = diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 33f8eeb42eff..9c9b71785698 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -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) diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index c9530f6aad50..1c113591922d 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -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 | ^^^ @@ -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` diff --git a/tests/neg-custom-args/captures/byname.scala b/tests/neg-custom-args/captures/byname.scala index 75ad527dbd2d..dd8fcf1b8818 100644 --- a/tests/neg-custom-args/captures/byname.scala +++ b/tests/neg-custom-args/captures/byname.scala @@ -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 diff --git a/tests/neg-custom-args/captures/i21920.check b/tests/neg-custom-args/captures/i21920.check new file mode 100644 index 000000000000..8efa24426d01 --- /dev/null +++ b/tests/neg-custom-args/captures/i21920.check @@ -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` diff --git a/tests/neg-custom-args/captures/i21920.scala b/tests/neg-custom-args/captures/i21920.scala new file mode 100644 index 000000000000..7ea5a63969b1 --- /dev/null +++ b/tests/neg-custom-args/captures/i21920.scala @@ -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()