Skip to content

Commit 9848c89

Browse files
committed
Refine checking if fields with inferred types
Private fields in publicly accessible classes that contribute fresh caps to their class still need an explicitly declared type, so that separate compilation can work.
1 parent a0f1613 commit 9848c89

File tree

4 files changed

+110
-22
lines changed

4 files changed

+110
-22
lines changed

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

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ object CheckCaptures:
194194
check.traverse(tp)
195195
end disallowBadRootsIn
196196

197+
private def contributesFreshToClass(sym: Symbol)(using Context): Boolean =
198+
sym.isField
199+
&& !sym.isOneOf(DeferredOrTermParamOrAccessor)
200+
&& !sym.hasAnnotation(defn.UntrackedCapturesAnnot)
201+
197202
private def ownerStr(owner: Symbol)(using Context): String =
198203
if owner.isAnonymousFunction then "enclosing function" else owner.show
199204

@@ -918,8 +923,7 @@ class CheckCaptures extends Recheck, SymTransformer:
918923
val fieldClassifiers =
919924
for
920925
sym <- cls.info.decls.toList
921-
if sym.isField && !sym.isOneOf(DeferredOrTermParamOrAccessor)
922-
&& !sym.hasAnnotation(defn.UntrackedCapturesAnnot)
926+
if contributesFreshToClass(sym)
923927
case fresh: FreshCap <- sym.info.spanCaptureSet.elems
924928
.filter(_.isTerminalCapability)
925929
.map(_.stripReadOnly)
@@ -1160,15 +1164,15 @@ class CheckCaptures extends Recheck, SymTransformer:
11601164
def checkInferredResult(tp: Type, tree: ValOrDefDef)(using Context): Type =
11611165
val sym = tree.symbol
11621166

1163-
def canUseInferred = // If canUseInferred is false, all capturing types in the type of `sym` need to be given explicitly
1164-
sym.isLocalToCompilationUnit // Symbols that can't be seen outside the compilation unit can always have inferred types
1165-
|| ctx.owner.enclosingPackageClass.isEmptyPackage
1166-
// We make an exception for symbols in the empty package.
1167-
// these could theoretically be accessed from other files in the empty package, but
1168-
// usually it would be too annoying to require explicit types.
1169-
|| sym.name.is(DefaultGetterName) // Default getters are exempted since otherwise it would be
1170-
// too annoying. This is a hole since a defualt getter's result type
1171-
// might leak into a type variable.
1167+
def isExemptFromChecks =
1168+
ctx.owner.enclosingPackageClass.isEmptyPackage
1169+
// We make an exception for symbols in the empty package.
1170+
// these could theoretically be accessed from other files in the empty package, but
1171+
// usually it would be too annoying to require explicit types.
1172+
|| sym.name.is(DefaultGetterName)
1173+
// Default getters are exempted since otherwise it would be
1174+
// too annoying. This is a hole since a defualt getter's result type
1175+
// might leak into a type variable.
11721176

11731177
def fail(tree: Tree, expected: Type, addenda: Addenda): Unit =
11741178
def maybeResult = if sym.is(Method) then " result" else ""
@@ -1191,14 +1195,28 @@ class CheckCaptures extends Recheck, SymTransformer:
11911195
|must conform to this type."""
11921196

11931197
tree.tpt match
1194-
case tpt: InferredTypeTree if !canUseInferred =>
1195-
val expected = tpt.tpe.dropAllRetains
1196-
todoAtPostCheck += { () =>
1197-
withCapAsRoot:
1198-
testAdapted(tp, expected, tree.rhs, addenda(expected))(fail)
1199-
// The check that inferred <: expected is done after recheck so that it
1200-
// does not interfere with normal rechecking by constraining capture set variables.
1201-
}
1198+
case tpt: InferredTypeTree if !isExemptFromChecks =>
1199+
if !sym.isLocalToCompilationUnit
1200+
// Symbols that can't be seen outside the compilation unit can have inferred types
1201+
// except for the else clause below.
1202+
then
1203+
val expected = tpt.tpe.dropAllRetains
1204+
todoAtPostCheck += { () =>
1205+
withCapAsRoot:
1206+
testAdapted(tp, expected, tree.rhs, addenda(expected))(fail)
1207+
// The check that inferred <: expected is done after recheck so that it
1208+
// does not interfere with normal rechecking by constraining capture set variables.
1209+
}
1210+
else if sym.is(Private) && !sym.isLocalToCompilationUnitIgnoringPrivate
1211+
&& contributesFreshToClass(sym)
1212+
// Private symbols capturing a root capability need explicit types
1213+
// so that we can compute field constributions to class instance
1214+
// capture sets across compilation units.
1215+
then
1216+
report.error(
1217+
em"""$sym needs an explicit type because it captures a root capability in its type ${tree.tpt.nuType}.
1218+
|Fields of publicily accessible classes that capture a root capability need to be given an explicit type.""",
1219+
tpt.srcPos)
12021220
case _ =>
12031221
tp
12041222
end checkInferredResult

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,11 +1248,13 @@ object SymDenotations {
12481248
|| isClass
12491249
&& (!isOneOf(EffectivelyOpenFlags) || isLocalToCompilationUnit)
12501250

1251-
final def isLocalToCompilationUnit(using Context): Boolean =
1252-
is(Private)
1253-
|| owner.ownersIterator.takeWhile(!_.isStaticOwner).exists(_.isTerm)
1251+
final def isLocalToCompilationUnitIgnoringPrivate(using Context): Boolean =
1252+
owner.ownersIterator.takeWhile(!_.isStaticOwner).exists(_.isTerm)
12541253
|| accessBoundary(defn.RootClass).isProperlyContainedIn(symbol.topLevelClass)
12551254

1255+
final def isLocalToCompilationUnit(using Context): Boolean =
1256+
is(Private) || isLocalToCompilationUnitIgnoringPrivate
1257+
12561258
final def isTransparentClass(using Context): Boolean =
12571259
is(TransparentType) || defn.isAssumedTransparent(symbol)
12581260

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
-- Error: tests/neg-custom-args/captures/check-inferred.scala:12:19 ----------------------------------------------------
2+
12 | private val count = Ref() // error
3+
| ^
4+
| value count needs an explicit type because it captures a root capability in its type test.Ref^.
5+
| Fields of publicily accessible classes that capture a root capability need to be given an explicit type.
6+
|
7+
| where: ^ refers to a fresh root capability classified as Mutable in the type of value count
8+
-- Error: tests/neg-custom-args/captures/check-inferred.scala:18:13 ----------------------------------------------------
9+
18 | val incr = () => // error
10+
| ^
11+
| value incr needs an explicit type because the inferred type does not conform to
12+
| the type that is externally visible in other compilation units.
13+
|
14+
| Inferred type : () ->{Counter.this.count} Unit
15+
| Externally visible type: () -> Unit
16+
19 | count.put(count.get + 1)
17+
-- Error: tests/neg-custom-args/captures/check-inferred.scala:20:13 ----------------------------------------------------
18+
20 | val decr = () => // error
19+
| ^
20+
| value decr needs an explicit type because the inferred type does not conform to
21+
| the type that is externally visible in other compilation units.
22+
|
23+
| Inferred type : () ->{Counter.this.count} Unit
24+
| Externally visible type: () -> Unit
25+
21 | count.put(count.get - 1)
26+
-- Error: tests/neg-custom-args/captures/check-inferred.scala:24:14 ----------------------------------------------------
27+
24 | val count = Ref(): Object^ // error
28+
| ^^^^^^^^^^^^^^
29+
| value count needs an explicit type because the inferred type does not conform to
30+
| the type that is externally visible in other compilation units.
31+
|
32+
| Inferred type : Object^
33+
| Externally visible type: Object
34+
|
35+
| where: ^ refers to a fresh root capability created in value count
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package test
2+
import caps.Mutable
3+
import caps.cap
4+
import caps.unsafe.untrackedCaptures
5+
6+
class Ref extends Mutable:
7+
var x = 0
8+
def get: Int = x
9+
update def put(y: Int): Unit = x = y
10+
11+
class Counter:
12+
private val count = Ref() // error
13+
private val altCount: Ref^ = Ref() // ok
14+
15+
@untrackedCaptures
16+
private val altAltCount = Ref() // ok
17+
18+
val incr = () => // error
19+
count.put(count.get + 1)
20+
val decr = () => // error
21+
count.put(count.get - 1)
22+
23+
trait CounterAPI:
24+
val count = Ref(): Object^ // error
25+
26+
def test() =
27+
class Counter:
28+
private val count = Ref() // ok
29+
val incr = () =>
30+
count.put(count.get + 1)
31+
val decr = () =>
32+
count.put(count.get - 1)
33+

0 commit comments

Comments
 (0)