|
| 1 | +--- |
| 2 | +layout: doc-page |
| 3 | +title: "Experimental definitions" |
| 4 | +--- |
| 5 | + |
| 6 | +## Experimental definitions |
| 7 | + |
| 8 | +The `@experimental` annotation allows the definition of an API that is not guaranteed backward binary or source compatibility. |
| 9 | +This annotation can be placed on term or type definitions. |
| 10 | + |
| 11 | +### References to experimental definitions |
| 12 | + |
| 13 | +Experimental definitions can only be referenced in an experimental scope. Experimental scopes are defined as follows. |
| 14 | + |
| 15 | +(1) The RHS of an experimental `def`, `val`, `var`, `given` or `type` is an experimental scope. |
| 16 | + |
| 17 | +<details> |
| 18 | +<summary>Examples</summary> |
| 19 | + |
| 20 | +```scala |
| 21 | +import scala.annotation.experimental |
| 22 | + |
| 23 | +@experimental |
| 24 | +def x = () |
| 25 | + |
| 26 | +def d1 = x // error: value x is marked @experimental and therefore ... |
| 27 | +@experimental def d2 = x |
| 28 | + |
| 29 | +val v1 = x // error: value x is marked @experimental and therefore ... |
| 30 | +@experimental val v2 = x |
| 31 | + |
| 32 | +var vr1 = x // error: value x is marked @experimental and therefore ... |
| 33 | +@experimental var vr2 = x |
| 34 | + |
| 35 | +lazy val lv1 = x // error: value x is marked @experimental and therefore ... |
| 36 | +@experimental lazy val lv2 = x |
| 37 | +``` |
| 38 | + |
| 39 | +```scala |
| 40 | +import scala.annotation.experimental |
| 41 | + |
| 42 | +@experimental |
| 43 | +val x = () |
| 44 | + |
| 45 | +@experimental |
| 46 | +def f() = () |
| 47 | + |
| 48 | +@experimental |
| 49 | +object X: |
| 50 | + def fx() = 1 |
| 51 | + |
| 52 | +def test1: Unit = |
| 53 | + f() // error: def f is marked @experimental and therefore ... |
| 54 | + x // error: value x is marked @experimental and therefore ... |
| 55 | + X.fx() // error: object X is marked @experimental and therefore ... |
| 56 | + import X.fx |
| 57 | + fx() // error: object X is marked @experimental and therefore ... |
| 58 | + |
| 59 | +@experimental |
| 60 | +def test2: Unit = |
| 61 | + // references to f, x and X are ok because `test2` is experimental |
| 62 | + f() |
| 63 | + x |
| 64 | + X.fx() |
| 65 | + import X.fx |
| 66 | + fx() |
| 67 | +``` |
| 68 | + |
| 69 | +```scala |
| 70 | +import scala.annotation.experimental |
| 71 | + |
| 72 | +@experimental type E |
| 73 | + |
| 74 | +type A = E // error |
| 75 | +@experimental type B = E |
| 76 | +``` |
| 77 | + |
| 78 | +```scala |
| 79 | +import scala.annotation.experimental |
| 80 | + |
| 81 | +@experimental class A |
| 82 | +@experimental type X |
| 83 | +@experimental type Y = Int |
| 84 | +@experimental opaque type Z = Int |
| 85 | + |
| 86 | +def test: Unit = |
| 87 | + new A // error: class A is marked @experimental and therefore ... |
| 88 | + val i0: A = ??? // error: class A is marked @experimental and therefore ... |
| 89 | + val i1: X = ??? // error: type X is marked @experimental and therefore ... |
| 90 | + val i2: Y = ??? // error: type Y is marked @experimental and therefore ... |
| 91 | + val i2: Z = ??? // error: type Y is marked @experimental and therefore ... |
| 92 | + () |
| 93 | +``` |
| 94 | + |
| 95 | +```scala |
| 96 | +@experimental |
| 97 | +trait ExpSAM { |
| 98 | + def foo(x: Int): Int |
| 99 | +} |
| 100 | +def bar(f: ExpSAM): Unit = {} // error: error form rule 2 |
| 101 | + |
| 102 | +def test: Unit = |
| 103 | + bar(x => x) // error: reference to experimental SAM |
| 104 | + () |
| 105 | +``` |
| 106 | + |
| 107 | +</details> |
| 108 | + |
| 109 | +(2.) The signatures of an experimental `def`, `val`, `var`, `given` and `type`, or constructors of `class` and `trait` are experimental scopes. |
| 110 | + |
| 111 | +<details> |
| 112 | +<summary>Examples</summary> |
| 113 | + |
| 114 | +```scala |
| 115 | +import scala.annotation.experimental |
| 116 | + |
| 117 | +@experimental def x = 2 |
| 118 | +@experimental class A |
| 119 | +@experimental type X |
| 120 | +@experimental type Y = Int |
| 121 | +@experimental opaque type Z = Int |
| 122 | + |
| 123 | +def test1( |
| 124 | + p1: A, // error: class A is marked @experimental and therefore ... |
| 125 | + p2: List[A], // error: class A is marked @experimental and therefore ... |
| 126 | + p3: X, // error: type X is marked @experimental and therefore ... |
| 127 | + p4: Y, // error: type Y is marked @experimental and therefore ... |
| 128 | + p5: Z, // error: type Z is marked @experimental and therefore ... |
| 129 | + p6: Any = x // error: def x is marked @experimental and therefore ... |
| 130 | +): A = ??? // error: class A is marked @experimental and therefore ... |
| 131 | + |
| 132 | +@experimental def test2( |
| 133 | + p1: A, |
| 134 | + p2: List[A], |
| 135 | + p3: X, |
| 136 | + p4: Y, |
| 137 | + p5: Z, |
| 138 | + p6: Any = x |
| 139 | +): A = ??? |
| 140 | + |
| 141 | +class Test1( |
| 142 | + p1: A, // error |
| 143 | + p2: List[A], // error |
| 144 | + p3: X, // error |
| 145 | + p4: Y, // error |
| 146 | + p5: Z, // error |
| 147 | + p6: Any = x // error |
| 148 | +) {} |
| 149 | + |
| 150 | +@experimental class Test2( |
| 151 | + p1: A, |
| 152 | + p2: List[A], |
| 153 | + p3: X, |
| 154 | + p4: Y, |
| 155 | + p5: Z, |
| 156 | + p6: Any = x |
| 157 | +) {} |
| 158 | + |
| 159 | +trait Test1( |
| 160 | + p1: A, // error |
| 161 | + p2: List[A], // error |
| 162 | + p3: X, // error |
| 163 | + p4: Y, // error |
| 164 | + p5: Z, // error |
| 165 | + p6: Any = x // error |
| 166 | +) {} |
| 167 | + |
| 168 | +@experimental trait Test2( |
| 169 | + p1: A, |
| 170 | + p2: List[A], |
| 171 | + p3: X, |
| 172 | + p4: Y, |
| 173 | + p5: Z, |
| 174 | + p6: Any = x |
| 175 | +) {} |
| 176 | +``` |
| 177 | + |
| 178 | +</details> |
| 179 | + |
| 180 | +(3.) The extension clause of an experimental `class`, `trait`, `object` are experimental scopes. |
| 181 | + |
| 182 | +<details> |
| 183 | +<summary>Examples</summary> |
| 184 | + |
| 185 | +```scala |
| 186 | +import scala.annotation.experimental |
| 187 | + |
| 188 | +@experimental def x = 2 |
| 189 | + |
| 190 | +@experimental class A1(x: Any) |
| 191 | +class A2(x: Any) |
| 192 | + |
| 193 | + |
| 194 | +@experimental class B1 extends A1(1) |
| 195 | +class B2 extends A1(1) // error: class A1 is marked @experimental and therefore marked @experimental and therefore ... |
| 196 | + |
| 197 | +@experimental class C1 extends A2(x) |
| 198 | +class C2 extends A2(x) // error def x is marked @experimental and therefore |
| 199 | +``` |
| 200 | + |
| 201 | +</details> |
| 202 | + |
| 203 | +(4.) Members of an experimental `class`, `trait` or `object` are in experimental scopes. |
| 204 | + |
| 205 | +<details> |
| 206 | +<summary>Examples</summary> |
| 207 | + |
| 208 | +```scala |
| 209 | +import scala.annotation.experimental |
| 210 | + |
| 211 | +@experimental def x = 2 |
| 212 | + |
| 213 | +@experimental class A { |
| 214 | + def f = x // ok because A is experimental |
| 215 | +} |
| 216 | + |
| 217 | +@experimental class B { |
| 218 | + def f = x // ok because A is experimental |
| 219 | +} |
| 220 | + |
| 221 | +@experimental object C { |
| 222 | + def f = x // ok because A is experimental |
| 223 | +} |
| 224 | + |
| 225 | +@experimental class D { |
| 226 | + def f = { |
| 227 | + object B { |
| 228 | + x // ok because A is experimental |
| 229 | + } |
| 230 | + } |
| 231 | +} |
| 232 | +``` |
| 233 | + |
| 234 | +</details> |
| 235 | + |
| 236 | +(5.) Annotations of an experimental definition are in experimental scopes. |
| 237 | + |
| 238 | +<details> |
| 239 | +<summary>Examples</summary> |
| 240 | + |
| 241 | +```scala |
| 242 | +import scala.annotation.experimental |
| 243 | + |
| 244 | +@experimental class myExperimentalAnnot extends scala.annotation.Annotation |
| 245 | + |
| 246 | +@myExperimentalAnnot // error |
| 247 | +def test: Unit = () |
| 248 | + |
| 249 | +@experimental |
| 250 | +@myExperimentalAnnot |
| 251 | +def test: Unit = () |
| 252 | +``` |
| 253 | + |
| 254 | +</details> |
| 255 | + |
| 256 | +(6.) Any code compiled using a _Nightly_ or _Snapshot_ version of the compiler is considered to be in an experimental scope. |
| 257 | +Can use the `-Yno-experimental` compiler flag to disable it and run as a proper release. |
| 258 | + |
| 259 | +In any other situation, a reference to an experimental definition will cause a compilation error. |
| 260 | + |
| 261 | +> Question: Should we disallow `@experimental def main(...)`? |
| 262 | +
|
| 263 | +### Experimental inheritance |
| 264 | + |
| 265 | +All subclasses of an experimental `class` or `trait` must be marked as `@experimental` even if they are in an experimental scope. |
| 266 | +Anonymous classes and SAMs of experimental classes are considered experimental. |
| 267 | + |
| 268 | +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. |
| 269 | + |
| 270 | +### Experimental overriding |
| 271 | + |
| 272 | +For an overriding member `M` and overridden member `O`, if `O` is non-experimental then `M` must be non-experimental. |
| 273 | + |
| 274 | +This makes sure that we cannot have accidental binary incompatibilities such as the following change. |
| 275 | +```diff |
| 276 | +class A: |
| 277 | + def f: Any = 1 |
| 278 | +class B extends A: |
| 279 | +- @experimental def f: Int = 2 |
| 280 | +``` |
| 281 | +### Test frameworks |
| 282 | + |
| 283 | +Tests can be defined as experimental. Tests frameworks can execute tests using reflection even if they are in an experimental class, object or method. |
| 284 | + |
| 285 | +Test that touch experimental APIs can be written as follows |
| 286 | + |
| 287 | +```scala |
| 288 | +import scala.annotation.experimental |
| 289 | + |
| 290 | +@experimental def x = 2 |
| 291 | + |
| 292 | +class MyTests { |
| 293 | + /*@Test*/ def test1 = x // error |
| 294 | + @experimental /*@Test*/ def test2 = x |
| 295 | +} |
| 296 | + |
| 297 | +@experimental |
| 298 | +class MyExperimentalTests { |
| 299 | + /*@Test*/ def test1 = x |
| 300 | + /*@Test*/ def test2 = x |
| 301 | +} |
| 302 | +``` |
0 commit comments