Skip to content

Commit ad43c66

Browse files
committed
Revert "Revert "Implement sealed type variables""
This reverts commit 4942724. # Conflicts: # compiler/src/dotty/tools/dotc/cc/Setup.scala # compiler/src/dotty/tools/dotc/core/Definitions.scala # tests/neg-custom-args/captures/heal-tparam-cs.scala # tests/neg-custom-args/captures/usingLogFile-alt.check # tests/neg-custom-args/captures/usingLogFile-alt.scala # tests/pos-special/stdlib/Test1.scala
1 parent 6f9e565 commit ad43c66

File tree

18 files changed

+130
-64
lines changed

18 files changed

+130
-64
lines changed

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

+1-4
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,7 @@ sealed abstract class CaptureSet extends Showable:
8282

8383
/** Does this capture set contain the root reference `cap` as element? */
8484
final def isUniversal(using Context) =
85-
elems.exists {
86-
case ref: TermRef => ref.symbol == defn.captureRoot
87-
case _ => false
88-
}
85+
elems.exists(_.isGenericRootCapability)
8986

9087
/** Does this capture set contain the root reference `cap` as element? */
9188
final def containsRoot(using Context) =

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

+22-1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,21 @@ object CheckCaptures:
140140
case tpe =>
141141
report.error(em"$elem: $tpe is not a legal element of a capture set", elem.srcPos)
142142

143+
/** Report an error if some part of `tp` contains the root capability in its capture set */
144+
def disallowRootCapabilitiesIn(tp: Type, what: String, have: String, addendum: String, pos: SrcPos)(using Context) =
145+
val check = new TypeTraverser:
146+
def traverse(t: Type) =
147+
if variance >= 0 then
148+
t.captureSet.disallowRootCapability: () =>
149+
def part = if t eq tp then "" else i"the part $t of "
150+
report.error(
151+
em"""$what cannot $have $tp since
152+
|${part}that type captures the root capability `cap`.
153+
|$addendum""",
154+
pos)
155+
traverseChildren(t)
156+
check.traverse(tp)
157+
143158
/** Attachment key for bodies of closures, provided they are values */
144159
val ClosureBodyValue = Property.Key[Unit]
145160

@@ -672,11 +687,17 @@ class CheckCaptures extends Recheck, SymTransformer:
672687
val tryOwner = ccState.tryBlockOwner.remove(tree).getOrElse(ctx.owner)
673688
val saved = curEnv
674689
curEnv = Env(tryOwner, EnvKind.Regular, CaptureSet.Var(curEnv.owner), curEnv)
675-
try
690+
val tp = try
676691
inContext(ctx.withOwner(tryOwner)):
677692
super.recheckTry(tree, pt)
678693
finally
679694
curEnv = saved
695+
if allowUniversalInBoxed && Feature.enabled(Feature.saferExceptions) then
696+
disallowRootCapabilitiesIn(tp,
697+
"Result of `try`", "have type",
698+
"This is often caused by a locally generated exception capability leaking as part of its result.",
699+
tree.srcPos)
700+
tp
680701

681702
/* Currently not needed, since capture checking takes place after ElimByName.
682703
* Keep around in case we need to get back to it

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

+10
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,16 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
481481
for case arg: TypeTree <- args do
482482
transformTT(arg, boxed = true, exact = false, rootTarget = ctx.owner) // type arguments in type applications are boxed
483483

484+
if allowUniversalInBoxed then
485+
val polyType = fn.tpe.widen.asInstanceOf[TypeLambda]
486+
for case (arg: TypeTree, pinfo, pname) <- args.lazyZip(polyType.paramInfos).lazyZip((polyType.paramNames)) do
487+
if pinfo.bounds.hi.hasAnnotation(defn.Caps_SealedAnnot) then
488+
def where = if fn.symbol.exists then i" in an argument of ${fn.symbol}" else ""
489+
CheckCaptures.disallowRootCapabilitiesIn(arg.knownType,
490+
i"Sealed type variable $pname", "be instantiated to",
491+
i"This is often caused by a local capability$where\nleaking as part of its result.",
492+
tree.srcPos)
493+
484494
case tree: TypeDef if tree.symbol.isClass =>
485495
inContext(ctx.withOwner(tree.symbol)):
486496
traverseChildren(tree)

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

+2
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,7 @@ class Definitions {
983983
@tu lazy val Caps_unsafeBox: Symbol = CapsUnsafeModule.requiredMethod("unsafeBox")
984984
@tu lazy val Caps_unsafeUnbox: Symbol = CapsUnsafeModule.requiredMethod("unsafeUnbox")
985985
@tu lazy val Caps_unsafeBoxFunArg: Symbol = CapsUnsafeModule.requiredMethod("unsafeBoxFunArg")
986+
@tu lazy val Caps_SealedAnnot: ClassSymbol = requiredClass("scala.caps.Sealed")
986987
@tu lazy val expandedUniversalSet: CaptureSet = CaptureSet(captureRoot.termRef)
987988

988989
@tu lazy val PureClass: Symbol = requiredClass("scala.Pure")
@@ -1034,6 +1035,7 @@ class Definitions {
10341035
@tu lazy val UncheckedAnnot: ClassSymbol = requiredClass("scala.unchecked")
10351036
@tu lazy val UncheckedStableAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedStable")
10361037
@tu lazy val UncheckedVarianceAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedVariance")
1038+
@tu lazy val UncheckedCapturesAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedCaptures")
10371039
@tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile")
10381040
@tu lazy val WithPureFunsAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WithPureFuns")
10391041
@tu lazy val CaptureCheckedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.CaptureChecked")

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -4086,10 +4086,15 @@ object Types {
40864086

40874087
protected def toPInfo(tp: Type)(using Context): PInfo
40884088

4089+
/** If `tparam` is a sealed type parameter symbol of a polymorphic method, add
4090+
* a @caps.Sealed annotation to the upperbound in `tp`.
4091+
*/
4092+
protected def addSealed(tparam: ParamInfo, tp: Type)(using Context): Type = tp
4093+
40894094
def fromParams[PI <: ParamInfo.Of[N]](params: List[PI], resultType: Type)(using Context): Type =
40904095
if (params.isEmpty) resultType
40914096
else apply(params.map(_.paramName))(
4092-
tl => params.map(param => toPInfo(tl.integrate(params, param.paramInfo))),
4097+
tl => params.map(param => toPInfo(addSealed(param, tl.integrate(params, param.paramInfo)))),
40934098
tl => tl.integrate(params, resultType))
40944099
}
40954100

@@ -4411,6 +4416,16 @@ object Types {
44114416
resultTypeExp: PolyType => Type)(using Context): PolyType =
44124417
unique(new PolyType(paramNames)(paramInfosExp, resultTypeExp))
44134418

4419+
override protected def addSealed(tparam: ParamInfo, tp: Type)(using Context): Type =
4420+
tparam match
4421+
case tparam: Symbol if tparam.is(Sealed) =>
4422+
tp match
4423+
case tp @ TypeBounds(lo, hi) =>
4424+
tp.derivedTypeBounds(lo,
4425+
AnnotatedType(hi, Annotation(defn.Caps_SealedAnnot, tparam.span)))
4426+
case _ => tp
4427+
case _ => tp
4428+
44144429
def unapply(tl: PolyType): Some[(List[LambdaParam], Type)] =
44154430
Some((tl.typeParams, tl.resType))
44164431
}

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+17-15
Original file line numberDiff line numberDiff line change
@@ -3194,7 +3194,9 @@ object Parsers {
31943194
* id [HkTypeParamClause] TypeParamBounds
31953195
*
31963196
* DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’
3197-
* DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds
3197+
* DefTypeParam ::= {Annotation}
3198+
* [`sealed`] -- under captureChecking
3199+
* id [HkTypeParamClause] TypeParamBounds
31983200
*
31993201
* TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
32003202
* TypTypeParam ::= {Annotation} id [HkTypePamClause] TypeBounds
@@ -3204,24 +3206,24 @@ object Parsers {
32043206
*/
32053207
def typeParamClause(ownerKind: ParamOwner): List[TypeDef] = inBrackets {
32063208

3207-
def variance(vflag: FlagSet): FlagSet =
3208-
if ownerKind == ParamOwner.Def || ownerKind == ParamOwner.TypeParam then
3209-
syntaxError(em"no `+/-` variance annotation allowed here")
3210-
in.nextToken()
3211-
EmptyFlags
3212-
else
3213-
in.nextToken()
3214-
vflag
3209+
def checkVarianceOK(): Boolean =
3210+
val ok = ownerKind != ParamOwner.Def && ownerKind != ParamOwner.TypeParam
3211+
if !ok then syntaxError(em"no `+/-` variance annotation allowed here")
3212+
in.nextToken()
3213+
ok
32153214

32163215
def typeParam(): TypeDef = {
32173216
val isAbstractOwner = ownerKind == ParamOwner.Type || ownerKind == ParamOwner.TypeParam
32183217
val start = in.offset
3219-
val mods =
3220-
annotsAsMods()
3221-
| (if (ownerKind == ParamOwner.Class) Param | PrivateLocal else Param)
3222-
| (if isIdent(nme.raw.PLUS) then variance(Covariant)
3223-
else if isIdent(nme.raw.MINUS) then variance(Contravariant)
3224-
else EmptyFlags)
3218+
var mods = annotsAsMods() | Param
3219+
if ownerKind == ParamOwner.Class then mods |= PrivateLocal
3220+
if Feature.ccEnabled && in.token == SEALED then
3221+
mods |= Sealed
3222+
in.nextToken()
3223+
if isIdent(nme.raw.PLUS) && checkVarianceOK() then
3224+
mods |= Covariant
3225+
else if isIdent(nme.raw.MINUS) && checkVarianceOK() then
3226+
mods |= Contravariant
32253227
atSpan(start, nameStart) {
32263228
val name =
32273229
if (isAbstractOwner && in.token == USCORE) {

compiler/src/dotty/tools/dotc/typer/Checking.scala

+6-1
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,12 @@ object Checking {
518518
// note: this is not covered by the next test since terms can be abstract (which is a dual-mode flag)
519519
// but they can never be one of ClassOnlyFlags
520520
if !sym.isClass && sym.isOneOf(ClassOnlyFlags) then
521-
fail(em"only classes can be ${(sym.flags & ClassOnlyFlags).flagsString}")
521+
val illegal = sym.flags & ClassOnlyFlags
522+
if sym.is(TypeParam)
523+
&& illegal == Sealed
524+
&& Feature.ccEnabled && cc.allowUniversalInBoxed
525+
then () // OK
526+
else fail(em"only classes can be ${illegal.flagsString}")
522527
if (sym.is(AbsOverride) && !sym.owner.is(Trait))
523528
fail(AbstractOverrideOnlyInTraits(sym))
524529
if sym.is(Trait) then

library/src/scala/caps.scala

+6
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,9 @@ import annotation.experimental
4646
def unsafeBoxFunArg: T => U = f
4747

4848
end unsafe
49+
50+
/** An annotation that expresses the sealed modifier on a type parameter
51+
* Should not be directly referred to in source
52+
*/
53+
@deprecated("The Sealed annotation should not be directly used in source code.\nUse the `sealed` modifier on type parameters instead.")
54+
class Sealed extends annotation.Annotation

tests/neg-custom-args/captures/heal-tparam-cs.scala

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
import language.experimental.captureChecking
22

3-
trait Cap { def use(): Unit }
3+
trait Capp { def use(): Unit }
44

5-
def localCap[T](op: (lcap: caps.Cap) ?-> (c: Cap^{lcap}) => T): T = ???
5+
def localCap[sealed T](op: (c: Capp^{cap}) => T): T = ???
66

7-
def main(io: Cap^{cap}, net: Cap^{cap}): Unit = {
7+
def main(io: Capp^{cap}, net: Capp^{cap}): Unit = {
88

9-
val test1 = localCap { c => // error
9+
val test1 = localCap { c => // should be error
1010
() => { c.use() }
1111
}
1212

13-
val test2: (c: Cap^{cap}) -> () ->{cap} Unit =
14-
localCap { c => // should work (was error)
15-
(c1: Cap^{cap}) => () => { c1.use() }
13+
val test2: (c: Capp^{cap}) -> () ->{cap} Unit =
14+
localCap { c => // should work
15+
(c1: Capp^{cap}) => () => { c1.use() }
1616
}
1717

18-
val test3: (c: Cap^{io}) -> () ->{io} Unit =
18+
val test3: (c: Capp^{io}) -> () ->{io} Unit =
1919
localCap { c => // should work
20-
(c1: Cap^{io}) => () => { c1.use() }
20+
(c1: Capp^{io}) => () => { c1.use() }
2121
}
2222

23-
val test4: (c: Cap^{io}) -> () ->{net} Unit =
23+
val test4: (c: Capp^{io}) -> () ->{net} Unit =
2424
localCap { c => // error
25-
(c1: Cap^{io}) => () => { c1.use() }
25+
(c1: Capp^{io}) => () => { c1.use() }
2626
}
2727

28-
def localCap2[T](op: (c: Cap^{io}) => T): T = ???
28+
def localCap2[sealed T](op: (c: Capp^{io}) => T): T = ???
2929

3030
val test5: () ->{io} Unit =
3131
localCap2 { c => // ok

tests/pos-custom-args/captures/i15749a.scala

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// TODO: Adapt to levels
21
class Unit
32
object u extends Unit
43

@@ -11,12 +10,12 @@ def test =
1110
def wrapper[T](x: T): Wrapper[T] =
1211
[X] => (op: T ->{cap} X) => op(x)
1312

14-
def strictMap[A <: Top, B <: Top](mx: Wrapper[A])(f: A ->{cap} B): Wrapper[B] =
13+
def strictMap[A <: Top, sealed B <: Top](mx: Wrapper[A])(f: A ->{cap} B): Wrapper[B] =
1514
mx((x: A) => wrapper(f(x)))
1615

1716
def force[A](thunk: Unit ->{cap} A): A = thunk(u)
1817

19-
def forceWrapper[A](mx: Wrapper[Unit ->{cap} A]): Wrapper[A] =
18+
def forceWrapper[sealed A](mx: Wrapper[Unit ->{cap} A]): Wrapper[A] =
2019
// Γ ⊢ mx: Wrapper[□ {cap} Unit => A]
2120
// `force` should be typed as ∀(□ {cap} Unit -> A) A, but it can not
2221
strictMap[Unit ->{cap} A, A](mx)(t => force[A](t)) // error

tests/pos-custom-args/captures/vars1.scala

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
import caps.unsafe.*
2+
import annotation.unchecked.uncheckedCaptures
23

34
object Test:
45
type ErrorHandler = (Int, String) => Unit
56

7+
@uncheckedCaptures
68
var defaultIncompleteHandler: ErrorHandler = ???
9+
@uncheckedCaptures
710
var incompleteHandler: ErrorHandler = defaultIncompleteHandler
811
private val x = incompleteHandler.unsafeUnbox
912
val _ : ErrorHandler = x
1013
val _ = x(1, "a")
1114

12-
def defaultIncompleteHandler1(): ErrorHandler = ???
15+
def defaultIncompleteHandler1(): (Int, String) => Unit = ???
1316
val defaultIncompleteHandler2: ErrorHandler = ???
17+
@uncheckedCaptures
1418
var incompleteHandler1: ErrorHandler = defaultIncompleteHandler1()
19+
@uncheckedCaptures
1520
var incompleteHandler2: ErrorHandler = defaultIncompleteHandler2
21+
@uncheckedCaptures
1622
private var incompleteHandler7 = defaultIncompleteHandler1()
23+
@uncheckedCaptures
1724
private var incompleteHandler8 = defaultIncompleteHandler2
1825

1926
incompleteHandler1 = defaultIncompleteHandler2

tests/pos-special/stdlib/Test1.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import java.io.*
66

77
object Test0:
88

9-
def usingLogFile[T](op: (lcap: caps.Cap) ?-> FileOutputStream^ => T): T =
9+
def usingLogFile[sealed T](op: FileOutputStream^ => T): T =
1010
val logFile = FileOutputStream("log")
1111
val result = op(logFile)
1212
logFile.close()

tests/pos-with-compiler-cc/dotc/Run.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import scala.collection.mutable
3232
import scala.util.control.NonFatal
3333
import scala.io.Codec
3434
import annotation.constructorOnly
35-
import caps.unsafe.unsafeUnbox
35+
import annotation.unchecked.uncheckedCaptures
3636

3737
/** A compiler run. Exports various methods to compile source files */
3838
class Run(comp: Compiler, @constructorOnly ictx0: Context) extends ImplicitRunInfo with ConstraintRunInfo {
@@ -165,6 +165,7 @@ class Run(comp: Compiler, @constructorOnly ictx0: Context) extends ImplicitRunIn
165165
val staticRefs = util.EqHashMap[Name, Denotation](initialCapacity = 1024)
166166

167167
/** Actions that need to be performed at the end of the current compilation run */
168+
@uncheckedCaptures
168169
private var finalizeActions = mutable.ListBuffer[() => Unit]()
169170

170171
/** Will be set to true if any of the compiled compilation units contains
@@ -275,7 +276,7 @@ class Run(comp: Compiler, @constructorOnly ictx0: Context) extends ImplicitRunIn
275276
Rewrites.writeBack()
276277
suppressions.runFinished(hasErrors = ctx.reporter.hasErrors)
277278
while (finalizeActions.nonEmpty) {
278-
val action = finalizeActions.remove(0).unsafeUnbox
279+
val action = finalizeActions.remove(0)
279280
action()
280281
}
281282
compiling = false

tests/pos-with-compiler-cc/dotc/core/Scopes.scala

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
/* NSC -- new Scala compiler
2-
* Copyright 2005-2012 LAMP/EPFL
3-
* @author Martin Odersky
4-
*/
5-
61
package dotty.tools
72
package dotc
83
package core
@@ -17,6 +12,7 @@ import Denotations._
1712
import printing.Texts._
1813
import printing.Printer
1914
import SymDenotations.NoDenotation
15+
import annotation.unchecked.uncheckedCaptures
2016

2117
import collection.mutable
2218

@@ -220,6 +216,7 @@ object Scopes {
220216
private var elemsCache: List[Symbol] | Null = null
221217

222218
/** The synthesizer to be used, or `null` if no synthesis is done on this scope */
219+
@uncheckedCaptures
223220
private var synthesize: SymbolSynthesizer | Null = null
224221

225222
/** Use specified synthesize for this scope */

tests/pos-with-compiler-cc/dotc/core/TypeComparer.scala

+6-5
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,26 @@ import reporting.trace
2525
import annotation.constructorOnly
2626
import cc.{CapturingType, derivedCapturingType, CaptureSet, stripCapturing, isBoxedCapturing, boxed, boxedUnlessFun, boxedIfTypeParam, isAlwaysPure}
2727
import language.experimental.pureFunctions
28-
import caps.unsafe.*
28+
import annotation.unchecked.uncheckedCaptures
2929

3030
/** Provides methods to compare types.
3131
*/
3232
class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling, PatternTypeConstrainer {
3333
import TypeComparer._
3434
Stats.record("TypeComparer")
3535

36-
private var myContext: Context = initctx.unsafeBox
37-
def comparerContext: Context = myContext.unsafeUnbox
36+
@uncheckedCaptures
37+
private var myContext: Context = initctx
38+
def comparerContext: Context = myContext
3839

39-
protected given [DummySoItsADef]: Context = myContext.unsafeUnbox
40+
protected given [DummySoItsADef]: Context = myContext
4041

4142
protected var state: TyperState = compiletime.uninitialized
4243
def constraint: Constraint = state.constraint
4344
def constraint_=(c: Constraint): Unit = state.constraint = c
4445

4546
def init(c: Context): Unit =
46-
myContext = c.unsafeBox
47+
myContext = c
4748
state = c.typerState
4849
monitored = false
4950
GADTused = false

0 commit comments

Comments
 (0)