From 1828dc160a7b0c099021aee908aeb437d6ba7a20 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 17 Aug 2021 10:04:42 +0200 Subject: [PATCH 1/4] Change @experimental spec --- .../src/dotty/tools/dotc/config/Feature.scala | 13 +- .../dotty/tools/dotc/transform/SymUtils.scala | 18 +- .../dotty/tools/dotc/typer/RefChecks.scala | 19 +- .../other-new-features/experimental-defs.md | 305 ++++++++++++++++++ docs/sidebar.yml | 1 + .../internal/experimentalTest.scala | 10 + .../src/scala/annotation/experimental.scala | 8 +- .../no-experimental/experimentalAnnot.scala | 9 +- .../experimentalCaseClass.scala | 4 +- .../experimentalDefaultParams.scala | 27 ++ .../no-experimental/experimentalEnum.scala | 2 +- .../experimentalExperimental.scala | 4 - .../experimentalInheritance.scala | 14 + .../no-experimental/experimentalMembers.scala | 39 +++ .../experimentalOverride.scala | 8 +- .../no-experimental/experimentalRHS.scala | 16 + .../no-experimental/experimentalSam.scala | 2 +- .../experimentalSignature.scala | 54 ++++ .../no-experimental/experimentalTerms.scala | 26 +- .../no-experimental/experimentalTests.scala | 15 + .../no-experimental/experimentalType.scala | 29 +- .../no-experimental/experimentalTypeRHS.scala | 6 + .../no-experimental/experimentalTypes2.scala | 14 + .../no-experimental/experimentalUnapply.scala | 4 +- .../no-experimental/i13091.scala | 2 +- tests/neg/experimentalExperimental.scala | 1 - tests/neg/experimentalInheritance2.scala | 6 + tests/pos/experimentalExperimental.scala | 1 + tests/run/experimentalRun.scala | 6 + 29 files changed, 584 insertions(+), 79 deletions(-) create mode 100644 docs/docs/reference/other-new-features/experimental-defs.md create mode 100644 library/src-non-bootstrapped/scala/annotation/internal/experimentalTest.scala create mode 100644 tests/neg-custom-args/no-experimental/experimentalDefaultParams.scala delete mode 100644 tests/neg-custom-args/no-experimental/experimentalExperimental.scala create mode 100644 tests/neg-custom-args/no-experimental/experimentalInheritance.scala create mode 100644 tests/neg-custom-args/no-experimental/experimentalMembers.scala create mode 100644 tests/neg-custom-args/no-experimental/experimentalRHS.scala create mode 100644 tests/neg-custom-args/no-experimental/experimentalSignature.scala create mode 100644 tests/neg-custom-args/no-experimental/experimentalTests.scala create mode 100644 tests/neg-custom-args/no-experimental/experimentalTypeRHS.scala create mode 100644 tests/neg-custom-args/no-experimental/experimentalTypes2.scala delete mode 100644 tests/neg/experimentalExperimental.scala create mode 100644 tests/neg/experimentalInheritance2.scala create mode 100644 tests/pos/experimentalExperimental.scala create mode 100644 tests/run/experimentalRun.scala diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 15c2baca1a2f..df2cab2c3375 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -97,8 +97,6 @@ object Feature: else false - private val assumeExperimentalIn = Set("dotty.tools.vulpix.ParallelTesting") - def checkExperimentalFeature(which: String, srcPos: SrcPos)(using Context) = if !isExperimentalEnabled then report.error(i"Experimental $which may only be used with a nightly or snapshot version of the compiler", srcPos) @@ -106,15 +104,13 @@ object Feature: def checkExperimentalDef(sym: Symbol, srcPos: SrcPos)(using Context) = if !isExperimentalEnabled then val symMsg = - if sym eq defn.ExperimentalAnnot then - i"use of @experimental is experimental" - else if sym.hasAnnotation(defn.ExperimentalAnnot) then + if sym.hasAnnotation(defn.ExperimentalAnnot) then i"$sym is marked @experimental" else if sym.owner.hasAnnotation(defn.ExperimentalAnnot) then i"${sym.owner} is marked @experimental" else i"$sym inherits @experimental" - report.error(s"$symMsg and therefore may only be used with a nightly or snapshot version of the compiler", srcPos) + report.error(s"$symMsg and therefore may only be used in an experimental scope.", srcPos) /** Check that experimental compiler options are only set for snapshot or nightly compiler versions. */ def checkExperimentalSettings(using Context): Unit = @@ -123,9 +119,6 @@ object Feature: do checkExperimentalFeature(s"feature $setting", NoSourcePosition) def isExperimentalEnabled(using Context): Boolean = - def hasSpecialPermission = - Thread.currentThread.getStackTrace.exists(elem => - assumeExperimentalIn.exists(elem.getClassName().startsWith(_))) - (Properties.experimental || hasSpecialPermission) && !ctx.settings.YnoExperimental.value + Properties.experimental && !ctx.settings.YnoExperimental.value end Feature \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 691425bfb713..353ebee25c29 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -267,11 +267,23 @@ object SymUtils: /** Is symbol declared or inherits @experimental? */ def isExperimental(using Context): Boolean = - // TODO should be add `@experimental` to `class experimental` in PostTyper? - self.eq(defn.ExperimentalAnnot) - || self.hasAnnotation(defn.ExperimentalAnnot) + self.hasAnnotation(defn.ExperimentalAnnot) || (self.maybeOwner.isClass && self.owner.hasAnnotation(defn.ExperimentalAnnot)) + def isInExperimentalScope(using Context): Boolean = + def isDefaultArgumentOfExperimentalMethod = + self.name.is(DefaultGetterName) + && self.owner.isClass + && { + val overloads = self.owner.asClass.membersNamed(self.name.firstPart) + overloads.filterWithFlags(HasDefaultParams, EmptyFlags) match + case denot: SymDenotation => denot.symbol.isExperimental + case _ => false + } + self.hasAnnotation(defn.ExperimentalAnnot) + || isDefaultArgumentOfExperimentalMethod + || (!self.is(Package) && self.owner.isInExperimentalScope) + /** The declared self type of this class, as seen from `site`, stripping * all refinements for opaque types. */ diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 6e9d4caa7f6a..234a60745357 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -948,27 +948,23 @@ object RefChecks { report.deprecationWarning(s"${sym.showLocated} is deprecated${since}${msg}", pos) private def checkExperimental(sym: Symbol, pos: SrcPos)(using Context): Unit = - if sym.isExperimental - && !sym.isConstructor // already reported on the class - && !ctx.owner.isExperimental // already reported on the @experimental of the owner - && !sym.is(ModuleClass) // already reported on the module - && (sym.span.exists || sym != defn.ExperimentalAnnot) // already reported on inferred annotations - then + if sym.isExperimental && !ctx.owner.isInExperimentalScope then Feature.checkExperimentalDef(sym, pos) private def checkExperimentalSignature(sym: Symbol, pos: SrcPos)(using Context): Unit = - val checker = new TypeTraverser: + class Checker extends TypeTraverser: def traverse(tp: Type): Unit = if tp.typeSymbol.isExperimental then Feature.checkExperimentalDef(tp.typeSymbol, pos) else traverseChildren(tp) - if !sym.owner.isExperimental && !pos.span.isSynthetic then // avoid double errors - checker.traverse(sym.info) + if !sym.isInExperimentalScope then + new Checker().traverse(sym.info) private def checkExperimentalAnnots(sym: Symbol)(using Context): Unit = - for annot <- sym.annotations if annot.symbol.isExperimental && annot.tree.span.exists do - Feature.checkExperimentalDef(annot.symbol, annot.tree) + if !sym.isExperimental then + for annot <- sym.annotations if annot.symbol.isExperimental && annot.tree.span.exists do + Feature.checkExperimentalDef(annot.symbol, annot.tree) /** If @migration is present (indicating that the symbol has changed semantics between versions), * emit a warning. @@ -1338,7 +1334,6 @@ class RefChecks extends MiniPhase { thisPhase => } override def transformTypeDef(tree: TypeDef)(using Context): TypeDef = { - checkExperimental(tree.symbol, tree.srcPos) checkExperimentalAnnots(tree.symbol) tree } diff --git a/docs/docs/reference/other-new-features/experimental-defs.md b/docs/docs/reference/other-new-features/experimental-defs.md new file mode 100644 index 000000000000..de010bd38793 --- /dev/null +++ b/docs/docs/reference/other-new-features/experimental-defs.md @@ -0,0 +1,305 @@ +--- +layout: doc-page +title: "Experimental definitions" +--- + +## Experimental definitions + +The `@experimental` annotation allows the definition of an API that is not guaranteed backward binary or source compatibility. +This annotation can be placed on term or type definitions. + +### References to experimental definitions + +Experimental definitions can only be referenced in an experimental scope. Experimental scopes are defined as follows. + +(1) The RHS of an experimental `def`, `val`, `var`, `given` or `type` is an experimental scope. + +
+Examples + +```scala +import scala.annotation.experimental + +@experimental +def x = () + +def d1 = x // error: value x is marked @experimental and therefore ... +@experimental def d2 = x + +val v1 = x // error: value x is marked @experimental and therefore ... +@experimental val v2 = x + +var vr1 = x // error: value x is marked @experimental and therefore ... +@experimental var vr2 = x + +lazy val lv1 = x // error: value x is marked @experimental and therefore ... +@experimental lazy val lv2 = x +``` + +```scala +import scala.annotation.experimental + +@experimental +val x = () + +@experimental +def f() = () + +@experimental +object X: + def fx() = 1 + +def test1: Unit = + f() // error: def f is marked @experimental and therefore ... + x // error: value x is marked @experimental and therefore ... + X.fx() // error: object X is marked @experimental and therefore ... + import X.fx + fx() // error: object X is marked @experimental and therefore ... + +@experimental +def test2: Unit = + // references to f, x and X are ok because `test2` is experimental + f() + x + X.fx() + import X.fx + fx() +``` + +```scala +import scala.annotation.experimental + +@experimental type E + +type A = E // error type E is marked @experimental and therefore ... +@experimental type B = E +``` + +```scala +import scala.annotation.experimental + +@experimental class A +@experimental type X +@experimental type Y = Int +@experimental opaque type Z = Int + +def test: Unit = + new A // error: class A is marked @experimental and therefore ... + val i0: A = ??? // error: class A is marked @experimental and therefore ... + val i1: X = ??? // error: type X is marked @experimental and therefore ... + val i2: Y = ??? // error: type Y is marked @experimental and therefore ... + val i2: Z = ??? // error: type Y is marked @experimental and therefore ... + () +``` + +```scala +@experimental +trait ExpSAM { + def foo(x: Int): Int +} +def bar(f: ExpSAM): Unit = {} // error: error form rule 2 + +def test: Unit = + bar(x => x) // error: reference to experimental SAM + () +``` + +
+ +(2.) The signatures of an experimental `def`, `val`, `var`, `given` and `type`, or constructors of `class` and `trait` are experimental scopes. + +
+Examples + +```scala +import scala.annotation.experimental + +@experimental def x = 2 +@experimental class A +@experimental type X +@experimental type Y = Int +@experimental opaque type Z = Int + +def test1( + p1: A, // error: class A is marked @experimental and therefore ... + p2: List[A], // error: class A is marked @experimental and therefore ... + p3: X, // error: type X is marked @experimental and therefore ... + p4: Y, // error: type Y is marked @experimental and therefore ... + p5: Z, // error: type Z is marked @experimental and therefore ... + p6: Any = x // error: def x is marked @experimental and therefore ... +): A = ??? // error: class A is marked @experimental and therefore ... + +@experimental def test2( + p1: A, + p2: List[A], + p3: X, + p4: Y, + p5: Z, + p6: Any = x +): A = ??? + +class Test1( + p1: A, // error + p2: List[A], // error + p3: X, // error + p4: Y, // error + p5: Z, // error + p6: Any = x // error +) {} + +@experimental class Test2( + p1: A, + p2: List[A], + p3: X, + p4: Y, + p5: Z, + p6: Any = x +) {} + +trait Test1( + p1: A, // error + p2: List[A], // error + p3: X, // error + p4: Y, // error + p5: Z, // error + p6: Any = x // error +) {} + +@experimental trait Test2( + p1: A, + p2: List[A], + p3: X, + p4: Y, + p5: Z, + p6: Any = x +) {} +``` + +
+ +(3.) The extension clause of an experimental `class`, `trait`, `object` are experimental scopes. + +
+Examples + +```scala +import scala.annotation.experimental + +@experimental def x = 2 + +@experimental class A1(x: Any) +class A2(x: Any) + + +@experimental class B1 extends A1(1) +class B2 extends A1(1) // error: class A1 is marked @experimental and therefore marked @experimental and therefore ... + +@experimental class C1 extends A2(x) +class C2 extends A2(x) // error def x is marked @experimental and therefore +``` + +
+ +(4.) Members of an experimental `class`, `trait` or `object` are in experimental scopes. + +
+Examples + +```scala +import scala.annotation.experimental + +@experimental def x = 2 + +@experimental class A { + def f = x // ok because A is experimental +} + +@experimental class B { + def f = x // ok because A is experimental +} + +@experimental object C { + def f = x // ok because A is experimental +} + +@experimental class D { + def f = { + object B { + x // ok because A is experimental + } + } +} +``` + +
+ +(5.) Annotations of an experimental definition are in experimental scopes. + +
+Examples + +```scala +import scala.annotation.experimental + +@experimental class myExperimentalAnnot extends scala.annotation.Annotation + +@myExperimentalAnnot // error +def test: Unit = () + +@experimental +@myExperimentalAnnot +def test: Unit = () +``` + +
+ +(6.) Any code compiled using a _Nightly_ or _Snapshot_ version of the compiler is considered to be in an experimental scope. +Can use the `-Yno-experimental` compiler flag to disable it and run as a proper release. + +In any other situation, a reference to an experimental definition will cause a compilation error. + +### Experimental inheritance + +All subclasses of an experimental `class` or `trait` must be marked as `@experimental` even if they are in an experimental scope. +Anonymous classes and SAMs of experimental classes are considered experimental. + +We require explicit annotations to make sure we do not have completion or cycles issues with nested classes. This restriction could be relaxed in the future. + +### Experimental overriding + +For an overriding member `M` and overridden member `O`, if `O` is non-experimental then `M` must be non-experimental. + +This makes sure that we cannot have accidental binary incompatibilities such as the following change. +```diff +class A: + def f: Any = 1 +class B extends A: +- @experimental def f: Int = 2 +``` +### Test frameworks + +Tests can be defined as experimental. Tests frameworks can execute tests using reflection even if they are in an experimental class, object or method. + +
+Examples + +Test that touch experimental APIs can be written as follows + +```scala +import scala.annotation.experimental + +@experimental def x = 2 + +class MyTests { + /*@Test*/ def test1 = x // error + @experimental /*@Test*/ def test2 = x +} + +@experimental +class MyExperimentalTests { + /*@Test*/ def test1 = x + /*@Test*/ def test2 = x +} +``` + +
diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 471382fa292f..4983c1e4b33a 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -67,6 +67,7 @@ sidebar: - page: docs/reference/other-new-features/explicit-nulls.md - page: docs/reference/other-new-features/safe-initialization.md - page: docs/reference/other-new-features/type-test.md + - page: docs/reference/other-new-features/experimental-defs.md - title: Other Changed Features subsection: - page: docs/reference/changed-features/numeric-literals.md diff --git a/library/src-non-bootstrapped/scala/annotation/internal/experimentalTest.scala b/library/src-non-bootstrapped/scala/annotation/internal/experimentalTest.scala new file mode 100644 index 000000000000..1240f9039670 --- /dev/null +++ b/library/src-non-bootstrapped/scala/annotation/internal/experimentalTest.scala @@ -0,0 +1,10 @@ +// KEEP IN NON-BOOTSTRAPPED SOURCES +package scala.annotation +package internal + +/** This is a dummy definition that tests that the stdlib can be compiled with an experimental definition. + * This might be redundant, we keep it definition in case there are no other @experimental definitions in the library. + * As this definition is in `src-non-bootstrapped`, it will not be published. + * It may accidentally be visible while compiling the non-bootstrapped library. + */ +@experimental def testExperimental = 4 diff --git a/library/src/scala/annotation/experimental.scala b/library/src/scala/annotation/experimental.scala index 4ccda84d623c..3d7a023176e3 100644 --- a/library/src/scala/annotation/experimental.scala +++ b/library/src/scala/annotation/experimental.scala @@ -2,13 +2,7 @@ package scala.annotation /** An annotation that can be used to mark a definition as experimental. * - * This class is experimental as well as if it was defined as - * ```scala - * @experimental - * class experimental extends StaticAnnotation - * ``` - * + * @see [[https://dotty.epfl.ch/docs/reference/other-new-features/experimental-defs]] * @syntax markdown */ -// @experimental class experimental extends StaticAnnotation diff --git a/tests/neg-custom-args/no-experimental/experimentalAnnot.scala b/tests/neg-custom-args/no-experimental/experimentalAnnot.scala index 3bd87d67ed7b..b22a845b4be7 100644 --- a/tests/neg-custom-args/no-experimental/experimentalAnnot.scala +++ b/tests/neg-custom-args/no-experimental/experimentalAnnot.scala @@ -1,7 +1,10 @@ import scala.annotation.experimental -@experimental // error -class myExperimentalAnnot extends scala.annotation.Annotation +@experimental class myExperimentalAnnot extends scala.annotation.Annotation @myExperimentalAnnot // error -def test: Unit = () +def test1: Unit = () + +@experimental +@myExperimentalAnnot +def test2: Unit = () diff --git a/tests/neg-custom-args/no-experimental/experimentalCaseClass.scala b/tests/neg-custom-args/no-experimental/experimentalCaseClass.scala index 51d5b4957993..b112c8a1213a 100644 --- a/tests/neg-custom-args/no-experimental/experimentalCaseClass.scala +++ b/tests/neg-custom-args/no-experimental/experimentalCaseClass.scala @@ -1,9 +1,9 @@ import scala.annotation.experimental -@experimental // error +@experimental case class Foo(a: Int) -@experimental // error +@experimental case class Bar(a: Int) object Bar: diff --git a/tests/neg-custom-args/no-experimental/experimentalDefaultParams.scala b/tests/neg-custom-args/no-experimental/experimentalDefaultParams.scala new file mode 100644 index 000000000000..4dedb3afa11d --- /dev/null +++ b/tests/neg-custom-args/no-experimental/experimentalDefaultParams.scala @@ -0,0 +1,27 @@ +import scala.annotation.experimental + +@experimental def x = 2 + +def test1( + p6: Any = x // error: def x is marked @experimental and therefore ... +): Any = ??? + +@experimental def test2( + p6: Any = x +): Any = ??? + +class Test1( + p6: Any = x // error +) {} + +@experimental class Test2( + p6: Any = x +) {} + +trait Test3( + p6: Any = x // error +) {} + +@experimental trait Test4( + p6: Any = x +) {} diff --git a/tests/neg-custom-args/no-experimental/experimentalEnum.scala b/tests/neg-custom-args/no-experimental/experimentalEnum.scala index 3a4e3cc093f0..1cbe78ca5427 100644 --- a/tests/neg-custom-args/no-experimental/experimentalEnum.scala +++ b/tests/neg-custom-args/no-experimental/experimentalEnum.scala @@ -1,6 +1,6 @@ import scala.annotation.experimental -@experimental // error +@experimental enum E: case A case B diff --git a/tests/neg-custom-args/no-experimental/experimentalExperimental.scala b/tests/neg-custom-args/no-experimental/experimentalExperimental.scala deleted file mode 100644 index d88406d634e7..000000000000 --- a/tests/neg-custom-args/no-experimental/experimentalExperimental.scala +++ /dev/null @@ -1,4 +0,0 @@ -import scala.annotation.experimental - -class MyExperimentalAnnot // error - extends experimental // error diff --git a/tests/neg-custom-args/no-experimental/experimentalInheritance.scala b/tests/neg-custom-args/no-experimental/experimentalInheritance.scala new file mode 100644 index 000000000000..f6eab1224310 --- /dev/null +++ b/tests/neg-custom-args/no-experimental/experimentalInheritance.scala @@ -0,0 +1,14 @@ +import scala.annotation.experimental + +@experimental def x = 2 + +@experimental class A1(x: Any) +class A2(x: Any) + + +@experimental class B1 extends A1(1) +class B2 // error: extension of experimental class A1 must have @experimental annotation +extends A1(1) // error: class A1 is marked @experimental ... + +@experimental class C1 extends A2(x) +class C2 extends A2(x) // error def x is marked @experimental and therefore diff --git a/tests/neg-custom-args/no-experimental/experimentalMembers.scala b/tests/neg-custom-args/no-experimental/experimentalMembers.scala new file mode 100644 index 000000000000..e30f27b069a8 --- /dev/null +++ b/tests/neg-custom-args/no-experimental/experimentalMembers.scala @@ -0,0 +1,39 @@ +import scala.annotation.experimental + +@experimental def x = 2 + +@experimental class A { + def f = x // ok because A is experimental +} + +@experimental class B { + def f = x // ok because A is experimental +} + +@experimental object C { + def f = x // ok because A is experimental +} + +@experimental class D { + def f = { + object B { + x // ok because A is experimental + } + } +} + +@experimental class E { + def f = { + def g = { + x // ok because A is experimental + } + } +} + +class F { + def f = { + def g = { + x // error + } + } +} diff --git a/tests/neg-custom-args/no-experimental/experimentalOverride.scala b/tests/neg-custom-args/no-experimental/experimentalOverride.scala index 2a052d959f46..653bd3b23da4 100644 --- a/tests/neg-custom-args/no-experimental/experimentalOverride.scala +++ b/tests/neg-custom-args/no-experimental/experimentalOverride.scala @@ -1,22 +1,22 @@ import scala.annotation.experimental -@experimental // error +@experimental class A: def f() = 1 -@experimental // error +@experimental class B extends A: override def f() = 2 class C: - @experimental // error + @experimental def f() = 1 class D extends C: override def f() = 2 trait A2: - @experimental // error + @experimental def f(): Int trait B2: diff --git a/tests/neg-custom-args/no-experimental/experimentalRHS.scala b/tests/neg-custom-args/no-experimental/experimentalRHS.scala new file mode 100644 index 000000000000..27143c120b96 --- /dev/null +++ b/tests/neg-custom-args/no-experimental/experimentalRHS.scala @@ -0,0 +1,16 @@ +import scala.annotation.experimental + +@experimental +def x = () + +def d1 = x // error: value x is marked @experimental and therefore ... +@experimental def d2 = x + +val v1 = x // error: value x is marked @experimental and therefore ... +@experimental val v2 = x + +var vr1 = x // error: value x is marked @experimental and therefore ... +@experimental var vr2 = x + +lazy val lv1 = x // error: value x is marked @experimental and therefore ... +@experimental lazy val lv2 = x diff --git a/tests/neg-custom-args/no-experimental/experimentalSam.scala b/tests/neg-custom-args/no-experimental/experimentalSam.scala index 578fb8f93f94..cdc9e61858d9 100644 --- a/tests/neg-custom-args/no-experimental/experimentalSam.scala +++ b/tests/neg-custom-args/no-experimental/experimentalSam.scala @@ -1,6 +1,6 @@ import scala.annotation.experimental -@experimental // error +@experimental trait ExpSAM { def foo(x: Int): Int } diff --git a/tests/neg-custom-args/no-experimental/experimentalSignature.scala b/tests/neg-custom-args/no-experimental/experimentalSignature.scala new file mode 100644 index 000000000000..9b1d3c5e999f --- /dev/null +++ b/tests/neg-custom-args/no-experimental/experimentalSignature.scala @@ -0,0 +1,54 @@ +import scala.annotation.experimental + +@experimental class A +@experimental type X +@experimental type Y = Int +@experimental opaque type Z = Int + +def test1( + p1: A, // error: class A is marked @experimental and therefore ... + p2: List[A], // error: class A is marked @experimental and therefore ... + p3: X, // error: type X is marked @experimental and therefore ... + p4: Y, // error: type Y is marked @experimental and therefore ... + p5: Z, // error: type Z is marked @experimental and therefore ... +): A = ??? // error: class A is marked @experimental and therefore ... + +@experimental def test2( + p1: A, + p2: List[A], + p3: X, + p4: Y, + p5: Z, +): A = ??? + +class Test1( + p1: A, // error + p2: List[A], // error + p3: X, // error + p4: Y, // error + p5: Z, // error +) {} + +@experimental class Test2( + p1: A, + p2: List[A], + p3: X, + p4: Y, + p5: Z, +) {} + +trait Test3( + p1: A, // error + p2: List[A], // error + p3: X, // error + p4: Y, // error + p5: Z, // error +) {} + +@experimental trait Test4( + p1: A, + p2: List[A], + p3: X, + p4: Y, + p5: Z, +) {} diff --git a/tests/neg-custom-args/no-experimental/experimentalTerms.scala b/tests/neg-custom-args/no-experimental/experimentalTerms.scala index 5fc3f2b3fad7..09b69d2da381 100644 --- a/tests/neg-custom-args/no-experimental/experimentalTerms.scala +++ b/tests/neg-custom-args/no-experimental/experimentalTerms.scala @@ -1,19 +1,27 @@ import scala.annotation.experimental -@experimental // error +@experimental val x = () -@experimental // error +@experimental def f() = () -@experimental // error +@experimental object X: def fx() = 1 -def test: Unit = - f() // error - x // error - X.fx() // error +def test1: Unit = + f() // error: def f is marked @experimental and therefore ... + x // error: value x is marked @experimental and therefore ... + X.fx() // error: object X is marked @experimental and therefore ... import X.fx - fx() // error - () + fx() // error: object X is marked @experimental and therefore ... + +@experimental +def test2: Unit = + // references to f, x and X are ok because `test2` is experimental + f() + x + X.fx() + import X.fx + fx() diff --git a/tests/neg-custom-args/no-experimental/experimentalTests.scala b/tests/neg-custom-args/no-experimental/experimentalTests.scala new file mode 100644 index 000000000000..f3fbcf8c587c --- /dev/null +++ b/tests/neg-custom-args/no-experimental/experimentalTests.scala @@ -0,0 +1,15 @@ +import scala.annotation.experimental + +@experimental def x = 2 + +class MyTests { + /*@Test*/ def test1 = x // error + @experimental + /*@Test*/ def test2 = x +} + +@experimental +class MyExperimentalTests { + /*@Test*/ def test1 = x + /*@Test*/ def test2 = x +} diff --git a/tests/neg-custom-args/no-experimental/experimentalType.scala b/tests/neg-custom-args/no-experimental/experimentalType.scala index e49b53192944..f4013788796a 100644 --- a/tests/neg-custom-args/no-experimental/experimentalType.scala +++ b/tests/neg-custom-args/no-experimental/experimentalType.scala @@ -1,31 +1,22 @@ import scala.annotation.experimental -@experimental // error +@experimental class A -@experimental // error +@experimental class B extends A @experimental -type X // error +type X @experimental -type Y = Int // error +type Y = Int @experimental -opaque type Z = Int // error - -type W = Z // error +opaque type Z = Int -def test( - p1: A, // error - p2: List[A], // error - p3: X, // error - p4: Y, // error - p5: Z, // error -): Unit = - new A // error - new B // error - val i1 = identity[X] // error // error - val i2 = identity[A] // error // error - () +type AA = A // error +type BB = Z // error +type XX = Z // error +type YY = Z // error +type ZZ = Z // error diff --git a/tests/neg-custom-args/no-experimental/experimentalTypeRHS.scala b/tests/neg-custom-args/no-experimental/experimentalTypeRHS.scala new file mode 100644 index 000000000000..3aaeb960bae9 --- /dev/null +++ b/tests/neg-custom-args/no-experimental/experimentalTypeRHS.scala @@ -0,0 +1,6 @@ +import scala.annotation.experimental + +@experimental type E + +type A = E // error +@experimental type B = E diff --git a/tests/neg-custom-args/no-experimental/experimentalTypes2.scala b/tests/neg-custom-args/no-experimental/experimentalTypes2.scala new file mode 100644 index 000000000000..706fd39fd15c --- /dev/null +++ b/tests/neg-custom-args/no-experimental/experimentalTypes2.scala @@ -0,0 +1,14 @@ +import scala.annotation.experimental + +@experimental class A +@experimental type X +@experimental type Y = Int +@experimental opaque type Z = Int + +def test2: Unit = + new A // error: class A is marked @experimental and therefore ... + val i0: A = ??? // error: class A is marked @experimental and therefore ... + val i1: X = ??? // error: type X is marked @experimental and therefore ... + val i2: Y = ??? // error: type Y is marked @experimental and therefore ... + val i3: Z = ??? // error: type Z is marked @experimental and therefore ... + () diff --git a/tests/neg-custom-args/no-experimental/experimentalUnapply.scala b/tests/neg-custom-args/no-experimental/experimentalUnapply.scala index d5cd3ad55590..0ba338a15a96 100644 --- a/tests/neg-custom-args/no-experimental/experimentalUnapply.scala +++ b/tests/neg-custom-args/no-experimental/experimentalUnapply.scala @@ -1,13 +1,13 @@ import scala.annotation.experimental -@experimental // error +@experimental class A object Extractor1: def unapply(s: Any): Option[A] = ??? // error object Extractor2: - @experimental // error + @experimental def unapply(s: Any): Option[Int] = ??? def test: Unit = diff --git a/tests/neg-custom-args/no-experimental/i13091.scala b/tests/neg-custom-args/no-experimental/i13091.scala index b2e0c1844540..2b08788ebbc1 100644 --- a/tests/neg-custom-args/no-experimental/i13091.scala +++ b/tests/neg-custom-args/no-experimental/i13091.scala @@ -1,5 +1,5 @@ import annotation.experimental -@experimental class Foo // error: use of @experimental is experimental ... +@experimental class Foo def test: Unit = new Foo // error: class Foo is marked @experimental ... diff --git a/tests/neg/experimentalExperimental.scala b/tests/neg/experimentalExperimental.scala deleted file mode 100644 index 9011a3e49225..000000000000 --- a/tests/neg/experimentalExperimental.scala +++ /dev/null @@ -1 +0,0 @@ -class MyExperimentalAnnot extends scala.annotation.experimental // error diff --git a/tests/neg/experimentalInheritance2.scala b/tests/neg/experimentalInheritance2.scala new file mode 100644 index 000000000000..84668ac5850f --- /dev/null +++ b/tests/neg/experimentalInheritance2.scala @@ -0,0 +1,6 @@ +import scala.annotation.experimental + +@experimental class A + +class B // // error: extension of experimental class A1 must have @experimental annotation + extends A diff --git a/tests/pos/experimentalExperimental.scala b/tests/pos/experimentalExperimental.scala new file mode 100644 index 000000000000..4b57e5b94346 --- /dev/null +++ b/tests/pos/experimentalExperimental.scala @@ -0,0 +1 @@ +class MyExperimentalAnnot extends scala.annotation.experimental diff --git a/tests/run/experimentalRun.scala b/tests/run/experimentalRun.scala new file mode 100644 index 000000000000..2d93c15c606f --- /dev/null +++ b/tests/run/experimentalRun.scala @@ -0,0 +1,6 @@ +import scala.annotation.experimental + +@experimental +def f = 3 + +@experimental @main def Test = f From 859a81bea28bcc617ea1dc08e502dfc235ab0dfc Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 23 Aug 2021 08:34:27 +0200 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Guillaume Martres --- docs/docs/reference/other-new-features/experimental-defs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/other-new-features/experimental-defs.md b/docs/docs/reference/other-new-features/experimental-defs.md index de010bd38793..d3e42395162a 100644 --- a/docs/docs/reference/other-new-features/experimental-defs.md +++ b/docs/docs/reference/other-new-features/experimental-defs.md @@ -177,7 +177,7 @@ trait Test1( -(3.) The extension clause of an experimental `class`, `trait`, `object` are experimental scopes. +(3.) The `extends` clause of an experimental `class`, `trait` or `object` is an experimental scope.
Examples @@ -200,7 +200,7 @@ class C2 extends A2(x) // error def x is marked @experimental and therefore
-(4.) Members of an experimental `class`, `trait` or `object` are in experimental scopes. +(4.) The body of an experimental `class`, `trait` or `object` is an experimental scope.
Examples From 5cad4c2b54e587a04c97a75e5b21834204956459 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 23 Aug 2021 10:09:26 +0200 Subject: [PATCH 3/4] Remove unnecessary condition --- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 234a60745357..409e20e0feed 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -963,7 +963,7 @@ object RefChecks { private def checkExperimentalAnnots(sym: Symbol)(using Context): Unit = if !sym.isExperimental then - for annot <- sym.annotations if annot.symbol.isExperimental && annot.tree.span.exists do + for annot <- sym.annotations if annot.symbol.isExperimental do Feature.checkExperimentalDef(annot.symbol, annot.tree) /** If @migration is present (indicating that the symbol has changed semantics between versions), From 366fa623acefe5a298541caada86f71c6510fe08 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 23 Aug 2021 14:31:52 +0200 Subject: [PATCH 4/4] Check for experimental annotations only in non-experimental scopes --- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 2 +- .../no-experimental/experimentalAnnot.scala | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 409e20e0feed..53a2e4a5a4f5 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -962,7 +962,7 @@ object RefChecks { new Checker().traverse(sym.info) private def checkExperimentalAnnots(sym: Symbol)(using Context): Unit = - if !sym.isExperimental then + if !sym.isInExperimentalScope then for annot <- sym.annotations if annot.symbol.isExperimental do Feature.checkExperimentalDef(annot.symbol, annot.tree) diff --git a/tests/neg-custom-args/no-experimental/experimentalAnnot.scala b/tests/neg-custom-args/no-experimental/experimentalAnnot.scala index b22a845b4be7..e6dfbf28f8bb 100644 --- a/tests/neg-custom-args/no-experimental/experimentalAnnot.scala +++ b/tests/neg-custom-args/no-experimental/experimentalAnnot.scala @@ -8,3 +8,15 @@ def test1: Unit = () @experimental @myExperimentalAnnot def test2: Unit = () + +@experimental +class Foo { + @myExperimentalAnnot + def test3: Unit = () + + def test4: Unit = { + @myExperimentalAnnot + val f: Unit = () + f + } +}