Skip to content

Commit ec60015

Browse files
authored
Fix apply rule (#24273)
Don't perform the apply rule optimization under boxing This was already the case for the select rule optimization in recheckSelection but was missing for the corresponding rule for applications.
2 parents 6f07a5d + 7bf957d commit ec60015

File tree

12 files changed

+139
-10
lines changed

12 files changed

+139
-10
lines changed

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
523523
def rootDot(name: Name)(implicit src: SourceFile): Select = Select(Ident(nme.ROOTPKG), name)
524524
def scalaDot(name: Name)(implicit src: SourceFile): Select = Select(rootDot(nme.scala), name)
525525
def scalaAnnotationDot(name: Name)(using SourceFile): Select = Select(scalaDot(nme.annotation), name)
526+
def scalaAnnotationInternalDot(name: Name)(using SourceFile): Select = Select(scalaAnnotationDot(nme.internal), name)
526527
def scalaRuntimeDot(name: Name)(using SourceFile): Select = Select(scalaDot(nme.runtime), name)
528+
def scalaCapsDot(name: Name)(using SourceFile): Select = Select(scalaDot(nme.caps), name)
529+
def scalaCapsInternalDot(name: Name)(using SourceFile): Select = Select(scalaCapsDot(nme.internal), name)
527530
def scalaUnit(implicit src: SourceFile): Select = scalaDot(tpnme.Unit)
528531
def scalaAny(implicit src: SourceFile): Select = scalaDot(tpnme.Any)
529532

@@ -553,16 +556,16 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
553556
Annotated(parent, annot)
554557

555558
def makeReachAnnot()(using Context): Tree =
556-
New(ref(defn.ReachCapabilityAnnot.typeRef), Nil :: Nil)
559+
New(scalaAnnotationInternalDot(tpnme.reachCapability), Nil :: Nil)
557560

558561
def makeReadOnlyAnnot()(using Context): Tree =
559-
New(ref(defn.ReadOnlyCapabilityAnnot.typeRef), Nil :: Nil)
562+
New(scalaAnnotationInternalDot(tpnme.readOnlyCapability), Nil :: Nil)
560563

561564
def makeOnlyAnnot(qid: Tree)(using Context) =
562-
New(AppliedTypeTree(ref(defn.OnlyCapabilityAnnot.typeRef), qid :: Nil), Nil :: Nil)
565+
New(AppliedTypeTree(scalaAnnotationInternalDot(tpnme.onlyCapability), qid :: Nil), Nil :: Nil)
563566

564567
def makeConsumeAnnot()(using Context): Tree =
565-
New(ref(defn.ConsumeAnnot.typeRef), Nil :: Nil)
568+
New(scalaCapsInternalDot(tpnme.consume), Nil :: Nil)
566569

567570
def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef =
568571
DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs)

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ class CCState:
8787

8888
private var collapseFresh: Boolean = false
8989

90+
private var discardUses: Boolean = false
91+
9092
object CCState:
9193

9294
/** If we are currently in capture checking or setup, and `mt` is a method
@@ -137,4 +139,16 @@ object CCState:
137139
/** Should all FreshCap instances be treated as equal to GlobalCap? */
138140
def collapseFresh(using Context): Boolean = ccState.collapseFresh
139141

142+
/** Run `op` but suppress all recording of uses in `markFree` */
143+
inline def withDiscardedUses[T](op: => T)(using Context): T =
144+
if isCaptureCheckingOrSetup then
145+
val ccs = ccState
146+
val saved = ccs.discardUses
147+
ccs.discardUses = true
148+
try op finally ccs.discardUses = saved
149+
else op
150+
151+
/** Should uses not be recorded in markFree? */
152+
def discardUses(using Context): Boolean = ccState.discardUses
153+
140154
end CCState

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ object CheckCaptures:
130130
|A classifier class is a class extending `caps.Capability` and directly extending `caps.Classifier`.""",
131131
ann.srcPos)
132132
check(ref)
133-
case tpe =>
134-
report.error(em"$elem: $tpe is not a legal element of a capture set", ann.srcPos)
133+
case elem =>
134+
report.error(em"$elem is not a legal element of a capture set", ann.srcPos)
135135
ann.retainedSet.retainedElementsRaw.foreach(check)
136136

137137
/** Disallow bad roots anywhere in type `tp``.
@@ -574,7 +574,7 @@ class CheckCaptures extends Recheck, SymTransformer:
574574
// Under deferredReaches, don't propagate out of methods inside terms.
575575
// The use set of these methods will be charged when that method is called.
576576

577-
if !cs.isAlwaysEmpty then
577+
if !cs.isAlwaysEmpty && !CCState.discardUses then
578578
recur(cs, curEnv, null)
579579
if addUseInfo then useInfos += ((tree, cs, curEnv))
580580
end markFree
@@ -783,6 +783,9 @@ class CheckCaptures extends Recheck, SymTransformer:
783783
else argType0.widen.stripCapturing
784784
capt.println(i"rechecking unsafeAssumePure of $arg with $pt: $argType")
785785
super.recheckFinish(argType, tree, pt)
786+
else if meth == defn.Caps_unsafeDiscardUses then
787+
val arg :: Nil = tree.args: @unchecked
788+
withDiscardedUses(recheck(arg, pt))
786789
else
787790
val res = super.recheckApply(tree, pt)
788791
includeCallCaptures(meth, res, tree)
@@ -837,6 +840,8 @@ class CheckCaptures extends Recheck, SymTransformer:
837840
appType match
838841
case appType @ CapturingType(appType1, refs)
839842
if qualType.exists
843+
&& !qualType.isBoxedCapturing
844+
&& !resultType.isBoxedCapturing
840845
&& !tree.fun.symbol.isConstructor
841846
&& !resultType.captureSet.containsResultCapability
842847
&& qualCaptures.mightSubcapture(refs)

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,7 @@ class Definitions {
10261026
@tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe")
10271027
@tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure")
10281028
@tu lazy val Caps_unsafeAssumeSeparate: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumeSeparate")
1029+
@tu lazy val Caps_unsafeDiscardUses: Symbol = CapsUnsafeModule.requiredMethod("unsafeDiscardUses")
10291030
@tu lazy val Caps_unsafeErasedValue: Symbol = CapsUnsafeModule.requiredMethod("unsafeErasedValue")
10301031
@tu lazy val Caps_ContainsTrait: TypeSymbol = CapsModule.requiredType("Contains")
10311032
@tu lazy val Caps_ContainsModule: Symbol = requiredModule("scala.caps.Contains")

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ object StdNames {
573573
val ofDim: N = "ofDim"
574574
val on: N = "on"
575575
val only: N = "only"
576+
val onlyCapability: N = "onlyCapability"
576577
val opaque: N = "opaque"
577578
val open: N = "open"
578579
val ordinal: N = "ordinal"
@@ -591,6 +592,8 @@ object StdNames {
591592
val productPrefix: N = "productPrefix"
592593
val quotes : N = "quotes"
593594
val raw_ : N = "raw"
595+
val reachCapability: N = "reachCapability"
596+
val readOnlyCapability: N = "readOnlyCapability"
594597
val rd: N = "rd"
595598
val refl: N = "refl"
596599
val reflect: N = "reflect"

library/src/scala/caps/package.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ object unsafe:
208208
*/
209209
def unsafeAssumeSeparate(op: Any): op.type = op
210210

211+
/** A wrapper around code for which uses go unrecorded */
212+
def unsafeDiscardUses(op: Any): op.type = op
213+
211214
/** An unsafe variant of erasedValue that can be used as an escape hatch. Unlike the
212215
* user-accessible compiletime.erasedValue, this version is assumed
213216
* to be a pure expression, hence capability safe. But there is no proof

library/src/scala/collection/immutable/LazyListIterable.scala

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,13 +1264,26 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
12641264
if (it.hasNext) eagerCons(it.next(), newLL(eagerHeadFromIterator(it)))
12651265
else Empty
12661266

1267+
// TODO This should be (xss: (collection.Iterable[A]^)*)
12671268
override def concat[A](xss: collection.Iterable[A]*): LazyListIterable[A] =
12681269
if (xss.knownSize == 0) empty
12691270
else newLL(eagerHeadConcatIterators(xss.iterator))
12701271

1271-
private def eagerHeadConcatIterators[A](it: Iterator[collection.Iterable[A]^]^): LazyListIterable[A]^{it} =
1272-
if (!it.hasNext) Empty
1273-
else eagerHeadPrependIterator(it.next().iterator)(eagerHeadConcatIterators(it))
1272+
/* TODO This should be:
1273+
private def eagerHeadConcatIterators[A](it: Iterator[collection.Iterable[A]^]^): LazyListIterable[A]^{it*} =
1274+
if !it.hasNext then Empty
1275+
else
1276+
eagerHeadPrependIterator
1277+
(caps.unsafe.unsafeDiscardUses(it.next()).iterator)
1278+
(eagerHeadConcatIterators(it))
1279+
*/
1280+
1281+
private def eagerHeadConcatIterators[A](it: Iterator[collection.Iterable[A]]^): LazyListIterable[A]^{it} =
1282+
if !it.hasNext then Empty
1283+
else
1284+
eagerHeadPrependIterator
1285+
(it.next().iterator)
1286+
(eagerHeadConcatIterators(it))
12741287

12751288
/** An infinite LazyListIterable that repeatedly applies a given function to a start value.
12761289
*
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/apply-rule.scala:3:19 ------------------------------------
2+
3 | val _: () -> A = x // error
3+
| ^
4+
| Found: (x : () ->{s*} A^{})
5+
| Required: () -> A
6+
|
7+
| Note that capability s* is not included in capture set {}.
8+
|
9+
| longer explanation available when compiling with `-explain`
10+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/apply-rule.scala:5:19 ------------------------------------
11+
5 | val _: () -> A = y // error
12+
| ^
13+
| Found: (y : () ->{s*} A^{})
14+
| Required: () -> A
15+
|
16+
| Note that capability s* is not included in capture set {}.
17+
|
18+
| longer explanation available when compiling with `-explain`
19+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/apply-rule.scala:9:19 ------------------------------------
20+
9 | val _: () -> A = x // error
21+
| ^
22+
| Found: (x : () ->{C} A^{})
23+
| Required: () -> A
24+
|
25+
| Note that capability C is not included in capture set {}.
26+
|
27+
| longer explanation available when compiling with `-explain`
28+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/apply-rule.scala:11:19 -----------------------------------
29+
11 | val _: () -> A = y // error
30+
| ^
31+
| Found: (y : () ->{C} A^{})
32+
| Required: () -> A
33+
|
34+
| Note that capability C is not included in capture set {}.
35+
|
36+
| longer explanation available when compiling with `-explain`
37+
-- Error: tests/neg-custom-args/captures/apply-rule.scala:2:11 ---------------------------------------------------------
38+
2 | val x = s(0) // error
39+
| ^^^^
40+
| Local reach capability s* leaks into capture scope of method select.
41+
| You could try to abstract the capabilities referred to by s* in a capset variable.
42+
-- Error: tests/neg-custom-args/captures/apply-rule.scala:4:12 ---------------------------------------------------------
43+
4 | val y = s.head // error
44+
| ^^^^^^
45+
| Local reach capability s* leaks into capture scope of method select.
46+
| You could try to abstract the capabilities referred to by s* in a capset variable.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
def select[A](s: Seq[() => A]) =
2+
val x = s(0) // error
3+
val _: () -> A = x // error
4+
val y = s.head // error
5+
val _: () -> A = y // error
6+
7+
def select2[A, C^](s: Seq[() ->{C} A]) =
8+
val x = s(0)
9+
val _: () -> A = x // error
10+
val y = s.head
11+
val _: () -> A = y // error
12+
13+
14+
15+
16+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/nicolas1.scala:10:2 --------------------------------------
2+
10 | val all: Seq[Rand ?->{head, tail*} A] = head +: tail // error
3+
| ^
4+
| Found: (contextual$1: Rand^'s1) ?->{head, tail*} A^'s2
5+
| Required: (Rand^) ?->{head} A
6+
|
7+
| Note that capability tail* is not included in capture set {head}.
8+
|
9+
| where: ^ refers to the universal root capability
10+
11 | all(nextInt(all.length))
11+
|
12+
| longer explanation available when compiling with `-explain`

0 commit comments

Comments
 (0)