Skip to content

Commit 30971b5

Browse files
committed
Open existential scopes only for (x: T) => U functions
Parametric functions `A -> B` or `A => B` do not open an existential scope anymore. Hence, a function of the form ```scala def f(x: A => B^) ``` is capture polymorphic in both the function effect and the function result effect. On the other hand, ```scala def f(x: (y: A) => B^) ``` would be a function that can be passed only arguments that return a fresh result on each call.
1 parent 0f0a5b3 commit 30971b5

25 files changed

+175
-81
lines changed

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -540,11 +540,13 @@ extension (tp: Type)
540540
case t @ AnnotatedType(parent, ann) =>
541541
// Don't map annotations, which includes capture sets
542542
t.derivedAnnotatedType(this(parent), ann)
543-
case t @ FunctionOrMethod(args, res)
544-
if args.forall(_.isAlwaysPure) =>
545-
// Also map existentials in results to reach capabilities if all
546-
// preceding arguments are known to be always pure
547-
t.derivedFunctionOrMethod(args, apply(Existential.toCap(res)))
543+
case t @ FunctionOrMethod(args, res) =>
544+
if args.forall(_.isAlwaysPure) then
545+
// Also map existentials in results to reach capabilities if all
546+
// preceding arguments are known to be always pure
547+
t.derivedFunctionOrMethod(args, apply(Existential.toCap(res)))
548+
else
549+
t
548550
case _ =>
549551
mapOver(t)
550552
end narrowCaps

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,18 +252,23 @@ trait CaptureRef extends TypeProxy, ValueType:
252252
* fail a comparison.
253253
*/
254254
def maxSubsumes(y: CaptureRef, canAddHidden: Boolean)(using ctx: Context, vs: VarState = VarState.Separate): Boolean =
255+
def yIsExistential = y.stripReadOnly match
256+
case Existential.Vble(_) =>
257+
capt.println(i"failed existential $this >: $y")
258+
true
259+
case _ => false
255260
(this eq y)
256261
|| this.match
257262
case Fresh(hidden) =>
258263
vs.ifNotSeen(this)(hidden.elems.exists(_.subsumes(y)))
259-
|| !y.stripReadOnly.isCap && canAddHidden && vs.addHidden(hidden, y)
264+
|| !y.stripReadOnly.isCap && !yIsExistential && canAddHidden && vs.addHidden(hidden, y)
260265
case Existential.Vble(binder) =>
261266
y.stripReadOnly match
262267
case Existential.Vble(binder1) => binder1.hasSuffix(binder)
263268
.showing(i"cmp existential $binder maxSubsumes $binder1 = $result", capt)
264269
case _ => true
265270
case _ =>
266-
this.isCap && canAddHidden && vs != VarState.HardSeparate
271+
this.isCap /*&& !yIsExistential*/ && canAddHidden && vs != VarState.HardSeparate
267272
|| y.match
268273
case ReadOnlyCapability(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden)
269274
case _ => false

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1260,7 +1260,7 @@ class CheckCaptures extends Recheck, SymTransformer:
12601260
if isCompatible(actualBoxed, expected1) then
12611261
if debugSuccesses then tree match
12621262
case Ident(_) =>
1263-
println(i"SUCCESS $tree:\n${TypeComparer.explained(_.isSubType(actual, expected))}")
1263+
println(i"SUCCESS $tree for $actual <:< $expected:\n${TypeComparer.explained(_.isSubType(actualBoxed, expected1))}")
12641264
case _ =>
12651265
actualBoxed
12661266
else

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,8 @@ object Existential:
281281
seen.getOrElseUpdate(t, Vble(mt))
282282
else
283283
if variance == 0 then
284-
fail(em"""$tp captures the root capability `cap` in invariant position""")
284+
fail(em"""$tp captures the root capability `cap` in invariant position.
285+
|This capability cannot be converted to an existential in the result type of a function.""")
285286
// we accept variance < 0, and leave the cap as it is
286287
super.mapOver(t)
287288
case defn.FunctionNOf(args, res, contextual) if t.typeSymbol.name.isImpureFunction =>

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
403403
&& (!sym.isConstructor || (t ne tp.finalResultType))
404404
// Don't add ^ to result types of class constructors deriving from Capability
405405
then CapturingType(t, defn.universalCSImpliedByCapability, boxed = false)
406-
else normalizeCaptures(normalizeFunctions(mapFollowingAliases(t), t))
406+
else normalizeCaptures(mapFollowingAliases(t))
407407

408408
def innerApply(t: Type) =
409409
t match
@@ -438,9 +438,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
438438
keptFunAliases = true
439439
mapOver(t)
440440
else
441-
// In the second pass, map the alias and make sure it has the form
442-
// of a dependent function.
443-
normalizeFunctions(apply(t.dealias), t, expandAlways = true)
441+
// In the second pass, map the alias
442+
apply(t.dealias)
444443
case t =>
445444
defaultApply(t)
446445
end toCapturing

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,10 +321,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
321321
// This is safe because X$ self-type is X.type
322322
sym1 = sym1.companionModule
323323
if (sym1 ne NoSymbol) && (sym1 eq sym2) then
324-
ctx.erasedTypes ||
325-
sym1.isStaticOwner ||
326-
isSubPrefix(tp1.prefix, tp2.prefix) ||
327-
thirdTryNamed(tp2)
324+
ctx.erasedTypes
325+
|| sym1.isStaticOwner
326+
|| isSubPrefix(tp1.prefix, tp2.prefix)
327+
|| thirdTryNamed(tp2)
328328
else
329329
(tp1.name eq tp2.name)
330330
&& !sym1.is(Private)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1124,7 +1124,7 @@ object Types extends TypeUtils {
11241124
TypeComparer.topLevelSubType(this, that)
11251125
}
11261126

1127-
/** Is this type a subtype of that type? */
1127+
/** Is this type a subtype of that type without adding to the constraint? */
11281128
final def frozen_<:<(that: Type)(using Context): Boolean = {
11291129
record("frozen_<:<")
11301130
TypeComparer.isSubTypeWhenFrozen(this, that)

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
260260
def isUniversal =
261261
refs.elems.size == 1
262262
&& (refs.isUniversal
263-
|| !printDebug && !showUniqueIds && refs.elems.nth(0).match
263+
|| !printDebug && !printFresh && !showUniqueIds && refs.elems.nth(0).match
264264
case Existential.Vble(binder) =>
265265
CCState.openExistentialScopes match
266266
case b :: _ => binder eq b
@@ -459,7 +459,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
459459
val vbleText: Text = CCState.openExistentialScopes.indexOf(binder) match
460460
case -1 =>
461461
"<cap of " ~ toText(binder) ~ ">"
462-
case n => "outer_" * n ++ "cap"
462+
case n => "outer_" * n ++ (if printFresh then "localcap" else "cap")
463463
vbleText ~ hashStr(binder)
464464
case Fresh(hidden) =>
465465
val idStr = if showUniqueIds then s"#${hidden.id}" else ""

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
190190
val capturesRoot = refs == rootSetText
191191
if cc.isCaptureCheckingOrSetup
192192
&& tp.allParamNamesSynthetic && !tp.looksDependent
193-
&& !showUniqueIds && !printDebug
194-
then
193+
&& !showUniqueIds && !printDebug && !printFresh
194+
then
195195
// cc.Setup converts all functions to dependent functions. Undo that when printing.
196196
toTextFunction(tp.paramInfos, tp.resType, tp, refs.provided(!capturesRoot), isContextual, isPure && !capturesRoot)
197197
else
Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,24 @@
1-
-- Error: tests/neg-custom-args/captures/boundschecks3.scala:11:13 -----------------------------------------------------
2-
11 | val bar: T -> T = ??? // error, since `T` is expanded here. But the msg is not very good.
3-
| ^^^^^^
4-
| test.C[box test.Tree^] captures the root capability `cap` in invariant position
1+
-- [E057] Type Mismatch Error: tests/neg-custom-args/captures/boundschecks3.scala:9:11 ---------------------------------
2+
9 | val foo: C[Tree^] = ??? // error
3+
| ^
4+
| Type argument box test.Tree^ does not conform to upper bound test.Tree in inferred type test.C[box test.Tree^]
5+
|
6+
| longer explanation available when compiling with `-explain`
7+
-- [E057] Type Mismatch Error: tests/neg-custom-args/captures/boundschecks3.scala:10:11 --------------------------------
8+
10 | type T = C[Tree^] // error
9+
| ^
10+
| Type argument box test.Tree^ does not conform to upper bound test.Tree in inferred type test.C[box test.Tree^]
11+
|
12+
| longer explanation available when compiling with `-explain`
13+
-- [E057] Type Mismatch Error: tests/neg-custom-args/captures/boundschecks3.scala:11:11 --------------------------------
14+
11 | val bar: T -> T = ??? // error
15+
| ^
16+
|Type argument box test.Tree^ does not conform to upper bound test.Tree in subpart test.C[box test.Tree^] of inferred type test.C[box test.Tree^] -> test.C[box test.Tree^]
17+
|
18+
| longer explanation available when compiling with `-explain`
19+
-- [E057] Type Mismatch Error: tests/neg-custom-args/captures/boundschecks3.scala:12:11 --------------------------------
20+
12 | val baz: C[Tree^] -> Unit = ??? // error
21+
| ^
22+
|Type argument box test.Tree^ does not conform to upper bound test.Tree in subpart test.C[box test.Tree^] of inferred type test.C[box test.Tree^] -> Unit
23+
|
24+
| longer explanation available when compiling with `-explain`

0 commit comments

Comments
 (0)