Skip to content

Commit 5cf6c79

Browse files
oderskynicolasstucki
authored andcommitted
Allow experimental language imports in experimental scopes
Also allow top-level experimental language imports if all top-level definitions are experimental
1 parent 028c749 commit 5cf6c79

File tree

13 files changed

+193
-14
lines changed

13 files changed

+193
-14
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ object Feature:
9797
else
9898
false
9999

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

104104
def checkExperimentalDef(sym: Symbol, srcPos: SrcPos)(using Context) =
105105
if !isExperimentalEnabled then

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3114,10 +3114,6 @@ object Parsers {
31143114
languageImport(tree) match
31153115
case Some(prefix) =>
31163116
in.languageImportContext = in.languageImportContext.importContext(imp, NoSymbol)
3117-
if prefix == nme.experimental
3118-
&& selectors.exists(sel => Feature.experimental(sel.name) != Feature.scala2macros && Feature.experimental(sel.name) != Feature.erasedDefinitions)
3119-
then
3120-
Feature.checkExperimentalFeature("features", imp.srcPos)
31213117
for
31223118
case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors
31233119
if allSourceVersionNames.contains(imported)

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
448448
throw ex
449449
}
450450

451+
override def transformStats(trees: List[Tree], exprOwner: Symbol)(using Context): List[Tree] =
452+
try super.transformStats(trees, exprOwner)
453+
finally Checking.checkExperimentalImports(trees)
454+
451455
/** Transforms the rhs tree into a its default tree if it is in an `erased` val/def.
452456
* Performed to shrink the tree that is known to be erased later.
453457
*/

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,44 @@ object Checking {
719719
checkValue(tree)
720720
case _ =>
721721
tree
722+
723+
/** Check that experimental language imports in `trees`
724+
* are done only in experimental scopes, or in a top-level
725+
* scope with only @experimental definitions.
726+
*/
727+
def checkExperimentalImports(trees: List[Tree])(using Context): Unit =
728+
729+
def nonExperimentalStat(trees: List[Tree]): Tree = trees match
730+
case (_: Import | EmptyTree) :: rest =>
731+
nonExperimentalStat(rest)
732+
case (tree @ TypeDef(_, impl: Template)) :: rest if tree.symbol.isPackageObject =>
733+
nonExperimentalStat(impl.body).orElse(nonExperimentalStat(rest))
734+
case (tree: PackageDef) :: rest =>
735+
nonExperimentalStat(tree.stats).orElse(nonExperimentalStat(rest))
736+
case (tree: MemberDef) :: rest =>
737+
if tree.symbol.isExperimental || tree.symbol.is(Synthetic) then
738+
nonExperimentalStat(rest)
739+
else
740+
tree
741+
case tree :: rest =>
742+
tree
743+
case Nil =>
744+
EmptyTree
745+
746+
for case imp @ Import(qual, selectors) <- trees do
747+
languageImport(qual) match
748+
case Some(nme.experimental)
749+
if !ctx.owner.isInExperimentalScope
750+
&& selectors.exists(sel => experimental(sel.name) != scala2macros && experimental(sel.name) != erasedDefinitions) =>
751+
def check(stable: => String) =
752+
checkExperimentalFeature("features", imp.srcPos,
753+
s"\n\nNote: the scope enclosing the import is not considered experimental because it contains the\nnon-experimental $stable")
754+
nonExperimentalStat(trees) match
755+
case EmptyTree =>
756+
case tree: MemberDef => check(i"${tree.symbol}")
757+
case tree => check(i"expression ${tree}")
758+
case _ =>
759+
end checkExperimentalImports
722760
}
723761

724762
trait Checking {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class CompilationTests {
4242
compileFilesInDir("tests/pos-custom-args/erased", defaultOptions.and("-language:experimental.erasedDefinitions")),
4343
compileFilesInDir("tests/pos", defaultOptions.and("-Ysafe-init")),
4444
compileFilesInDir("tests/pos-deep-subtype", allowDeepSubtypes),
45+
compileFilesInDir("tests/pos-custom-args/no-experimental", defaultOptions.and("-Yno-experimental")),
4546
compileDir("tests/pos-special/java-param-names", defaultOptions.withJavacOnlyOptions("-parameters")),
4647
compileFile(
4748
// succeeds despite -Xfatal-warnings because of -nowarn
@@ -178,7 +179,7 @@ class CompilationTests {
178179
compileFile("tests/neg-custom-args/matchable.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")),
179180
compileFile("tests/neg-custom-args/i7314.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")),
180181
compileFile("tests/neg-custom-args/feature-shadowing.scala", defaultOptions.and("-Xfatal-warnings", "-feature")),
181-
compileDir("tests/neg-custom-args/hidden-type-errors", defaultOptions.and("-explain")),
182+
compileDir("tests/neg-custom-args/hidden-type-errors", defaultOptions.and("-explain")),
182183
).checkExpectedErrors()
183184
}
184185

docs/docs/reference/other-new-features/experimental-defs.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ This annotation can be placed on term or type definitions.
1010

1111
### References to experimental definitions
1212

13-
Experimental definitions can only be referenced in an experimental scope. Experimental scopes are defined as follows.
13+
- Experimental definitions can only be referenced in an experimental scope.
14+
- Experimental language features can be imported in an experimental scope.
15+
16+
Experimental scopes are defined as follows.
1417

1518
(1) The RHS of an experimental `def`, `val`, `var`, `given` or `type` is an experimental scope.
1619

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class Test7 {
2+
import scala.language.experimental
3+
import experimental.genericNumberLiterals // error: no aliases can be used to refer to a language import
4+
val x: BigInt = 13232202002020202020202 // error
5+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import annotation.experimental
2+
3+
@experimental
4+
object Object1:
5+
import language.experimental.fewerBraces
6+
import language.experimental.namedTypeArguments
7+
import language.experimental.genericNumberLiterals
8+
import language.experimental.erasedDefinitions
9+
erased def f = 1
10+
11+
object Object2:
12+
import language.experimental.fewerBraces // error
13+
import language.experimental.namedTypeArguments // error
14+
import language.experimental.genericNumberLiterals // error
15+
import language.experimental.erasedDefinitions
16+
erased def f = 1
17+
18+
@experimental
19+
object Class1:
20+
import language.experimental.fewerBraces
21+
import language.experimental.namedTypeArguments
22+
import language.experimental.genericNumberLiterals
23+
import language.experimental.erasedDefinitions
24+
erased def f = 1
25+
26+
object Class2:
27+
import language.experimental.fewerBraces // error
28+
import language.experimental.namedTypeArguments // error
29+
import language.experimental.genericNumberLiterals // error
30+
import language.experimental.erasedDefinitions
31+
erased def f = 1
32+
33+
@experimental
34+
def fun1 =
35+
import language.experimental.fewerBraces
36+
import language.experimental.namedTypeArguments
37+
import language.experimental.genericNumberLiterals
38+
import language.experimental.erasedDefinitions
39+
erased def f = 1
40+
41+
def fun2 =
42+
import language.experimental.fewerBraces // error
43+
import language.experimental.namedTypeArguments // error
44+
import language.experimental.genericNumberLiterals // error
45+
import language.experimental.erasedDefinitions
46+
erased def f = 1
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import annotation.experimental
2+
3+
class Class1:
4+
import language.experimental.fewerBraces // error
5+
import language.experimental.namedTypeArguments // error
6+
import language.experimental.genericNumberLiterals // error
7+
import language.experimental.erasedDefinitions // ok: only check at erased definition
8+
@experimental def f = 1
9+
def g = 1
10+
11+
object Object1:
12+
import language.experimental.fewerBraces // error
13+
import language.experimental.namedTypeArguments // error
14+
import language.experimental.genericNumberLiterals // error
15+
import language.experimental.erasedDefinitions // ok: only check at erased definition
16+
@experimental def f = 1
17+
def g = 1
18+
19+
def fun1 =
20+
import language.experimental.fewerBraces // error
21+
import language.experimental.namedTypeArguments // error
22+
import language.experimental.genericNumberLiterals // error
23+
import language.experimental.erasedDefinitions // ok: only check at erased definition
24+
@experimental def f = 1
25+
def g = 1
26+
27+
val value1 =
28+
import language.experimental.fewerBraces // error
29+
import language.experimental.namedTypeArguments // error
30+
import language.experimental.genericNumberLiterals // error
31+
import language.experimental.erasedDefinitions // ok: only check at erased definition
32+
@experimental def f = 1
33+
def g = 1
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import annotation.experimental
2+
3+
class Class1:
4+
import language.experimental.fewerBraces // error
5+
import language.experimental.namedTypeArguments // error
6+
import language.experimental.genericNumberLiterals // error
7+
import language.experimental.erasedDefinitions // ok: only check at erased definition
8+
9+
object Object1:
10+
import language.experimental.fewerBraces // error
11+
import language.experimental.namedTypeArguments // error
12+
import language.experimental.genericNumberLiterals // error
13+
import language.experimental.erasedDefinitions // ok: only check at erased definition
14+
15+
def fun1 =
16+
import language.experimental.fewerBraces // error
17+
import language.experimental.namedTypeArguments // error
18+
import language.experimental.genericNumberLiterals // error
19+
import language.experimental.erasedDefinitions // ok: only check at erased definition
20+
21+
val value1 =
22+
import language.experimental.fewerBraces // error
23+
import language.experimental.namedTypeArguments // error
24+
import language.experimental.genericNumberLiterals // error
25+
import language.experimental.erasedDefinitions // ok: only check at erased definition
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import annotation.experimental
2+
3+
class Class1:
4+
import language.experimental.fewerBraces // error
5+
import language.experimental.namedTypeArguments // error
6+
import language.experimental.genericNumberLiterals // error
7+
import language.experimental.erasedDefinitions // error
8+
@experimental def f = 1
9+
10+
object Object1:
11+
import language.experimental.fewerBraces // error
12+
import language.experimental.namedTypeArguments // error
13+
import language.experimental.genericNumberLiterals // error
14+
import language.experimental.erasedDefinitions // error
15+
@experimental def f = 1
16+
17+
def fun1 =
18+
import language.experimental.fewerBraces // error
19+
import language.experimental.namedTypeArguments // error
20+
import language.experimental.genericNumberLiterals // error
21+
import language.experimental.erasedDefinitions // error
22+
@experimental def f = 1
23+
24+
val value1 =
25+
import language.experimental.fewerBraces // error
26+
import language.experimental.namedTypeArguments // error
27+
import language.experimental.genericNumberLiterals // error
28+
import language.experimental.erasedDefinitions // error
29+
@experimental def f = 1

tests/neg-custom-args/no-experimental/experimental.scala

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,3 @@ class Test2 {
2626
class Test6 {
2727
import scala.language.experimental // ok
2828
}
29-
30-
class Test7 {
31-
import scala.language.experimental
32-
import experimental.genericNumberLiterals // error: no aliases can be used to refer to a language import
33-
val x: BigInt = 13232202002020202020202 // error
34-
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import language.experimental.erasedDefinitions
2+
import annotation.experimental
3+
4+
@experimental
5+
erased def f = 1

0 commit comments

Comments
 (0)