Skip to content

Allow experimental language imports in experimental scopes #13396

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

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ object Feature:
else
false

def checkExperimentalFeature(which: String, srcPos: SrcPos)(using Context) =
def checkExperimentalFeature(which: String, srcPos: SrcPos, note: => String = "")(using Context) =
if !isExperimentalEnabled then
report.error(i"Experimental $which may only be used with a nightly or snapshot version of the compiler", srcPos)
report.error(i"Experimental $which may only be used with a nightly or snapshot version of the compiler$note", srcPos)

def checkExperimentalDef(sym: Symbol, srcPos: SrcPos)(using Context) =
if !isExperimentalEnabled then
Expand Down
4 changes: 0 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3114,10 +3114,6 @@ object Parsers {
languageImport(tree) match
case Some(prefix) =>
in.languageImportContext = in.languageImportContext.importContext(imp, NoSymbol)
if prefix == nme.experimental
&& selectors.exists(sel => Feature.experimental(sel.name) != Feature.scala2macros)
then
Feature.checkExperimentalFeature("features", imp.srcPos)
for
case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors
if allSourceVersionNames.contains(imported)
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
throw ex
}

override def transformStats(trees: List[Tree], exprOwner: Symbol)(using Context): List[Tree] =
try super.transformStats(trees, exprOwner)
finally Checking.checkExperimentalImports(trees)

/** Transforms the rhs tree into a its default tree if it is in an `erased` val/def.
* Performed to shrink the tree that is known to be erased later.
*/
Expand Down
39 changes: 39 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,44 @@ object Checking {
checkValue(tree)
case _ =>
tree

/** Check that experimental language imports in `trees`
* are done only in experimental scopes, or in scopes where
* all statements are @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
languageImport(qual) match
case Some(nme.experimental)
if !ctx.owner.isInExperimentalScope
&& selectors.exists(sel => experimental(sel.name) != scala2macros) =>
def check(stable: => String) =
checkExperimentalFeature("features", imp.srcPos,
s"\n\nNote: the scope enclosing the import is not considered experimental because it contains the\nnon-experimental $stable")
nonExperimentalStat(trees) match
case EmptyTree =>
case tree: MemberDef => check(i"${tree.symbol}")
case tree => check(i"expression ${tree}")
case _ =>
end checkExperimentalImports
}

trait Checking {
Expand Down Expand Up @@ -830,6 +868,7 @@ trait Checking {
em"Implementation restriction: ${path.tpe.classSymbol} is not a valid prefix " +
"for a wildcard export, as it is a package.", path.srcPos)


/** Check that module `sym` does not clash with a class of the same name
* that is concurrently compiled in another source file.
*/
Expand Down
2 changes: 1 addition & 1 deletion compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class CompilationTests {
compileFile("tests/neg-custom-args/matchable.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")),
compileFile("tests/neg-custom-args/i7314.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")),
compileFile("tests/neg-custom-args/feature-shadowing.scala", defaultOptions.and("-Xfatal-warnings", "-feature")),
compileDir("tests/neg-custom-args/hidden-type-errors", defaultOptions.and("-explain")),
compileDir("tests/neg-custom-args/hidden-type-errors", defaultOptions.and("-explain")),
).checkExpectedErrors()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import language.experimental.erasedDefinitions // error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See pos-curstomArgs/experimental.erased for the same example that compiles. It's not CanThrow that triggers the error but other.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested pos-curstomArgs/experimental.erased manually with -Yno-experimental. I assume that will be tested anyway since we start with a stable compiler. Or do we need a separate pos test that asserts -Yno-experimental`?

Copy link
Contributor

@nicolasstucki nicolasstucki Aug 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the same behavior as in master.

import language.experimental.erasedDefinitions
import annotation.experimental

@experimental
erased class CanThrow[-E <: Exception]

Compiles with snapshot build without error but fails when we add -Yno-experimental.

If we cannot compile it with -Yno-experimental we will not be able to re-bootstrap on a stable compiler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it manually with -Yno-experimental and verified that it compiles

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see what goes on. The generated package object is not experimental.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message of is also unhelpful. If a user does this change they will have no idea why it started failing.

import language.experimental.erasedDefinitions // error
import annotation.experimental

@experimental
erased class CanThrow[-E <: Exception]
+ object Foo

The scope of CanThrow is still experimental. But now it it says Experimental features may only be used with a nightly or snapshot version of the compiler which has not changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now disregard synthetic definitions in the check. That also takes care of compiler-generated companion objects.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import annotation.experimental

@experimental
erased class CanThrow[-E <: Exception]

def other = 1
6 changes: 0 additions & 6 deletions tests/neg-custom-args/no-experimental/experimental.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,4 @@ class Test2 {

class Test6 {
import scala.language.experimental // ok
}

class Test7 {
import scala.language.experimental
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted since we now check experimental imports after typer, and Test7 produces errors at Typer

import experimental.genericNumberLiterals // error: no aliases can be used to refer to a language import
val x: BigInt = 13232202002020202020202 // error
}
10 changes: 10 additions & 0 deletions tests/pos/experimental-erased-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import language.experimental.erasedDefinitions
import annotation.experimental

@experimental object Test:

erased class CanThrow[-E <: Exception]

def other = 1


15 changes: 15 additions & 0 deletions tests/pos/experimental-erased.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import language.experimental.erasedDefinitions
import annotation.experimental

@experimental
erased class CanThrow[-E <: Exception](val i: Int = 0)

@experimental
object Foo

@experimental
def bar = 1