Skip to content

Commit 8cb97ce

Browse files
committed
Make magic imply capture checking
Comes with the following bug fixes: 1. Maybe arguments should not be boxed. 2. Capture sets of module vals need to be interpolated 3. When reporting overriding errors under -explain, use isSubTypeWhenFrozen 4. Interpolate self types of modules downwards 5. Make Tuple a Pure trait (fails i13968-maybe.scala otherwise)
1 parent 341ae82 commit 8cb97ce

12 files changed

Lines changed: 44 additions & 38 deletions

File tree

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,9 +1535,10 @@ class CheckCaptures extends Recheck, SymTransformer:
15351535

15361536
val saved = curEnv
15371537
curEnv = Env(cls, EnvKind.Regular, localSet, curEnv)
1538+
val selfType = cls.classInfo.selfType
15381539
try
15391540
// (2) Capture set of self type includes capture set of class
1540-
val thisSet = cls.classInfo.selfType.captureSet.withDescription(i"of the self type of $cls")
1541+
val thisSet = selfType.captureSet.withDescription(i"of the self type of $cls")
15411542
withGlobalCapAsRoot: // OK? We need this here since self types use GlobalAny instead of a LocalCap
15421543
checkSubset(localSet, thisSet, tree.srcPos)
15431544

@@ -1576,8 +1577,9 @@ class CheckCaptures extends Recheck, SymTransformer:
15761577
finally
15771578
if cls.is(ModuleClass) then
15781579
interpolate(cls.sourceModule.info, cls.sourceModule)
1580+
interpolate(selfType, cls)
15791581
else
1580-
capt.println(i"Use set of $cls = ${cls.useSet}, self type: ${cls.classInfo.selfType}")
1582+
capt.println(i"Use set of $cls = ${cls.useSet}, self type: $selfType")
15811583
completed += cls
15821584
curEnv = saved
15831585
end recheckClassDef

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,9 +311,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
311311
CapturingType(OrType(parent1, tp2, tp.isSoft), refs1, tp1.isBoxed)
312312
case tp @ OrType(tp1, tp2 @ CapturingType(parent2, refs2)) =>
313313
CapturingType(OrType(tp1, parent2, tp.isSoft), refs2, tp2.isBoxed)
314-
case tp @ AppliedType(tycon, args)
315-
if !defn.isFunctionClass(tp.dealias.typeSymbol) && (tp.dealias eq tp) =>
316-
tp.derivedAppliedType(tycon, args.mapConserve(_.boxDeeply))
314+
case tp @ AppliedType(tycon, args) if tp.dealias eq tp =>
315+
val sym = tycon.typeSymbol
316+
if !defn.isFunctionClass(sym) && sym != defn.MagicMaybeClass
317+
then tp.derivedAppliedType(tycon, args.mapConserve(_.boxDeeply))
318+
else tp
317319
case tp: RealTypeBounds =>
318320
tp.derivedTypeBounds(tp.lo, tp.hi.boxDeeply)
319321
case tp: LazyRef =>

compiler/src/dotty/tools/dotc/config/Feature.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -327,26 +327,27 @@ object Feature:
327327
* @return true iff the import was handled
328328
*/
329329
def handleGlobalLanguageImport(prefix: TermName, imported: Name)(using Context): Boolean =
330+
def enableCC() =
331+
ctx.compilationUnit.needsCaptureChecking = true
332+
if ctx.run != null then ctx.run.nn.ccEnabledSomewhere = true
330333
QualifiedName(prefix, imported.asTermName) match
331334
case `pureFunctions` =>
332335
ctx.compilationUnit.knowsPureFuns = true
333336
if ctx.run != null then ctx.run.nn.pureFunsImportEncountered = true
334337
true
335338
case `captureChecking` =>
336-
ctx.compilationUnit.needsCaptureChecking = true
337-
if ctx.run != null then ctx.run.nn.ccEnabledSomewhere = true
339+
enableCC()
338340
true
339341
case `separationChecking` =>
340-
ctx.compilationUnit.needsCaptureChecking = true
342+
enableCC()
341343
ctx.compilationUnit.needsSeparationChecking = true
342-
if ctx.run != null then ctx.run.nn.ccEnabledSomewhere = true
343344
true
344345
case `safe` =>
345-
ctx.compilationUnit.needsCaptureChecking = true
346+
enableCC()
346347
ctx.compilationUnit.safeMode = true
347-
if ctx.run != null then ctx.run.nn.ccEnabledSomewhere = true
348348
true
349349
case `magic` =>
350+
enableCC()
350351
ctx.compilationUnit.magic = true
351352
ctx.compilationUnit.sourceVersion = Some(SourceVersion.future)
352353
true

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,7 +1223,7 @@ extends DeclarationMsg(OverrideErrorID), NoDisambiguation:
12231223
override def canExplain =
12241224
memberTp.exists && otherTp.exists
12251225
def explain(using Context) =
1226-
if canExplain then err.whyNoMatchStr(memberTp, otherTp) else ""
1226+
if canExplain then err.whyNoMatchStr(memberTp, otherTp, frozen = true) else ""
12271227

12281228
class ForwardReferenceExtendsOverDefinition(value: Symbol, definition: Symbol)(using Context)
12291229
extends ReferenceMsg(ForwardReferenceExtendsOverDefinitionID) {
@@ -3960,7 +3960,7 @@ final class IllegalUseOfSpecialized(using Context)
39603960
inline def foo[T: Specialized](v: Vec[T]) = v.x
39613961

39623962
In this instance it was used in a way which is unsupported, such as
3963-
trying to create a type synonym or a value with explicit type Specialized[X].
3963+
trying to create a type synonym or a value with explicit type Specialized[X].
39643964
"""
39653965

39663966
/** Shows up as a TypeError (in the notes field) if variance is attempted
@@ -3974,17 +3974,17 @@ final class IllegalVarianceInSpecializedTraitsNote(using Context) extends Note:
39743974
- Primitives are specialized: Foo[Int] erases to Foo$$sp$$Int
39753975
- Reference types are specialized to the highest non-top class: Foo[Lion] erases to Foo$$sp$$Animal
39763976
- Top classes are erased normally: Foo[Any] / Foo[AnyVal] / Foo[Object] / Foo[AnyRef] erase to Foo.
3977-
This means that variance patterns that cross these erasure categories will fail at
3977+
This means that variance patterns that cross these erasure categories will fail at
39783978
runtime due to a ClassCastException, so they are not permitted.
39793979

39803980
Please see the docs for more information on how specialized traits are erased.
39813981
Suggested fixes:
39823982
- Make the type of the target site more general e.g. Foo[Object] instead of Foo[Animal].
3983-
- Reconsider if you really need to use Nothing / Object / Any / AnyRef / AnyVal in your code.
3983+
- Reconsider if you really need to use Nothing / Object / Any / AnyRef / AnyVal in your code.
39843984
- Remove Specialized from the definition of the corresponding parameter.
39853985
"""
39863986

3987-
override def covers(other: Note)(using Context): Boolean =
3987+
override def covers(other: Note)(using Context): Boolean =
39883988
other.isInstanceOf[IllegalVarianceInSpecializedTraitsNote]
39893989

39903990
final class VarianceInSpecializedTraitsLimitation(using Context)
@@ -3997,17 +3997,17 @@ final class VarianceInSpecializedTraitsLimitation(using Context)
39973997
- Primitives are specialized: Foo[Int] erases to Foo$$sp$$Int
39983998
- Reference types are specialized to the highest non-top class: Foo[Lion] erases to Foo$$sp$$Animal
39993999
- Top classes are erased normally: Foo[Any] / Foo[AnyVal] / Foo[Object] / Foo[AnyRef] erase to Foo.
4000-
This means that certain variance patterns that cross these erasure categories will fail at
4000+
This means that certain variance patterns that cross these erasure categories will fail at
40014001
runtime due to a ClassCastException, so they are not permitted.
4002-
4003-
For example, treating Foo[Any] as Foo[Animal] via contravariance is not allowed with Specialized.
4002+
4003+
For example, treating Foo[Any] as Foo[Animal] via contravariance is not allowed with Specialized.
40044004

40054005
Please see the docs for more information on how specialized traits are erased.
40064006

40074007
If you accept this limitation you can silence this warning with @nowarn. For example:
4008-
4008+
40094009
@nowarn("id=E${VarianceInSpecializedTraitsLimitationID.errorNumber}")
40104010
inline trait Foo[-T: Specialized]:
4011-
4011+
40124012
Otherwise, remove Specialized, or remove the variance.
40134013
"""

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ object ErrorReporting {
226226
else ""
227227

228228
/** A subtype log explaining why `found` does not conform to `expected` */
229-
def whyNoMatchStr(found: Type, expected: Type): String =
229+
def whyNoMatchStr(found: Type, expected: Type, frozen: Boolean = false): String =
230230
val header =
231231
i"""I tried to show that
232232
| $found
@@ -241,7 +241,9 @@ object ErrorReporting {
241241
else
242242
i"""a constraint with:
243243
|$c"""
244-
i"""${TypeComparer.explained(_.isSubType(found, expected), header, short = !ctx.settings.Ydebug.value)}
244+
i"""${TypeComparer.explained(
245+
if frozen then _.isSubTypeWhenFrozen(found, expected)
246+
else _.isSubType(found, expected), header, short = !ctx.settings.Ydebug.value)}
245247
|
246248
|The tests were made under $constraintText"""
247249

library/src/scala/Tuple.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import compiletime.*
66
import compiletime.ops.int.*
77

88
/** Tuple of arbitrary arity. */
9-
sealed trait Tuple extends Product {
9+
sealed trait Tuple extends Product, caps.Pure {
1010
import Tuple.*
1111

1212
/** Creates a copy of this tuple as an Array. */

tests/neg-custom-args/captures/effect-swaps-explicit.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
|Capability `contextual$1` outlives its scope: it leaks into outer capture set 's1 which is owned by method fail3.
66
|The leakage occurred when trying to match the following types:
77
|
8-
|Found: (contextual$1: boundary.Label[Result[Future[T^'s2]^'s3, E^'s4]^'s5]^) ?->{Result, fr, async}
8+
|Found: (contextual$1: boundary.Label[Result[Future[T^'s2]^'s3, E^'s4]^'s5]^) ?->{fr, async}
99
| Result[Future[T^'s6]^{fr, contextual$1}, E^'s7]^'s8
1010
|Required: (boundary.Label[Result[Future[T^'s9]^'s1, E^'s10]^'s11]^) ?=> Result[Future[T^'s9]^'s1, E^'s10]^'s11
1111
|

tests/neg-custom-args/captures/effect-swaps.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
|Capability `contextual$1` outlives its scope: it leaks into outer capture set 's1 which is owned by method fail3.
66
|The leakage occurred when trying to match the following types:
77
|
8-
|Found: (contextual$1: boundary.Label[Result[Future[T^'s2]^'s3, E^'s4]^'s5]^) ?->{Result, fr, async}
8+
|Found: (contextual$1: boundary.Label[Result[Future[T^'s2]^'s3, E^'s4]^'s5]^) ?->{fr, async}
99
| Result[Future[T^'s6]^{fr, contextual$1}, E^'s7]^'s8
1010
|Required: (boundary.Label[Result[Future[T^'s9]^'s1, E^'s10]^'s11]^) ?=> Result[Future[T^'s9]^'s1, E^'s10]^'s11
1111
|

tests/neg-custom-args/captures/local-mutables-2.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- Error: tests/neg-custom-args/captures/local-mutables-2.scala:7:9 ----------------------------------------------------
22
7 |def foo() = // error: separation failure
33
| ^
4-
|Separation failure in method foo's inferred result type (() ->{any.rd} Int, (x: Int) => Unit)^{}.
4+
|Separation failure in method foo's inferred result type (() ->{any.rd} Int, (x: Int) => Unit).
55
|One part, () ->{any.rd} Int, hides capabilities {any².rd}.
66
|Another part, (x: Int) => Unit, captures capabilities {any³, any, any²}.
77
|The two sets overlap at {`any` of value r}.
@@ -12,7 +12,7 @@
1212
-- Error: tests/neg-custom-args/captures/local-mutables-2.scala:11:10 --------------------------------------------------
1313
11 |def foo2() = // error: separation failure
1414
| ^
15-
| Separation failure in method foo2's inferred result type (() ->{any.rd} Int, (x: Int) => Unit)^{}.
15+
| Separation failure in method foo2's inferred result type (() ->{any.rd} Int, (x: Int) => Unit).
1616
| One part, (x: Int) => Unit, hides capabilities {any}.
1717
| Another part, () ->{any.rd} Int, captures capabilities {any.rd}.
1818
| The two sets overlap at {`any` of variable r}.

tests/run/LazyLists-maybe.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package xcollections:
44
import annotation.unchecked.uncheckedVariance
55
import compiletime.uninitialized
66

7-
abstract class LazyList[+T]:
7+
abstract class LazyList[+T] extends caps.Pure:
88

99
private var myHead: T = uninitialized
1010
private var myTail: LazyList[T] = uninitialized
@@ -69,12 +69,12 @@ package xcollections:
6969
else set(xs.head, xs.tail ++ ys)
7070

7171
extension [T, U](xs: LazyList[T])
72-
def map(f: T => U): LazyList[U] = new:
72+
def map(f: T -> U): LazyList[U] = new:
7373
protected def force() =
7474
if xs.isEmpty then empty
7575
else set(f(xs.head), xs.tail.map(f))
7676

77-
def flatMap(f: T => LazyList[U]): LazyList[U] = new:
77+
def flatMap(f: T -> LazyList[U]): LazyList[U] = new:
7878
protected def force(): LazyList[U] =
7979
if xs.isEmpty then empty
8080
else f(xs.head) ++ xs.tail.flatMap(f)
@@ -84,7 +84,7 @@ package xcollections:
8484
else xs.tail.foldLeft(f(z, xs.head))(f)
8585

8686
extension [T](xs: LazyList[T])
87-
def filter(p: T => Boolean): LazyList[T] = new:
87+
def filter(p: T -> Boolean): LazyList[T] = new:
8888
protected def force(): LazyList[T] =
8989
if xs.isEmpty then empty
9090
else if p(xs.head) then set(xs.head, xs.tail.filter(p))

0 commit comments

Comments
 (0)