Edit this page on GitHub

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:

    Example 1
    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
    
    Example 2
    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()
    
    Example 3
    import scala.annotation.experimental
    
    @experimental type E
    
    type A = E // error type E is marked @experimental and therefore ...
    @experimental type B = E
    
    Example 4
    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 ...
      ()
    
    Example 5
    @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:

    Example 1
    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 extends clause of an experimental class, trait or object is an experimental scope. Examples:

    Example 1
    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. The body of an experimental class, trait or object is an experimental scope. Examples:

    Example 1
    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:

    Example 1
    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 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.

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:

Example 1

Test that touch experimental APIs can be written as follows

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
}

-experimental compiler flag

This flag enables the use of any experimental language feature in the project. It does this by adding an @experimental annotation to all top-level definitions. Hence, dependent projects also have to be experimental.