-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable experimental mode when experimental feature is imported #19807
Changes from 7 commits
258c11a
ee8277d
b92e62d
1ebae87
b21523b
32f5a17
df85c6e
803603b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,6 +42,7 @@ import cc.{isCaptureChecking, isRetainsLike} | |
|
||
import collection.mutable | ||
import reporting.* | ||
import Annotations.ExperimentalAnnotation | ||
|
||
object Checking { | ||
import tpd.* | ||
|
@@ -797,50 +798,62 @@ object Checking { | |
tree | ||
|
||
/** Check that experimental language imports in `trees` | ||
* are done only in experimental scopes, or in a top-level | ||
* scope with only @experimental definitions. | ||
* are done only in experimental scopes. For for top-level | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* experimental imports, all top-level definitions are transformed | ||
* to @experimental definitions. | ||
* | ||
*/ | ||
def checkExperimentalImports(trees: List[Tree])(using Context): Unit = | ||
|
||
def nonExperimentalStat(trees: List[Tree]): Tree = trees match | ||
case (_: Import | EmptyTree) :: rest => | ||
nonExperimentalStat(rest) | ||
case (tree @ TypeDef(_, impl: Template)) :: rest if tree.symbol.isPackageObject => | ||
nonExperimentalStat(impl.body).orElse(nonExperimentalStat(rest)) | ||
case (tree: PackageDef) :: rest => | ||
nonExperimentalStat(tree.stats).orElse(nonExperimentalStat(rest)) | ||
case (tree: MemberDef) :: rest => | ||
if tree.symbol.isExperimental || tree.symbol.is(Synthetic) then | ||
nonExperimentalStat(rest) | ||
else | ||
tree | ||
case tree :: rest => | ||
tree | ||
case Nil => | ||
EmptyTree | ||
|
||
for case imp @ Import(qual, selectors) <- trees do | ||
def checkAndAdaptExperimentalImports(trees: List[Tree])(using Context): Unit = | ||
def nonExperimentalTopLevelDefs(pack: Symbol): Iterator[Symbol] = | ||
def isNonExperimentalTopLevelDefinition(sym: Symbol) = | ||
!sym.isExperimental | ||
&& sym.source == ctx.compilationUnit.source | ||
&& !sym.isConstructor // not constructor of package object | ||
&& !sym.is(Package) && !sym.isPackageObject && !sym.name.endsWith(str.TOPLEVEL_SUFFIX) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed the redundant test |
||
|
||
val packageMembers = | ||
pack.info.decls | ||
.toList.iterator | ||
.filter(isNonExperimentalTopLevelDefinition) | ||
val packageObjectMembers = | ||
pack.info.decls | ||
.toList.iterator | ||
.filter(sym => sym.isClass && (sym.is(Package) || sym.isPackageObject)) | ||
.flatMap(nonExperimentalTopLevelDefs) | ||
|
||
packageMembers ++ packageObjectMembers | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider combining the two definitions into: pack.info.decls.toList.iterator.flatMap: sym =>
if sym.isClass && (sym.isPackage or sym.isPackageObject) then
nonExperimentalTopLevelDefs(sym)
else if isNonExperimentalTopLevelDefinition(sym) then sym :: Nil
else Nil There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated |
||
|
||
def unitExperimentalLanguageImports = | ||
def isAllowedImport(sel: untpd.ImportSelector) = | ||
val name = Feature.experimental(sel.name) | ||
name == Feature.scala2macros | ||
|| name == Feature.erasedDefinitions | ||
|| name == Feature.captureChecking | ||
trees.filter { | ||
case Import(qual, selectors) => | ||
languageImport(qual) match | ||
case Some(nme.experimental) => | ||
!selectors.forall(isAllowedImport) && !ctx.owner.isInExperimentalScope | ||
case _ => false | ||
case _ => false | ||
} | ||
|
||
languageImport(qual) match | ||
case Some(nme.experimental) | ||
if !ctx.owner.isInExperimentalScope && !selectors.forall(isAllowedImport) => | ||
def check(stable: => String) = | ||
Feature.checkExperimentalFeature("features", imp.srcPos, | ||
s"\n\nNote: the scope enclosing the import is not considered experimental because it contains the\nnon-experimental $stable") | ||
if ctx.owner.is(Package) then | ||
// allow top-level experimental imports if all definitions are @experimental | ||
nonExperimentalStat(trees) match | ||
case EmptyTree => | ||
case tree: MemberDef => check(i"${tree.symbol}") | ||
case tree => check(i"expression ${tree}") | ||
else Feature.checkExperimentalFeature("features", imp.srcPos) | ||
if ctx.owner.is(Package) || ctx.owner.name.startsWith(str.REPL_SESSION_LINE) then | ||
def markTopLevelDefsAsExperimental(why: String): Unit = | ||
for sym <- nonExperimentalTopLevelDefs(ctx.owner) do | ||
sym.addAnnotation(ExperimentalAnnotation(s"Added by $why", sym.span)) | ||
|
||
unitExperimentalLanguageImports match | ||
case imp :: _ => markTopLevelDefsAsExperimental(i"top level $imp") | ||
case _ => | ||
end checkExperimentalImports | ||
Feature.experimentalEnabledByLanguageSetting match | ||
case Some(sel) => markTopLevelDefsAsExperimental(i"-language:experimental.$sel") | ||
case _ if ctx.settings.experimental.value => markTopLevelDefsAsExperimental(i"-experimental") | ||
case _ => | ||
else | ||
for imp <- unitExperimentalLanguageImports do | ||
Feature.checkExperimentalFeature("feature local import", imp.srcPos) | ||
|
||
end checkAndAdaptExperimentalImports | ||
|
||
/** Checks that PolyFunction only have valid refinements. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
//> using options -experimental -Yno-experimental | ||
//> using options -experimental | ||
|
||
@extendFoo | ||
class AFoo // error |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
//> using options -experimental -Yno-experimental | ||
//> using options -experimental | ||
|
||
@extendFoo | ||
class AFoo // error |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
//> using options -experimental -Yno-experimental | ||
//> using options -experimental | ||
|
||
@buggy // error | ||
case class Foo() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems correct. Would be good to add a doc comment that clarifies what this is. I guess: The features that automatically imply experimental mode, and therefore all clients of these modules also need to be experimental?
That's indeed not the case for capture checking. Capture checking is designed not to be viral.