Skip to content

Commit 3e98418

Browse files
oderskyLinyxus
authored andcommitted
Drop special code paths for objects
Treat objects like normal lazy vals when it comes to computing and checking their types. Previously, the type of an object was its self type, but that is problematic since self types get interpolated upwards. By dropping the special code paths we make sure that objects are treated like other vals that are initialized with new.
1 parent 2eb0630 commit 3e98418

File tree

9 files changed

+541
-67
lines changed

9 files changed

+541
-67
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,9 @@ object Capabilities:
543543
case prefix: Capability => prefix.computeOwner(mapUnscoped)
544544
case NoPrefix if mapUnscoped && classifier.derivesFrom(defn.Caps_Unscoped) =>
545545
ctx.owner.topLevelClass
546+
.orElse: // fallback needed if ctx.owner is a toplevel module val
547+
assert(ctx.owner.is(ModuleVal))
548+
ctx.owner
546549
case _ => setOwner
547550
case _ /* : GlobalCap | ResultCap | ParamRef */ => NoSymbol
548551

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,9 +1018,9 @@ object CaptureSet:
10181018
&& !elems.contains(elem)
10191019
&& !owner.isAnonymousFunction =>
10201020
def fail = i"attempting to add $elem to $this"
1021-
def hideIn(fc: FreshCap): Unit =
1021+
def hideIn(fc: FreshCap): Boolean =
10221022
assert(elem.tryClassifyAs(fc.hiddenSet.classifier), fail)
1023-
if !isRefining then
1023+
if isRefining then
10241024
// If a variable is added by addCaptureRefinements in a synthetic
10251025
// refinement of a class type, don't do level checking. The problem is
10261026
// that the variable might be matched against a type that does not have
@@ -1030,14 +1030,16 @@ object CaptureSet:
10301030
// TODO: We should instead mark the variable as impossible to instantiate
10311031
// and drop the refinement later in the inferred type.
10321032
// Test case is drop-refinement.scala.
1033-
assert(fc.acceptsLevelOf(elem),
1034-
i"level failure, cannot add $elem with ${elem.levelOwner} to $owner / $getClass / $fail")
1035-
fc.hiddenSet.add(elem)
1033+
true
1034+
else if fc.acceptsLevelOf(elem) then
1035+
fc.hiddenSet.add(elem)
1036+
true
1037+
else
1038+
capt.println(i"level failure when subsuming fresh caps, cannot add $elem with ${elem.levelOwner} to $owner / $fail")
1039+
false
10361040
val isSubsumed = (false /: elems): (isSubsumed, prev) =>
10371041
prev match
1038-
case prev: FreshCap =>
1039-
hideIn(prev)
1040-
true
1042+
case prev: FreshCap => hideIn(prev)
10411043
case _ => isSubsumed
10421044
if !isSubsumed then
10431045
if elem.origin != Origin.InDecl(owner) || elem.hiddenSet.isConst then

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

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,43 +1213,41 @@ class CheckCaptures extends Recheck, SymTransformer:
12131213
val savedEnv = curEnv
12141214
val runInConstructor = !sym.isOneOf(Param | ParamAccessor | Lazy | NonMember)
12151215
try
1216-
if sym.is(Module) then sym.info // Modules are checked by checking the module class
1217-
else
1218-
if sym.is(Mutable) then
1219-
if !sym.hasAnnotation(defn.UncheckedCapturesAnnot) then
1220-
val addendum = setup.capturedBy.get(sym) match
1221-
case Some(encl) =>
1222-
val enclStr =
1223-
if encl.isAnonymousFunction then
1224-
val location = setup.anonFunCallee.get(encl) match
1225-
case Some(meth) if meth.exists => i" argument in a call to $meth"
1226-
case _ => ""
1227-
s"an anonymous function$location"
1228-
else encl.show
1229-
i"\n\nNote that $sym does not count as local since it is captured by $enclStr"
1230-
case _ =>
1231-
""
1232-
disallowBadRootsIn(
1233-
tree.tpt.nuType, NoSymbol, i"Mutable $sym", "have type", addendum, sym.srcPos)
1234-
if ccConfig.strictMutability
1235-
&& sym.owner.isClass
1236-
&& !sym.owner.derivesFrom(defn.Caps_Stateful)
1237-
&& !sym.hasAnnotation(defn.UntrackedCapturesAnnot) then
1238-
report.error(
1239-
em"""Mutable $sym is defined in a class that does not extend `Stateful`.
1240-
|The variable needs to be annotated with `untrackedCaptures` to allow this.""",
1241-
tree.namePos)
1242-
1243-
// Lazy vals need their own environment to track captures from their RHS,
1244-
// similar to how methods work
1245-
if sym.is(Lazy) then
1246-
val localSet = capturedVars(sym)
1247-
if localSet ne CaptureSet.empty then
1248-
curEnv = Env(sym, EnvKind.Regular, localSet, curEnv, nestedClosure = NoSymbol)
1249-
else if runInConstructor then
1250-
pushConstructorEnv()
1251-
1252-
checkInferredResult(super.recheckValDef(tree, sym), tree)
1216+
if sym.is(Mutable) then
1217+
if !sym.hasAnnotation(defn.UncheckedCapturesAnnot) then
1218+
val addendum = setup.capturedBy.get(sym) match
1219+
case Some(encl) =>
1220+
val enclStr =
1221+
if encl.isAnonymousFunction then
1222+
val location = setup.anonFunCallee.get(encl) match
1223+
case Some(meth) if meth.exists => i" argument in a call to $meth"
1224+
case _ => ""
1225+
s"an anonymous function$location"
1226+
else encl.show
1227+
i"\n\nNote that $sym does not count as local since it is captured by $enclStr"
1228+
case _ =>
1229+
""
1230+
disallowBadRootsIn(
1231+
tree.tpt.nuType, NoSymbol, i"Mutable $sym", "have type", addendum, sym.srcPos)
1232+
if ccConfig.strictMutability
1233+
&& sym.owner.isClass
1234+
&& !sym.owner.derivesFrom(defn.Caps_Stateful)
1235+
&& !sym.hasAnnotation(defn.UntrackedCapturesAnnot) then
1236+
report.error(
1237+
em"""Mutable $sym is defined in a class that does not extend `Stateful`.
1238+
|The variable needs to be annotated with `untrackedCaptures` to allow this.""",
1239+
tree.namePos)
1240+
1241+
// Lazy vals need their own environment to track captures from their RHS,
1242+
// similar to how methods work
1243+
if sym.is(Lazy) then
1244+
val localSet = capturedVars(sym)
1245+
if localSet ne CaptureSet.empty then
1246+
curEnv = Env(sym, EnvKind.Regular, localSet, curEnv, nestedClosure = NoSymbol)
1247+
else if runInConstructor then
1248+
pushConstructorEnv()
1249+
1250+
checkInferredResult(super.recheckValDef(tree, sym), tree)
12531251
finally
12541252
if !sym.is(Param) then
12551253
// Parameters with inferred types belong to anonymous methods. We need to wait
@@ -1264,7 +1262,13 @@ class CheckCaptures extends Recheck, SymTransformer:
12641262
if runInConstructor && savedEnv.owner.isClass then
12651263
markFree(declaredCaptures, tree, addUseInfo = false)
12661264

1267-
if sym.owner.isStaticOwner && !declaredCaptures.elems.isEmpty && sym != defn.captureRoot then
1265+
if sym.owner.isStaticOwner
1266+
&& !declaredCaptures.elems.isEmpty
1267+
&& sym != defn.captureRoot
1268+
&& !(sym.is(ModuleVal) && sym.owner.is(Package))
1269+
// global modules that don't derive from capability can have captures
1270+
// only if their fields have them, and then the field was already reported.
1271+
then
12681272
def where =
12691273
if sym.effectiveOwner.is(Package) then "top-level definition"
12701274
else i"member of static ${sym.owner}"

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

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
515515
private def transformTT(tree: TypeTree, sym: Symbol, boxed: Boolean)(using Context): Unit =
516516
if !tree.hasNuType then
517517
var transformed =
518-
if tree.isInferred
518+
if tree.isInferred || sym.is(ModuleVal)
519519
then transformInferredType(tree.tpe)
520520
else transformExplicitType(tree.tpe, sym, tptToCheck = tree)
521521
if boxed then transformed = transformed.boxDeeply
@@ -756,18 +756,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
756756
updateInfo(cls, newInfo, cls.owner)
757757
capt.println(i"update class info of $cls with parents $ps selfinfo $selfInfo to $newInfo")
758758
cls.thisType.asInstanceOf[ThisType].invalidateCaches()
759-
if cls.is(ModuleClass) then
760-
// if it's a module, the capture set of the module reference is the capture set of the self type
761-
val modul = cls.sourceModule
762-
val selfCaptures = selfInfo1 match
763-
case CapturingType(_, refs) => refs
764-
case _ => CaptureSet.empty
765-
// Note: Can't do val selfCaptures = selfInfo1.captureSet here.
766-
// This would potentially give stackoverflows when setup is run repeatedly.
767-
// One test case is pos-custom-args/captures/checkbounds.scala under
768-
// ccConfig.alwaysRepeatRun = true.
769-
updateInfo(modul, CapturingType(modul.info, selfCaptures), modul.owner)
770-
modul.termRef.invalidateCaches()
771759
case _ =>
772760
end postProcess
773761

library/src/scala/collection/Map.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ object MapOps {
445445
@SerialVersionUID(3L)
446446
object Map extends MapFactory.Delegate[Map](immutable.Map) {
447447
private val DefaultSentinel: AnyRef = new AnyRef
448-
private val DefaultSentinelFn: () => AnyRef = () => DefaultSentinel
448+
private val DefaultSentinelFn: () -> AnyRef = () => DefaultSentinel
449449
}
450450

451451
/** Explicit instantiation of the `Map` trait to reduce class file size in subclasses. */

tests/neg-custom-args/captures/class-uses.check

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
-- Warning: tests/neg-custom-args/captures/class-uses.scala:7:0 --------------------------------------------------------
2-
7 |object Console extends SharedCapability:
3-
|^
4-
|object Console has a non-empty capture set but will not be added as
5-
|a capability to computed capture sets since it is globally accessible
6-
|as a top-level definition. Global values cannot be capabilities.
71
-- Error: tests/neg-custom-args/captures/class-uses.scala:17:22 --------------------------------------------------------
82
17 | Console.println(g.toString) // error
93
| ^
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- Error: tests/neg-custom-args/captures/object-uses.scala:12:22 -------------------------------------------------------
2+
12 | Console.println(g.toString) // error
3+
| ^
4+
| reference (A.this.g : test.File^) is not included in the allowed capture set {A.this.f}
5+
| of the enclosing object B in class A
6+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/object-uses.scala:15:18 ----------------------------------
7+
15 | val _: Object = b // error
8+
| ^
9+
| Found: (A.this.b : A.this.B.type)
10+
| Required: Object
11+
|
12+
| Note that capability A.this.f is not included in capture set {}.
13+
|
14+
| longer explanation available when compiling with `-explain`
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package test
2+
3+
class File
4+
5+
class A {
6+
val f: File^ = File()
7+
val g: File^ = File()
8+
9+
object B uses f:
10+
def show =
11+
Console.println(f.toString)
12+
Console.println(g.toString) // error
13+
14+
val b = B
15+
val _: Object = b // error
16+
17+
}

0 commit comments

Comments
 (0)