Skip to content

Commit b160322

Browse files
committed
First stab at useless checking
Several TODOs need follow-up
1 parent b0e25eb commit b160322

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+334
-235
lines changed

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

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,17 @@ extension (tp: Type)
9999
case AnnotatedType(tp1, ann) if tp1.derivesFrom(defn.Caps_CapSet) && ann.symbol.isRetains =>
100100
ann.tree.retainedSet.retainedElementsRaw
101101
case tp =>
102-
// Nothing is a special type to represent the empty set
103-
if tp.isNothingType then Nil
104-
else tp :: Nil // should be checked by wellformedness
102+
tp.dealiasKeepAnnots match
103+
case tp: TypeRef if tp.symbol == defn.Caps_CapSet =>
104+
// This can happen in cases where we try to type an eta expansion `$x => f($x)`
105+
// from a polymorphic target type using capture sets. In that case the parameter type
106+
// of $x is not treated as inferred is approximated to CapSet. An example is
107+
// capset-problem.scala. We handle these cases by appromxating to the empty set.
108+
Nil
109+
case _ =>
110+
// Nothing is a special type to represent the empty set
111+
if tp.isNothingType then Nil
112+
else tp :: Nil // should be checked by wellformedness
105113

106114
/** A list of capabilities of a retained set. */
107115
def retainedElements(using Context): List[Capability] =
@@ -571,18 +579,30 @@ extension (sym: Symbol)
571579
def hasTrackedParts(using Context): Boolean =
572580
!CaptureSet.ofTypeDeeply(sym.info).isAlwaysEmpty
573581

574-
/** `sym` itself or its info is annotated @use or it is a type parameter with a matching
575-
* @use-annotated term parameter that contains `sym` in its deep capture set.
582+
/** Until 3.7:
583+
* `sym` itself or its info is annotated @use or it is a type parameter with a matching
584+
* @use-annotated term parameter that contains `sym` in its deep capture set.
585+
* From 3.8:
586+
* `sym` is a capset parameter without a `@reserve` annotation that
587+
* - belongs to a class in a class, or
588+
* - belongs to a method where it appears in a the deep capture set of a following term parameter of the same method.
576589
*/
577590
def isUseParam(using Context): Boolean =
578591
sym.hasAnnotation(defn.UseAnnot)
579592
|| sym.info.hasAnnotation(defn.UseAnnot)
580593
|| sym.is(TypeParam)
581-
&& sym.owner.rawParamss.nestedExists: param =>
582-
param.is(TermParam) && param.hasAnnotation(defn.UseAnnot)
583-
&& param.info.deepCaptureSet.elems.exists:
584-
case c: TypeRef => c.symbol == sym
585-
case _ => false
594+
&& !sym.info.hasAnnotation(defn.ReserveAnnot)
595+
&& (sym.owner.isClass
596+
|| sym.owner.rawParamss.nestedExists: param =>
597+
param.is(TermParam)
598+
&& (!ccConfig.allowUse || param.hasAnnotation(defn.UseAnnot))
599+
&& param.info.deepCaptureSet.elems.exists:
600+
case c: TypeRef => c.symbol == sym
601+
case _ => false
602+
|| {
603+
//println(i"not is use param $sym")
604+
false
605+
})
586606

587607
/** `sym` or its info is annotated with `@consume`. */
588608
def isConsumeParam(using Context): Boolean =

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,6 +1802,7 @@ class CheckCaptures extends Recheck, SymTransformer:
18021802

18031803
checkAnnot(defn.UseAnnot)
18041804
checkAnnot(defn.ConsumeAnnot)
1805+
checkAnnot(defn.ReserveAnnot)
18051806
end OverridingPairsCheckerCC
18061807

18071808
def traverse(t: Tree)(using Context) =

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
752752
end postProcess
753753

754754
/** Check that @use and @consume annotations only appear on parameters and not on
755-
* anonymous function parameters
755+
* anonymous function parameters. Check that @use annotations don't appear
756+
* at all from 3.8 on.
756757
*/
757758
def checkProperUseOrConsume(tree: Tree)(using Context): Unit = tree match
758759
case tree: MemberDef =>
@@ -766,11 +767,22 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
766767
&& !(sym.is(Method) && sym.owner.isClass)
767768
then
768769
report.error(
769-
em"""@consume cannot be used here. Only memeber methods and their term parameters
770+
em"""@consume cannot be used here. Only member methods and their term parameters
770771
|can have @consume annotations.""",
771772
tree.srcPos)
772773
else if annotCls == defn.UseAnnot then
773-
if !isMethodParam then
774+
if !ccConfig.allowUse then
775+
if sym.is(TypeParam) then
776+
report.error(
777+
em"""@use is redundant here and should no longer be written explicitly.
778+
|Capset variables are always implicitly used, unless they are annotated with @caps.preserve.""",
779+
tree.srcPos)
780+
else
781+
report.error(
782+
em"""@use is no longer supported. Instead of @use you can introduce capset
783+
|variables for the polymorphic parts of parameter types.""",
784+
tree.srcPos)
785+
else if !isMethodParam then
774786
report.error(
775787
em"@use cannot be used here. Only method parameters can have @use annotations.",
776788
tree.srcPos)

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,11 @@ object ccConfig:
5050
def useFreshLevels(using Context): Boolean =
5151
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`)
5252

53-
/** If true, turn on separation checking */
54-
def useSepChecks(using Context): Boolean =
55-
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.8`)
56-
5753
/** Not used currently. Handy for trying out new features */
5854
def newScheme(using ctx: Context): Boolean =
5955
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`)
6056

57+
def allowUse(using Context): Boolean =
58+
Feature.sourceVersion.stable.isAtMost(SourceVersion.`3.7`)
59+
6160
end ccConfig

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,8 +1074,9 @@ class Definitions {
10741074
@tu lazy val UncheckedVarianceAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedVariance")
10751075
@tu lazy val UncheckedCapturesAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedCaptures")
10761076
@tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.unsafe.untrackedCaptures")
1077-
@tu lazy val UseAnnot: ClassSymbol = requiredClass("scala.caps.use")
1078-
@tu lazy val ConsumeAnnot: ClassSymbol = requiredClass("scala.caps.consume")
1077+
@tu lazy val UseAnnot: ClassSymbol = requiredClass("scala.caps.use")
1078+
@tu lazy val ConsumeAnnot: ClassSymbol = requiredClass("scala.caps.consume")
1079+
@tu lazy val ReserveAnnot: ClassSymbol = requiredClass("scala.caps.reserve")
10791080
@tu lazy val RefineOverrideAnnot: ClassSymbol = requiredClass("scala.caps.internal.refineOverride")
10801081
@tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile")
10811082
@tu lazy val LanguageFeatureMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.languageFeature")
@@ -1476,7 +1477,7 @@ class Definitions {
14761477
def patchStdLibClass(denot: ClassDenotation)(using Context): Unit =
14771478
// Do not patch the stdlib files if we explicitly disable it
14781479
// This is only to be used during the migration of the stdlib
1479-
if ctx.settings.YnoStdlibPatches.value then
1480+
if ctx.settings.YnoStdlibPatches.value then
14801481
return
14811482

14821483
def patch2(denot: ClassDenotation, patchCls: Symbol): Unit =
@@ -2111,8 +2112,9 @@ class Definitions {
21112112
CapsModule, CapsModule.moduleClass, PureClass,
21122113
Caps_Capability, // TODO: Remove when Capability is stabilized
21132114
RequiresCapabilityAnnot,
2114-
captureRoot, Caps_CapSet, Caps_ContainsTrait, Caps_ContainsModule, Caps_ContainsModule.moduleClass, UseAnnot,
2115-
Caps_Mutable, Caps_Sharable, Caps_Control, Caps_Classifier, ConsumeAnnot,
2115+
captureRoot, Caps_CapSet, Caps_ContainsTrait, Caps_ContainsModule, Caps_ContainsModule.moduleClass,
2116+
Caps_Mutable, Caps_Sharable, Caps_Control, Caps_Classifier,
2117+
ConsumeAnnot, UseAnnot, ReserveAnnot,
21162118
CapsUnsafeModule, CapsUnsafeModule.moduleClass,
21172119
CapsInternalModule, CapsInternalModule.moduleClass,
21182120
RetainsAnnot, RetainsCapAnnot, RetainsByNameAnnot)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
137137
keywordText("erased ").provided(isErased)
138138
~ specialAnnotText(defn.UseAnnot, arg)
139139
~ specialAnnotText(defn.ConsumeAnnot, arg)
140+
~ specialAnnotText(defn.ReserveAnnot, arg)
140141
~ homogenizeArg(arg).match
141142
case arg: TypeBounds => "?" ~ toText(arg)
142143
case arg => toText(arg)

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
602602
def typedIdent(tree: untpd.Ident, pt: Type)(using Context): Tree =
603603
record("typedIdent")
604604
val name = tree.name
605-
def kind = if (name.isTermName) "" else "type "
605+
def kind =
606+
if name.isTermName then
607+
if ctx.mode.is(Mode.InCaptureSet) then "capability "
608+
else ""
609+
else "type "
606610
typr.println(s"typed ident $kind$name in ${ctx.owner}")
607611
if ctx.mode.is(Mode.Pattern) then
608612
if name == nme.WILDCARD then

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class CompilationTests {
3737
compileFilesInDir("tests/pos-special/sourcepath/outer", defaultOptions.and("-sourcepath", "tests/pos-special/sourcepath")),
3838
compileFile("tests/pos-special/sourcepath/outer/nested/Test4.scala", defaultOptions.and("-sourcepath", "tests/pos-special/sourcepath")),
3939
compileFilesInDir("tests/pos-scala2", defaultOptions.and("-source", "3.0-migration")),
40-
compileFilesInDir("tests/pos-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking", "-language:experimental.separationChecking")),
40+
compileFilesInDir("tests/pos-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking", "-language:experimental.separationChecking", "-source", "3.8")),
4141
compileFile("tests/pos-special/utf8encoded.scala", defaultOptions.and("-encoding", "UTF8")),
4242
compileFile("tests/pos-special/utf16encoded.scala", defaultOptions.and("-encoding", "UTF16")),
4343
compileDir("tests/pos-special/i18589", defaultOptions.and("-Wsafe-init").without("-Ycheck:all")),
@@ -149,7 +149,7 @@ class CompilationTests {
149149
aggregateTests(
150150
compileFilesInDir("tests/neg", defaultOptions, FileFilter.exclude(TestSources.negScala2LibraryTastyExcludelisted)),
151151
compileFilesInDir("tests/neg-deep-subtype", allowDeepSubtypes),
152-
compileFilesInDir("tests/neg-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking", "-language:experimental.separationChecking")),
152+
compileFilesInDir("tests/neg-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking", "-language:experimental.separationChecking", "-source", "3.8")),
153153
compileFile("tests/neg-custom-args/sourcepath/outer/nested/Test1.scala", defaultOptions.and("-sourcepath", "tests/neg-custom-args/sourcepath")),
154154
compileDir("tests/neg-custom-args/sourcepath2/hi", defaultOptions.and("-sourcepath", "tests/neg-custom-args/sourcepath2", "-Xfatal-warnings")),
155155
compileList("duplicate source", List(
@@ -172,7 +172,7 @@ class CompilationTests {
172172
aggregateTests(
173173
compileFilesInDir("tests/run", defaultOptions.and("-Wsafe-init")),
174174
compileFilesInDir("tests/run-deep-subtype", allowDeepSubtypes),
175-
compileFilesInDir("tests/run-custom-args/captures", allowDeepSubtypes.and("-language:experimental.captureChecking", "-language:experimental.separationChecking")),
175+
compileFilesInDir("tests/run-custom-args/captures", allowDeepSubtypes.and("-language:experimental.captureChecking", "-language:experimental.separationChecking", "-source", "3.8")),
176176
// Run tests for legacy lazy vals.
177177
compileFilesInDir("tests/run", defaultOptions.and("-Wsafe-init", "-Ylegacy-lazy-vals", "-Ycheck-constraint-deps"), FileFilter.include(TestSources.runLazyValsAllowlist)),
178178
).checkRuns()

library/src/scala/caps/package.scala

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,25 @@ object Contains:
7070
@experimental
7171
given containsImpl[C >: CapSet <: CapSet @retainsCap, R <: Singleton]: Contains[C, R]()
7272

73-
/** An annotation on parameters `x` stating that the method's body makes
74-
* use of the reach capability `x*`. Consequently, when calling the method
75-
* we need to charge the deep capture set of the actual argiment to the
73+
/** An annotation on capset parameters `C` stating that the method's body does not
74+
* have `C` in its use-set. `C` might be accessed under a box in the method
75+
* or in the result type of the method. Consequently, when calling the method
76+
* we do not need to charge the capture set of the actual argiment to the
7677
* environment.
7778
*
7879
* Note: This should go into annotations. For now it is here, so that we
7980
* can experiment with it quickly between minor releases
8081
*/
8182
@experimental
83+
final class reserve extends annotation.StaticAnnotation
84+
85+
/** Allowed only for source versions up to 3.7:
86+
* An annotation on parameters `x` stating that the method's body makes
87+
* use of the reach capability `x*`. Consequently, when calling the method
88+
* we need to charge the deep capture set of the actual argiment to the
89+
* environment.
90+
*/
91+
@experimental
8292
final class use extends annotation.StaticAnnotation
8393

8494
/** An annotations on parameters and update methods.

project/Build.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,7 +1452,7 @@ object Build {
14521452
moduleName := "scala-library",
14531453
version := dottyVersion,
14541454
versionScheme := Some("semver-spec"),
1455-
// sbt defaults to scala 2.12.x and metals will report issues as it doesn't consider the project a scala 3 project
1455+
// sbt defaults to scala 2.12.x and metals will report issues as it doesn't consider the project a scala 3 project
14561456
// (not the actual version we use to compile the project)
14571457
scalaVersion := referenceVersion,
14581458
crossPaths := false, // org.scala-lang:scala-library doesn't have a crosspath
@@ -1606,7 +1606,7 @@ object Build {
16061606
settings(scala2LibraryBootstrappedSettings).
16071607
settings(
16081608
moduleName := "scala2-library-cc",
1609-
scalacOptions += "-language:experimental.separationChecking" // for separation checking
1609+
scalacOptions ++= List("-language:experimental.separationChecking", "-source", "3.8") // for @use changes
16101610
)
16111611

16121612
lazy val scala2LibraryBootstrappedSettings = Seq(

0 commit comments

Comments
 (0)