diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/MatrixMultiplication.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/MatrixMultiplication.scala new file mode 100644 index 000000000000..45fdd696ea92 --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/MatrixMultiplication.scala @@ -0,0 +1,99 @@ +//> using scala 3.10.0-RC1-bin-SNAPSHOT +//> using options -language:experimental.specializedTraits + +package dotty.tools.benchmarks.specializedtraits + +import org.openjdk.jmh.annotations.* +import java.util.concurrent.TimeUnit +import org.openjdk.jmh.infra.Blackhole + +// Flat row-major layout: element (i,j) of an n x n matrix is at index i*n+j +class MatManual(val elems: Array[Int], val n: Int): + def apply(i: Int, j: Int): Int = elems(i * n + j) + + def matMul(other: MatManual, result: Array[Int]): MatManual = + require(this.n == other.n) + for + i <- 0 until n + k <- 0 until n + do + val aik = this(i, k) + for j <- 0 until n do + result(i * n + j) += aik * other(k, j) + MatManual(result, n) + +class MatGeneric[T: Numeric](val elems: Array[T], val n: Int): + private val num = summon[Numeric[T]] + + def apply(i: Int, j: Int): T = elems(i * n + j) + + def matMul(other: MatGeneric[T], result: Array[T]): MatGeneric[T] = + require(this.n == other.n) + for + i <- 0 until n + k <- 0 until n + do + val aik = this(i, k) + for j <- 0 until n do + result(i * n + j) = num.plus(result(i * n + j), num.times(aik, other(k, j))) + MatGeneric(result, n) + +inline trait MatSpecialized[T: {Specialized, NumericSpecialized}](val elems: Array[T], val n: Int): + private val num = summon[NumericSpecialized[T]] + + def apply(i: Int, j: Int): T = elems(i * n + j) + + def matMul(other: MatSpecialized[T], result: Array[T]): Array[T] = + require(this.n == other.n) + for + i <- 0 until n + k <- 0 until n + do + val aik = this(i, k) + for j <- 0 until n do + result(i * n + j) = num.plus(result(i * n + j), num.times(aik, other(k, j))) + result + +@State(Scope.Benchmark) +class Matrices: + val n = 300 + + val mat1 = Array.fill(n * n)(math.round(math.random() * 100).toInt) + val mat2 = Array.fill(n * n)(math.round(math.random() * 100).toInt) + + val result = Array.ofDim[Int](n * n) + +@State(Scope.Benchmark) +@BenchmarkMode(Array(Mode.AverageTime)) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 15, time = 10, timeUnit = TimeUnit.SECONDS) +@Fork(1) +class MatBench: + @Benchmark + def manual(m: Matrices, blackHole: Blackhole) = + val x1 = MatManual(m.mat1, m.n) + val y1 = MatManual(m.mat2, m.n) + blackHole.consume(x1.matMul(y1, m.result)) + + @Benchmark + def generic(m: Matrices, blackHole: Blackhole) = + val x1 = MatGeneric[Int](m.mat1, m.n) + val y1 = MatGeneric[Int](m.mat2, m.n) + blackHole.consume(x1.matMul(y1, m.result)) + + @Benchmark + def specialized(m: Matrices, blackHole: Blackhole) = + val x1 = new MatSpecialized[Int](m.mat1, m.n) {} + val y1 = new MatSpecialized[Int](m.mat2, m.n) {} + blackHole.consume(x1.matMul(y1, m.result)) + +inline trait NumericSpecialized[T: Specialized]: + def fromInt(x: Int): T + def plus(x: T, y: T): T + def times(x: T, y: T): T + +implicit object IntIsNumeric extends NumericSpecialized[Int]: + override def fromInt(x: Int): Int = x + override def plus(x: Int, y: Int): Int = x + y + override def times(x: Int, y: Int): Int = x * y diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/MatrixMultiplicationPoly.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/MatrixMultiplicationPoly.scala new file mode 100644 index 000000000000..a813f931e5a7 --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/MatrixMultiplicationPoly.scala @@ -0,0 +1,170 @@ +//> using scala 3.10.0-RC1-bin-SNAPSHOT +//> using options -language:experimental.specializedTraits + +package dotty.tools.benchmarks.specializedtraits + +import org.openjdk.jmh.annotations.* +import java.util.concurrent.TimeUnit +import org.openjdk.jmh.infra.Blackhole + +// Flat row-major layout: element (i,j) of an n×n matrix is at index i*n+j +class MatManualInt(val elems: Array[Int], val n: Int): + def apply(i: Int, j: Int): Int = elems(i * n + j) + + def matMul(other: MatManualInt, result: Array[Int]): MatManualInt = + require(this.n == other.n) + for + i <- 0 until n + k <- 0 until n + do + val aik = this(i, k) + for j <- 0 until n do + result(i * n + j) += aik * other(k, j) + MatManualInt(result, n) + +class MatManualFloat(val elems: Array[Float], val n: Int): + def apply(i: Int, j: Int): Float = elems(i * n + j) + + def matMul(other: MatManualFloat, result: Array[Float]): MatManualFloat = + require(this.n == other.n) + for + i <- 0 until n + k <- 0 until n + do + val aik = this(i, k) + for j <- 0 until n do + result(i * n + j) += aik * other(k, j) + MatManualFloat(result, n) + +class MatManualDouble(val elems: Array[Double], val n: Int): + def apply(i: Int, j: Int): Double = elems(i * n + j) + + def matMul(other: MatManualDouble, result: Array[Double]): MatManualDouble = + require(this.n == other.n) + for + i <- 0 until n + k <- 0 until n + do + val aik = this(i, k) + for j <- 0 until n do + result(i * n + j) += aik * other(k, j) + MatManualDouble(result, n) + +class MatGeneric[T: Numeric](val elems: Array[T], val n: Int): + private val num = summon[Numeric[T]] + + def apply(i: Int, j: Int): T = elems(i * n + j) + + def matMul(other: MatGeneric[T], result: Array[T]): MatGeneric[T] = + require(this.n == other.n) + for + i <- 0 until n + k <- 0 until n + do + val aik = this(i, k) + for j <- 0 until n do + result(i * n + j) = num.plus(result(i * n + j), num.times(aik, other(k, j))) + MatGeneric(result, n) + +inline trait MatSpecialized[T: {Specialized, NumericSpecialized}](val elems: Array[T], val n: Int): + private val num = summon[NumericSpecialized[T]] + + def apply(i: Int, j: Int): T = elems(i * n + j) + + def matMul(other: MatSpecialized[T], result: Array[T]): Array[T] = + require(this.n == other.n) + for + i <- 0 until n + k <- 0 until n + do + val aik = this(i, k) + for j <- 0 until n do + result(i * n + j) = num.plus(result(i * n + j), num.times(aik, other(k, j))) + result + +@State(Scope.Benchmark) +class Matrices: + val n = 300 + + val mat1Int = Array.fill(n * n)(math.round(math.random() * 100).toInt) + val mat2Int = Array.fill(n * n)(math.round(math.random() * 100).toInt) + + val mat1Float = Array.fill(n * n)(math.random().floatValue * 100) + val mat2Float = Array.fill(n * n)(math.random().floatValue * 100) + + val mat1Double = Array.fill(n * n)(math.random() * 100) + val mat2Double = Array.fill(n * n)(math.random() * 100) + + val resultInt = Array.ofDim[Int](n * n) + val resultFloat = Array.ofDim[Float](n * n) + val resultDouble = Array.ofDim[Double](n * n) + +@State(Scope.Benchmark) +@BenchmarkMode(Array(Mode.AverageTime)) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 15, time = 10, timeUnit = TimeUnit.SECONDS) +@Fork(1) +class MatBench: + @Benchmark + def manual(m: Matrices, blackHole: Blackhole) = + val x1 = MatManualInt(m.mat1Int, m.n) + val y1 = MatManualInt(m.mat2Int, m.n) + blackHole.consume(x1.matMul(y1, m.resultInt)) + + val x2 = MatManualFloat(m.mat1Float, m.n) + val y2 = MatManualFloat(m.mat2Float, m.n) + blackHole.consume(x2.matMul(y2, m.resultFloat)) + + val x3 = MatManualDouble(m.mat1Double, m.n) + val y3 = MatManualDouble(m.mat2Double, m.n) + blackHole.consume(x3.matMul(y3, m.resultDouble)) + + @Benchmark + def generic(m: Matrices, blackHole: Blackhole) = + val x1 = MatGeneric[Int](m.mat1Int, m.n) + val y1 = MatGeneric[Int](m.mat2Int, m.n) + blackHole.consume(x1.matMul(y1, m.resultInt)) + + val x2 = MatGeneric[Float](m.mat1Float, m.n) + val y2 = MatGeneric[Float](m.mat2Float, m.n) + blackHole.consume(x2.matMul(y2, m.resultFloat)) + + val x3 = MatGeneric[Double](m.mat1Double, m.n) + val y3 = MatGeneric[Double](m.mat2Double, m.n) + blackHole.consume(x3.matMul(y3, m.resultDouble)) + + @Benchmark + def specialized(m: Matrices, blackHole: Blackhole) = + val x1 = new MatSpecialized[Int](m.mat1Int, m.n) {} + val y1 = new MatSpecialized[Int](m.mat2Int, m.n) {} + blackHole.consume(x1.matMul(y1, m.resultInt)) + + val x2 = new MatSpecialized[Float](m.mat1Float, m.n) {} + val y2 = new MatSpecialized[Float](m.mat2Float, m.n) {} + blackHole.consume(x2.matMul(y2, m.resultFloat)) + + val x3 = new MatSpecialized[Double](m.mat1Double, m.n) {} + val y3 = new MatSpecialized[Double](m.mat2Double, m.n) {} + blackHole.consume(x3.matMul(y3, m.resultDouble)) + +inline trait NumericSpecialized[T: Specialized]: + def fromInt(x: Int): T + def plus(x: T, y: T): T + def times(x: T, y: T): T + +implicit object IntIsNumeric extends NumericSpecialized[Int]: + override def fromInt(x: Int): Int = x + override def plus(x: Int, y: Int): Int = x + y + override def times(x: Int, y: Int): Int = x * y + +implicit object FloatIsNumeric extends NumericSpecialized[Float]: + override def fromInt(x: Int): Float = x + override def plus(x: Float, y: Float): Float = x + y + override def times(x: Float, y: Float): Float = x * y + +implicit object DoubleIsNumeric extends NumericSpecialized[Double]: + override def fromInt(x: Int): Double = x + override def plus(x: Double, y: Double): Double = x + y + override def times(x: Double, y: Double): Double = x * y + diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/PairHashing.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/PairHashing.scala new file mode 100644 index 000000000000..4254a34acd9e --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/PairHashing.scala @@ -0,0 +1,62 @@ +//> using scala 3.10.0-RC1-bin-SNAPSHOT +//> using options -language:experimental.specializedTraits + +package dotty.tools.benchmarks.specializedtraits + +import org.openjdk.jmh.annotations.* +import java.util.concurrent.TimeUnit +import scala.util.Random +import org.openjdk.jmh.infra.Blackhole + +class PairGeneric[T, S](x: T, y: S): + override def hashCode(): Int = x.hashCode() ^ y.hashCode() + +inline trait PairSpecialized[T: Specialized, S: Specialized](x: T, y: S): + override def hashCode(): Int = x.hashCode() ^ y.hashCode() + +class PairStringString(x: String, y: String): + override def hashCode(): Int = x.hashCode() ^ y.hashCode() + +@State(Scope.Benchmark) +class Pairs: + val n = 5_000_000 + val Ms_str_str = (0 until n).map(i => PairStringString("Good Morning", i.toString())).toArray + val As_str_str = (0 until n).map(i => PairGeneric("Good Morning", i.toString())).toArray + val Ts_str_str = (0 until n).map(i => new PairSpecialized[String, String]("Good Morning", i.toString()) {}.asInstanceOf[Any]).toArray + +@State(Scope.Benchmark) +@BenchmarkMode(Array(Mode.AverageTime)) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 15, time = 10, timeUnit = TimeUnit.SECONDS) +@Fork(1) +class PairHashBench: + @Benchmark + def manual(state: Pairs, blackHole: Blackhole) = + var i = 0 + var res = Random.nextInt(10) + while (i < state.n) { + res ^= state.Ms_str_str(i).hashCode() + i += 1 + } + blackHole.consume(res) + + @Benchmark + def generic(state: Pairs, blackHole: Blackhole) = + var i = 0 + var res = Random.nextInt(10) + while (i < state.n) { + res ^= state.As_str_str(i).hashCode() + i += 1 + } + blackHole.consume(res) + + @Benchmark + def specialized(state: Pairs, blackHole: Blackhole) = + var i = 0 + var res = Random.nextInt(10) + while (i < state.n) { + res ^= state.Ts_str_str(i).asInstanceOf[PairSpecialized[String, String]].hashCode() + i += 1 + } + blackHole.consume(res) diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/PairHashingPoly.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/PairHashingPoly.scala new file mode 100644 index 000000000000..1147836c2efa --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/PairHashingPoly.scala @@ -0,0 +1,86 @@ +//> using scala 3.10.0-RC1-bin-SNAPSHOT +//> using options -language:experimental.specializedTraits + +package dotty.tools.benchmarks.specializedtraits + +import org.openjdk.jmh.annotations.* +import java.util.concurrent.TimeUnit +import scala.util.Random +import org.openjdk.jmh.infra.Blackhole + +case class Foo(val x: Int) +case class Bar(val y: Int) + +class PairGeneric[T, S](x: T, y: S): + override def hashCode(): Int = x.hashCode() ^ y.hashCode() + +inline trait PairSpecialized[T: Specialized, S: Specialized](x: T, y: S): + override def hashCode(): Int = x.hashCode() ^ y.hashCode() + +class PairStringFoo(x: String, y: Foo): + override def hashCode(): Int = x.hashCode() ^ y.hashCode() + +class PairFooBar(x: Foo, y: Bar): + override def hashCode(): Int = x.hashCode() ^ y.hashCode() + +class PairBarString(x: Bar, y: String): + override def hashCode(): Int = x.hashCode() ^ y.hashCode() + +@State(Scope.Benchmark) +class Pairs: + val n = 5_000_000 + + val Ms_str_foo = Array.fill(n) {PairStringFoo("Good Morning", Foo(Random.nextInt(10)))} + val Ms_foo_bar = Array.fill(n) {PairFooBar(Foo(Random.nextInt(10)), Bar(Random.nextInt(10)))} + val Ms_bar_str = Array.fill(n) {PairBarString(Bar(Random.nextInt(10)), "Hello")} + + val As_str_foo = Array.fill(n) {PairGeneric("Good Morning", Foo(Random.nextInt(10)))} + val As_foo_bar = Array.fill(n) {PairGeneric(Foo(Random.nextInt(10)), Bar(Random.nextInt(10)))} + val As_bar_str = Array.fill(n) {PairGeneric(Bar(Random.nextInt(10)), "Hello")} + + val Ts_str_foo = Array.fill[Any](n) {new PairSpecialized[String, Foo]("Good Morning", Foo(Random.nextInt(10))) {}} + val Ts_foo_bar = Array.fill[Any](n) {new PairSpecialized[Foo, Bar](Foo(Random.nextInt(10)), Bar(Random.nextInt(10))) {}} + val Ts_bar_str = Array.fill[Any](n) {new PairSpecialized[Bar, String](Bar(Random.nextInt(10)), "Hello") {}} + +@State(Scope.Benchmark) +@BenchmarkMode(Array(Mode.AverageTime)) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 15, time = 10, timeUnit = TimeUnit.SECONDS) +@Fork(1) +class PairHashBench: + @Benchmark + def manual(state: Pairs, blackHole: Blackhole) = + var i = 0 + var res = Random.nextInt(10) + while (i < state.n) { + res ^= state.Ms_str_foo(i).hashCode() + res ^= state.Ms_foo_bar(i).hashCode() + res ^= state.Ms_bar_str(i).hashCode() + i += 1 + } + blackHole.consume(res) + + @Benchmark + def generic(state: Pairs, blackHole: Blackhole) = + var i = 0 + var res = Random.nextInt(10) + while (i < state.n) { + res ^= state.As_str_foo(i).hashCode() + res ^= state.As_foo_bar(i).hashCode() + res ^= state.As_bar_str(i).hashCode() + i += 1 + } + blackHole.consume(res) + + @Benchmark + def specialized(state: Pairs, blackHole: Blackhole) = + var i = 0 + var res = Random.nextInt(10) + while (i < state.n) { + res ^= state.Ts_str_foo(i).asInstanceOf[PairSpecialized[String, Foo]].hashCode() + res ^= state.Ts_foo_bar(i).asInstanceOf[PairSpecialized[Foo, Bar]].hashCode() + res ^= state.Ts_bar_str(i).asInstanceOf[PairSpecialized[Bar, String]].hashCode() + i += 1 + } + blackHole.consume(res) diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/README.md b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/README.md new file mode 100644 index 000000000000..18e9820d1b29 --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/README.md @@ -0,0 +1,13 @@ +## Specialized Trait Benchmarks + +### Run +1. Publish the compiler first and check that the version in the benchmark file corresponds to the generated version, +2. Kill bloop server if republishing an existing version: + `scala-cli --power bloop exit` +3. Run the benchmark with: + ``` +scala-cli --power --jmh bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/VectorDotProduct.scala + ``` + +### Troubleshooting +- May have to run it again / delete .scala-build and rerun if you get a class not found error from `scala-cli` first time - the `--jmh` flag is still experimental. diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/VectorDotProduct.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/VectorDotProduct.scala new file mode 100644 index 000000000000..57d9e1fd471a --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/VectorDotProduct.scala @@ -0,0 +1,90 @@ +//> using scala 3.10.0-RC1-bin-SNAPSHOT +//> using options -language:experimental.specializedTraits + +package dotty.tools.benchmarks.specializedtraits + +import org.openjdk.jmh.annotations.* +import java.util.concurrent.TimeUnit + +class VecManual(elems: Array[Int]): + private val num = summon[Numeric[Int]] + + def length = elems.length + + def apply(i: Int): Int = elems(i) + + def scalarProduct(other: VecManual): Int = + require(this.length == other.length) + var result = num.fromInt(0) + for i <- 0 until length do + result = num.plus(result, num.times(this(i), other(i))) + result + +class VecGeneric[T: Numeric](elems: Array[T]): + private val num = summon[Numeric[T]] + + def length = elems.length + + def apply(i: Int): T = elems(i) + + def scalarProduct(other: VecGeneric[T]): T = + require(this.length == other.length) + var result = num.fromInt(0) + for i <- 0 until length do + result = num.plus(result, num.times(this(i), other(i))) + result + +inline trait VecSpecialized[T: {Specialized, Numeric2}](elems: Array[T]): + private val num = summon[Numeric2[T]] + + def length = elems.length + + def apply(i: Int): T = elems(i) + + def scalarProduct(other: VecSpecialized[T]): T = + require(this.length == other.length) + var result = num.fromInt(0) + for i <- 0 until length do + result = num.plus(result, num.times(this(i), other(i))) + result + +@State(Scope.Benchmark) +class Arrays: + val arr1 = Array.fill(100_000_000) {math.round(math.random().floatValue * 4)} + val arr2 = Array.fill(100_000_000) {math.round(math.random().floatValue * 4)} + val target = arr1.zip(arr2).map((x, y) => x * y).fold(0)(_ + _) + +@State(Scope.Benchmark) +@BenchmarkMode(Array(Mode.AverageTime)) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 15, time = 10, timeUnit = TimeUnit.SECONDS) +@Fork(1) +class VecBench: + @Benchmark + def manual(arr: Arrays) = + val x = VecManual(arr.arr1) + val y = VecManual(arr.arr2) + assert(x.scalarProduct(y) == arr.target) + + @Benchmark + def generic(arr: Arrays) = + val x = VecGeneric[Int](arr.arr1) + val y = VecGeneric[Int](arr.arr2) + assert(x.scalarProduct(y) == arr.target) + + @Benchmark + def specialized(arr: Arrays) = + val x = new VecSpecialized[Int](arr.arr1) {} + val y = new VecSpecialized[Int](arr.arr2) {} + assert(x.scalarProduct(y) == arr.target) + +inline trait Numeric2[T: Specialized]: + def fromInt(x: Int): T + def plus(x: T, y: T): T + def times(x: T, y: T): T + +implicit object IntIsNumeric extends Numeric2[Int]: + override def fromInt(x: Int): Int = x + override def plus(x: Int, y: Int): Int = x + y + override def times(x: Int, y: Int): Int = x * y diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/VectorDotProductPoly.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/VectorDotProductPoly.scala new file mode 100644 index 000000000000..281df0c79aa1 --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/specializedtraits/VectorDotProductPoly.scala @@ -0,0 +1,156 @@ +//> using scala 3.10.0-RC1-bin-SNAPSHOT +//> using options -language:experimental.specializedTraits + +package dotty.tools.benchmarks.specializedtraits + +import org.openjdk.jmh.annotations.* +import java.util.concurrent.TimeUnit + +class VecManualInt(elems: Array[Int]): + def length = elems.length + + def apply(i: Int): Int = elems(i) + + def scalarProduct(other: VecManualInt): Int = + require(this.length == other.length) + var result = 0 + for i <- 0 until length do + result = result + (this(i) * other(i)) + result + +class VecManualFloat(elems: Array[Float]): + def length = elems.length + + def apply(i: Int): Float = elems(i) + + def scalarProduct(other: VecManualFloat): Float = + require(this.length == other.length) + var result = 0F + for i <- 0 until length do + result = result + (this(i) * other(i)) + result + +class VecManualDouble(elems: Array[Double]): + def length = elems.length + + def apply(i: Int): Double = elems(i) + + def scalarProduct(other: VecManualDouble): Double = + require(this.length == other.length) + var result = 0D + for i <- 0 until length do + result = result + (this(i) * other(i)) + result + +class VecGeneric[T: Numeric](elems: Array[T]): + private val num = summon[Numeric[T]] + + def length = elems.length + + def apply(i: Int): T = elems(i) + + def scalarProduct(other: VecGeneric[T]): T = + require(this.length == other.length) + var result = num.fromInt(0) + for i <- 0 until length do + result = num.plus(result, num.times(this(i), other(i))) + result + +inline trait VecSpecialized[T: {Specialized, NumericSpecialized}](elems: Array[T]): + private val num = summon[NumericSpecialized[T]] + + def length = elems.length + + def apply(i: Int): T = elems(i) + + def scalarProduct(other: VecSpecialized[T]): T = + require(this.length == other.length) + var result = num.fromInt(0) + for i <- 0 until length do + result = num.plus(result, num.times(this(i), other(i))) + result + +@State(Scope.Benchmark) +class Arrays: + val arr1Int = Array.fill(100_000) {math.round(math.random().floatValue * 4)} + val arr2Int = Array.fill(100_000) {math.round(math.random().floatValue * 4)} + + val arr1Float = Array.fill(100_000) {math.random().floatValue * 10} + val arr2Float = Array.fill(100_000) {math.random().floatValue * 10} + + val arr1Double = Array.fill(100_000) {math.random() * 5} + val arr2Double = Array.fill(100_000) {math.random() * 5} + + val targetInt = arr1Int.zip(arr2Int).map((x, y) => x * y).fold(0)(_ + _) + val targetFloat = arr1Float.zip(arr2Float).map((x, y) => x * y).fold(0F)(_ + _) + val targetDouble = arr1Double.zip(arr2Double).map((x, y) => x * y).fold(0D)(_ + _) + +@State(Scope.Benchmark) +@BenchmarkMode(Array(Mode.AverageTime)) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 15, time = 10, timeUnit = TimeUnit.SECONDS) +@Fork(1) +class VecBench: + @Benchmark + def manual(arr: Arrays) = + val x1 = VecManualInt(arr.arr1Int) + val y1 = VecManualInt(arr.arr2Int) + assert(x1.scalarProduct(y1) == arr.targetInt) + + val x2 = VecManualFloat(arr.arr1Float) + val y2 = VecManualFloat(arr.arr2Float) + assert(x2.scalarProduct(y2) == arr.targetFloat) + + val x3 = VecManualDouble(arr.arr1Double) + val y3 = VecManualDouble(arr.arr2Double) + assert(x3.scalarProduct(y3) == arr.targetDouble) + + @Benchmark + def generic(arr: Arrays) = + val x1 = VecGeneric[Int](arr.arr1Int) + val y1 = VecGeneric[Int](arr.arr2Int) + assert(x1.scalarProduct(y1) == arr.targetInt) + + val x2 = VecGeneric[Float](arr.arr1Float) + val y2 = VecGeneric[Float](arr.arr2Float) + assert(x2.scalarProduct(y2) == arr.targetFloat) + + val x3 = VecGeneric[Double](arr.arr1Double) + val y3 = VecGeneric[Double](arr.arr2Double) + assert(x3.scalarProduct(y3) == arr.targetDouble) + + @Benchmark + def specialized(arr: Arrays) = + val x1 = new VecSpecialized[Int](arr.arr1Int) {} + val y1 = new VecSpecialized[Int](arr.arr2Int) {} + assert(x1.scalarProduct(y1) == arr.targetInt) + + val x2 = new VecSpecialized[Float](arr.arr1Float) {} + val y2 = new VecSpecialized[Float](arr.arr2Float) {} + assert(x2.scalarProduct(y2) == arr.targetFloat) + + val x3 = new VecSpecialized[Double](arr.arr1Double) {} + val y3 = new VecSpecialized[Double](arr.arr2Double) {} + assert(x3.scalarProduct(y3) == arr.targetDouble) + +inline trait NumericSpecialized[T: Specialized]: + def fromInt(x: Int): T + def plus(x: T, y: T): T + def times(x: T, y: T): T + +implicit object IntIsNumeric extends NumericSpecialized[Int]: + override def fromInt(x: Int): Int = x + override def plus(x: Int, y: Int): Int = x + y + override def times(x: Int, y: Int): Int = x * y + +implicit object FloatIsNumeric extends NumericSpecialized[Float]: + override def fromInt(x: Int): Float = x + override def plus(x: Float, y: Float): Float = x + y + override def times(x: Float, y: Float): Float = x * y + +implicit object DoubleIsNumeric extends NumericSpecialized[Double]: + override def fromInt(x: Int): Double = x + override def plus(x: Double, y: Double): Double = x + y + override def times(x: Double, y: Double): Double = x * y + diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 2c5ffaaf4967..7d9a4e47e5b9 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -84,6 +84,9 @@ class CompilationUnit protected (val source: SourceFile, val info: CompilationUn /** Will be set to true if the unit contains a pureFunctions language import */ var knowsPureFuns: Boolean = false + + /** Will be set to true if the unit contains an inlineTrait language import */ + var knowsInlineTraits: Boolean = false var suspended: Boolean = false var suspendedAtInliningPhase: Boolean = false diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index a7525fb2e370..2eea941fa6fe 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -43,17 +43,19 @@ class Compiler { List(new UnrollDefinitions) :: // Unroll annotated methods if detected in PostTyper List(new sjs.PrepJSInterop) :: // Additional checks and transformations for Scala.js (Scala.js only) List(new SetRootTree) :: // Set the `rootTreeOrProvider` on class symbols + List(new DesugarSpecializedTraits, // Process Specialized traits + new SpecializeInlineTraits) :: // Inline the code of inline traits into their children Nil /** Phases dealing with TASTY tree pickling and unpickling */ protected def picklerPhases: List[List[Phase]] = - List(new Pickler) :: // Generate TASTY info - List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks - List(new Inlining) :: // Inline and execute macros - List(new PostInlining) :: // Add mirror support for inlined code - List(new Staging) :: // Check staging levels and heal staged types - List(new Splicing) :: // Replace level 1 splices with holes - List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures + List(new Pickler) :: // Generate TASTY info + List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks + List(new Inlining) :: // Inline and execute macros + List(new PostInlining) :: // Add mirror support for inlined code + List(new Staging) :: // Check staging levels and heal staged types + List(new Splicing) :: // Replace level 1 splices with holes + List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures Nil /** Phases dealing with the transformation from pickled trees to backend trees */ @@ -81,7 +83,9 @@ class Compiler { new ForwardDepChecks, // Check that there are no forward references to local vals new SpecializeApplyMethods, // Adds specialized methods to FunctionN new TryCatchPatterns, // Compile cases in try/catch - new PatternMatcher) :: // Compile pattern matches + new PatternMatcher, // Compile pattern matches + new PruneInlinedMethods, // Remove methods which have already been inlined + new PruneInlineTraits) :: // Remove right-hand side of definitions in inline traits List(new TestRecheck.Pre) :: // Test only: run rechecker, enabled under -Yrecheck-test List(new TestRecheck) :: // Test only: run rechecker, enabled under -Yrecheck-test List(new cc.Setup) :: // Preparations for check captures phase, enabled under captureChecking @@ -94,7 +98,7 @@ class Compiler { new ExplicitSelf, // Make references to non-trivial self types explicit as casts new StringInterpolatorOpt, // Optimizes raw and s and f string interpolators by rewriting them to string concatenations or formats new DropBreaks) :: // Optimize local Break throws by rewriting them - List(new PruneErasedDefs, // Make erased symbols private + List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_` new InlinePatterns, // Remove placeholders of inlined patterns new VCInlineMethods, // Inlines calls to value class methods diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index fe529415a117..311ac332cbbc 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -313,6 +313,11 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { * a pureFunctions language import. */ var pureFunsImportEncountered = false + + /** Will be set to true if any of the compiled compilation units contains + * an inlineTraits language import. + */ + var inlineTraitsImportEncountered = false /** Will be set to true if experimental.captureChecking is enabled * or any of the compiled compilation units contains a captureChecking language import. diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index dc8d9c700700..f179f91accf8 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -110,13 +110,13 @@ class TreeTypeMap( tree1.withType(mapType(tree1.tpe)) match { case id: Ident => if needsSelect(id.tpe) then - try ref(id.tpe.asInstanceOf[TermRef]).withSpan(id.span) + try ref(id.tpe.asInstanceOf[TermRef])(using ctx.withSource(id.source)).withSpan(id.span) catch case ex: TypeError => super.transform(id) else super.transform(id) case sel: Select => if needsIdent(sel.tpe) then - ref(sel.tpe.asInstanceOf[TermRef]).withSpan(sel.span) + ref(sel.tpe.asInstanceOf[TermRef])(using ctx.withSource(sel.source)).withSpan(sel.span) else super.transform(sel) case app: Apply => diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 52046e93818c..f5e0502e1689 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -43,6 +43,8 @@ object Feature: val safe = experimental("safe") val dedentedStringLiterals = experimental("dedentedStringLiterals") val magic = experimental("magic") + val inlineTraits = experimental("inlineTraits") + val specializedTraits = experimental("specializedTraits") val nonViralExperimentalFeatures: Set[TermName] = Set(captureChecking, separationChecking, safe) @@ -69,6 +71,7 @@ object Feature: (scala2macros, "Allow Scala 2 macros"), (dependent, "Allow dependent method types"), (erasedDefinitions, "Allow erased definitions"), + (strictEqualityPatternMatching, "relaxed CanEqual checks for ADT pattern matching"), (symbolLiterals, "Allow symbol literals"), (saferExceptions, "Enable safer exceptions"), (pureFunctions, "Enable pure functions for capture checking"), @@ -81,6 +84,8 @@ object Feature: (safe, "Require safe mode"), (dedentedStringLiterals, "Enable experimental dedented string literals"), (magic, "Enable extensions for working with coding agents"), + (inlineTraits, "Allow inline traits"), + (specializedTraits, "Allow specialized traits"), ) /** Features that are now standard; the language import / -language choice is @@ -182,7 +187,12 @@ object Feature: def quotedPatternsWithPolymorphicFunctionsEnabled(using Context) = enabled(quotedPatternsWithPolymorphicFunctions) - + + def inlineTraitsEnabled(using Context) = + enabledBySetting(inlineTraits) + || enabledBySetting(specializedTraits) + || ctx.compilationUnit.knowsInlineTraits + /** Is pureFunctions enabled for this compilation unit? */ def pureFunsEnabled(using Context) = enabledBySetting(pureFunctions) @@ -215,6 +225,12 @@ object Feature: enabledBySetting(magic) || ctx.originalCompilationUnit.magic + /** Are inline traits enabled for this compilation unit */ + def inlineTraitsEnabledSomewhere(using Context) = + enabledBySetting(inlineTraits) + || enabledBySetting(specializedTraits) + || ctx.run != null && ctx.run.nn.inlineTraitsImportEncountered + /** Is pureFunctions enabled for any of the currently compiled compilation units? */ def pureFunsEnabledSomewhere(using Context) = enabledBySetting(pureFunctions) @@ -301,7 +317,7 @@ object Feature: * snippet compiler so they take effect across inputs (i16250). */ val globalLanguageImports: Set[TermName] = - Set(pureFunctions, captureChecking, separationChecking, safe) + Set(pureFunctions, captureChecking, separationChecking, safe, inlineTraits) /** Handle a global language import `import language..`. * Sets the compilation unit's and current run's fields accordingly. @@ -330,6 +346,10 @@ object Feature: case `magic` => ctx.compilationUnit.magic = true true + case `inlineTraits` => + ctx.compilationUnit.knowsInlineTraits = true + if ctx.run != null then ctx.run.nn.inlineTraitsImportEncountered = true + true case _ => false diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 5055f8d83ee1..519ca413f8e8 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -39,6 +39,7 @@ import java.nio.file.InvalidPathException import dotty.tools.dotc.coverage.Coverage import dotty.tools.dotc.rewrites.Rewrites import scala.annotation.tailrec +import dotty.tools.dotc.inlines.Inlines.InlineTraitState object Contexts { @@ -143,6 +144,7 @@ object Contexts { def typerState: TyperState def gadt: GadtConstraint = gadtState.gadt def gadtState: GadtState + def inlineTraitState: InlineTraitState def searchHistory: SearchHistory def source: SourceFile @@ -430,6 +432,7 @@ object Contexts { superOrThisCallContext(owner, constrCtx.scope) .setTyperState(typerState) .setGadtState(gadtState) + .setInlineTraitState(inlineTraitState) .fresh .setScope(this.scope) } @@ -625,6 +628,9 @@ object Contexts { private var _gadtState: GadtState = uninitialized final def gadtState: GadtState = _gadtState + + private var _inlineTraitState: InlineTraitState = uninitialized + final def inlineTraitState: InlineTraitState = _inlineTraitState private var _searchHistory: SearchHistory = uninitialized final def searchHistory: SearchHistory = _searchHistory @@ -650,6 +656,7 @@ object Contexts { _tree = origin.tree _scope = origin.scope _gadtState = origin.gadtState + _inlineTraitState = origin.inlineTraitState _searchHistory = origin.searchHistory _source = origin.source _moreProperties = origin.moreProperties @@ -713,6 +720,11 @@ object Contexts { def setFreshGADTBounds: this.type = setGadtState(gadtState.fresh) + def setInlineTraitState(inlineTraitState: InlineTraitState): this.type = + util.Stats.record("Context.setInlineTraitState") + this._inlineTraitState = inlineTraitState + this + def setSearchHistory(searchHistory: SearchHistory): this.type = util.Stats.record("Context.setSearchHistory") this._searchHistory = searchHistory @@ -807,6 +819,7 @@ object Contexts { .updated(profilerLoc, Profiler.NoOp) c._searchHistory = new SearchRoot c._gadtState = GadtState(GadtConstraint.empty) + c._inlineTraitState = InlineTraitState() c end FreshContext diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 8d65165db280..18fe4d17a87c 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -246,6 +246,7 @@ class Definitions { @tu lazy val ScalaCollectionImmutablePackageClass: ClassSymbol = requiredPackage("scala.collection.immutable").moduleClass.asClass @tu lazy val ScalaMathPackageClass: ClassSymbol = requiredPackage("scala.math").moduleClass.asClass @tu lazy val ScalaUtilPackageClass: ClassSymbol = requiredPackage("scala.util").moduleClass.asClass + @tu lazy val ScalaSpecializePackageVal: TermSymbol = requiredPackage("scala.specialize") // fundamental modules @tu lazy val SysPackage : Symbol = requiredModule("scala.sys.package") @@ -825,6 +826,10 @@ class Definitions { @tu lazy val StringAddClass : ClassSymbol = requiredClass("scala.runtime.StringAdd") @tu lazy val StringAdd_+ : Symbol = StringAddClass.requiredMethod(nme.raw.PLUS) + @tu lazy val SpecializedClass : ClassSymbol = requiredClass("scala.specialize.Specialized") + @tu lazy val SpecializedModule: Symbol = SpecializedClass.companionModule + @tu lazy val SpecializedModule_apply: Symbol = SpecializedModule.requiredMethod(nme.apply) + @tu lazy val StringContextClass: ClassSymbol = requiredClass("scala.StringContext") @tu lazy val StringContext_s : Symbol = StringContextClass.requiredMethod(nme.s) @tu lazy val StringContext_raw: Symbol = StringContextClass.requiredMethod(nme.raw_) @@ -1765,6 +1770,12 @@ class Definitions { private val PredefImportFns: RootRef = RootRef(() => ScalaPredefModule.termRef) + // The new Specialized lives in scala.specialize. + // This is to avoid conflict with the Scala2 specialized annotation. + // It is not imported by default with the scala package, so we additionally import it here. + private val SpecializeImportFns: RootRef = + RootRef(() => ScalaSpecializePackageVal.termRef) + @tu private lazy val YimportsImportFns: List[RootRef] = ctx.settings.Yimports.value.map { name => val denot = getModuleIfDefined(name).suchThat(_.is(Module)) `orElse` @@ -1780,8 +1791,8 @@ class Definitions { @tu private lazy val ScalaRootImportFns: List[RootRef] = if !ctx.settings.Yimports.isDefault then YimportsImportFns else if ctx.settings.YnoImports.value then Nil - else if ctx.settings.YnoPredef.value then ScalaImportFns - else ScalaImportFns :+ PredefImportFns + else if ctx.settings.YnoPredef.value then ScalaImportFns :+ SpecializeImportFns + else ScalaImportFns :+ SpecializeImportFns :+ PredefImportFns @tu private lazy val JavaRootImportTypes: List[TermRef] = JavaRootImportFns.map(_.refFn()) @tu private lazy val ScalaRootImportTypes: List[TermRef] = ScalaRootImportFns.map(_.refFn()) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index fa14c0333b55..cee3a4d80621 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -453,13 +453,13 @@ object Flags { /** Flags representing source modifiers */ private val CommonSourceModifierFlags: FlagSet = - commonFlags(Private, Protected, Final, Case, Implicit, Given, Override, JavaStatic, Transparent, Erased, Synchronized) + commonFlags(Private, Protected, Final, Case, Implicit, Given, Override, JavaStatic, Transparent, Erased, Synchronized, Inline) val TypeSourceModifierFlags: FlagSet = CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open | Into val TermSourceModifierFlags: FlagSet = - CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Tracked + CommonSourceModifierFlags.toTermFlags | AbsOverride | Lazy | Tracked /** Flags representing modifiers that can appear in trees */ val ModifierFlags: FlagSet = @@ -592,6 +592,7 @@ object Flags { val InlineOrProxy: FlagSet = Inline | InlineProxy // An inline method or inline argument proxy */ val InlineMethod: FlagSet = Inline | Method val InlineImplicitMethod: FlagSet = Implicit | InlineMethod + val InlineTrait: FlagSet = Inline | Trait val InlineParam: FlagSet = Inline | Param val InlineByNameProxy: FlagSet = InlineProxy | Method val JavaEnum: FlagSet = JavaDefined | Enum // A Java enum trait diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 17c08c917ae1..50b8351a1758 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -77,6 +77,9 @@ object NameOps { def isAnonymousFunctionName: Boolean = name.startsWith(str.ANON_FUN) def isUnapplyName: Boolean = name == nme.unapply || name == nme.unapplySeq def isRightAssocOperatorName: Boolean = name.lastPart.last == ':' + def isSpecializedTraitInterfaceName: Boolean = name.toString.contains(str.SPECIALIZED_TRAIT_SUFFIX) + def isSpecializedTraitImplementationName: Boolean = name.toString.contains(str.SPECIALIZED_TRAIT_IMPL_SUFFIX) + def isRawSpecializedTraitImplementationName: Boolean = name.toString.endsWith(str.SPECIALIZED_TRAIT_IMPL_SUFFIX) /** Optionally, if name is a selector name `_i`, its index i - 1 */ def selectorIndex: Option[Int] = diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 8b1d4ca0bd4a..fe478db85c7b 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -234,6 +234,8 @@ object Phases { private var mySbtExtractAPIPhase: Phase = uninitialized private var myPicklerPhase: Phase = uninitialized private var mySetRootTreePhase: Phase = uninitialized + private var mySpecializeInlineTraitsPhase: Phase = uninitialized + private var myDesugarSpecializedTraitsPhase: Phase = uninitialized private var myInliningPhase: Phase = uninitialized private var myStagingPhase: Phase = uninitialized private var mySplicingPhase: Phase = uninitialized @@ -266,6 +268,8 @@ object Phases { final def sbtExtractAPIPhase: Phase = mySbtExtractAPIPhase final def picklerPhase: Phase = myPicklerPhase final def setRootTreePhase: Phase = mySetRootTreePhase + final def specializeInlineTraitsPhase: Phase = mySpecializeInlineTraitsPhase + final def desugarSpecializedTraitsPhase: Phase = myDesugarSpecializedTraitsPhase final def inliningPhase: Phase = myInliningPhase final def stagingPhase: Phase = myStagingPhase final def splicingPhase: Phase = mySplicingPhase @@ -298,6 +302,8 @@ object Phases { mySbtExtractAPIPhase = phaseOfClass(classOf[sbt.ExtractAPI]) mySetRootTreePhase = phaseOfClass(classOf[SetRootTree]) myPicklerPhase = phaseOfClass(classOf[Pickler]) + mySpecializeInlineTraitsPhase = phaseOfClass(classOf[SpecializeInlineTraits]) + myDesugarSpecializedTraitsPhase = phaseOfClass(classOf[DesugarSpecializedTraits]) myInliningPhase = phaseOfClass(classOf[Inlining]) myStagingPhase = phaseOfClass(classOf[Staging]) mySplicingPhase = phaseOfClass(classOf[Splicing]) @@ -560,6 +566,8 @@ object Phases { def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase def sbtExtractAPIPhase(using Context): Phase = ctx.base.sbtExtractAPIPhase def picklerPhase(using Context): Phase = ctx.base.picklerPhase + def specializeInlineTraitsPhase(using Context): Phase = ctx.base.specializeInlineTraitsPhase + def desugarSpecializedTraitsPhase(using Context): Phase = ctx.base.desugarSpecializedTraitsPhase def inliningPhase(using Context): Phase = ctx.base.inliningPhase def stagingPhase(using Context): Phase = ctx.base.stagingPhase def splicingPhase(using Context): Phase = ctx.base.splicingPhase diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 5aede3b9101c..9107d8085917 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -26,6 +26,9 @@ object StdNames { inline val LOCALDUMMY_PREFIX = " rinfo match @@ -646,6 +646,20 @@ object SymDenotations { final def isAnonymousClass(using Context): Boolean = isClass && initial.name.isAnonymousClassName + /** Is this symbol an specialized trait interface? */ + final def isSpecializedTraitInterface(using Context): Boolean = + isClass && name.isSpecializedTraitInterfaceName + + /** Is this symbol an specialized trait implementation class? */ + final def isSpecializedTraitImplementationClass(using Context): Boolean = + isClass && name.isSpecializedTraitImplementationName + + /** Is this symbol a specialized trait implementation class that + * was generated from a specialization using only top classes / Nothing + * and is therefore not subject to a specialized interface */ + final def isRawSpecializedTraitImplementationClass(using Context): Boolean = + isClass && name.isSpecializedTraitImplementationName + final def isAnonymousFunction(using Context): Boolean = this.symbol.is(Method) && initial.name.isAnonymousFunctionName @@ -1049,6 +1063,15 @@ object SymDenotations { def isInlineMethod(using Context): Boolean = isAllOf(InlineMethod, butNot = Accessor) + def isInlineTrait(using Context): Boolean = + isAllOf(InlineTrait) + + def isSpecializedMethod(using Context): Boolean = + Specialization.isSpecializedMethod(symbol) + + def isSpecializedTrait(using Context): Boolean = + Specialization.isSpecializedTrait(symbol) + /** Does this method or field need to be retained at runtime */ def isRetainedInline(using Context): Boolean = is(Inline, butNot = Deferred) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 27473732ba1d..df53da1049c4 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -26,7 +26,10 @@ import Capabilities.Capability import NameKinds.WildcardParamName import MatchTypes.isConcrete import reporting.Message.Note +import reporting.IllegalVarianceInSpecializedTraitsNote import scala.util.boundary, boundary.break +import dotty.tools.dotc.transform.Specialization +import dotty.tools.dotc.transform.DesugarSpecializedTraits /** Provides methods to compare types. */ @@ -192,7 +195,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def testSubType(tp1: Type, tp2: Type): CompareResult = GADTused = false opaquesUsed = false - if !topLevelSubType(tp1, tp2) then CompareResult.Fail(Nil) + errorNotes = Nil + if !topLevelSubType(tp1, tp2) then CompareResult.Fail(errorNotes.map(_._2)) else if GADTused then CompareResult.OKwithGADTUsed else if opaquesUsed then CompareResult.OKwithOpaquesUsed // we cast on GADTused, so handles if both are used else CompareResult.OK @@ -1970,8 +1974,31 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling && defn.isByNameFunction(arg2.dealias) => isSubArg(arg1res, arg2.argInfos.head) case _ => - if v < 0 then isSubType(arg2, arg1) - else if v > 0 then isSubType(arg1, arg2) + if v < 0 then + val isValidSubtype = isSubType(arg2, arg1) + // Specialized traits have special variance rules because they have special erasure + if tp1.classSymbol.isSpecializedTrait + && Specialization.traitParamIsSpecialized(tp1.classSymbol, tparam.paramRef.typeSymbol) + && isValidSubtype + && !(DesugarSpecializedTraits.isSameErasureBucket(arg1, arg2)) + then + addErrorNote(IllegalVarianceInSpecializedTraitsNote()) + false + else // Normal contravariance case + isValidSubtype + else if v > 0 then + val isValidSubtype = isSubType(arg1, arg2) + // Specialized traits have special variance rules because they have special erasure + if tp1.classSymbol.isSpecializedTrait + && Specialization.traitParamIsSpecialized(tp1.classSymbol, tparam.paramRef.typeSymbol) + && isValidSubtype + && (arg1 ne arg2) + && (arg1.classSymbol == defn.NothingClass) + then + addErrorNote(IllegalVarianceInSpecializedTraitsNote()) + false + else // Normal covariance case + isValidSubtype else isSameType(arg2, arg1) val arg1 = args1.head diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index ccd8a0fc57a2..bbb29b5dc862 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -7,12 +7,15 @@ import Flags.JavaDefined import Uniques.unique import backend.sjs.JSDefinitions import transform.ExplicitOuter.* +import transform.Specialization import transform.ValueClasses.* import transform.ContextFunctionResults.* import unpickleScala2.Scala2Erasure import Decorators.* import Definitions.MaxImplementedFunctionArity import scala.annotation.tailrec +import dotty.tools.dotc.transform.DesugarSpecializedTraits +import dotty.tools.dotc.util.Property /** The language in which the definition being erased was written. */ enum SourceLanguage: @@ -76,8 +79,10 @@ end SourceLanguage */ object TypeErasure: + private val DisallowSpecialized = Property.Key[Unit] + private def erasureDependsOnArgs(sym: Symbol)(using Context) = - sym == defn.ArrayClass || sym == defn.PairClass || sym.isDerivedValueClass + sym == defn.ArrayClass || sym == defn.PairClass || sym.isDerivedValueClass || sym.isSpecializedTrait /** The arity of this tuple type, which can be made up of EmptyTuple, TupleX and `*:` pairs. * @@ -206,6 +211,14 @@ object TypeErasure: def preErasureCtx(using Context) = if (ctx.erasedTypes) ctx.withPhase(erasurePhase) else ctx + /** The current context but with Foo[Int] erasing to Foo instead of + * Foo$sp$Int when Foo is a specialized trait. */ + def disallowSpecializedCtx(using Context) = ctx.fresh.setProperty(DisallowSpecialized, ()) + + /** The current context but with Foo[Int] erasing to Foo$sp$Int instead of + * Foo when Foo is a specialized trait. */ + def allowSpecializedCtx(using Context) = ctx.fresh.dropProperty(DisallowSpecialized) + /** The standard erasure of a Scala type. Value classes are erased as normal classes. * * @param tp The type to erase. @@ -776,6 +789,16 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst else if semiEraseVCs && sym.isDerivedValueClass then eraseDerivedValueClass(tp) else if defn.isSyntheticFunctionClass(sym) then defn.functionTypeErasure(sym) else eraseNormalClassRef(tp) + case Specialization(spec) if ((ctx.phase == erasurePhase || ctx.erasedTypes) // At the beginning the $sp$ trait symbols are not present so up until + // erasure need to consider the signature of def foo(x: Foo[Int]): Int as + // foo(Foo):Int. Only at erasure do the symbols swap. This ensures + // the signatures don't change before erasure which is required (meta-ordering + // constraint in Compiler.scala) + && spec.isSpecialized && ctx.property(DisallowSpecialized).isEmpty) => + val specName = spec.newSpecializedTraitName + val interfaceSymbol = spec.symbol.owner.enclosingPackageClass.info.decls.lookup(specName) + assert(interfaceSymbol.exists && interfaceSymbol.isClass) + this(interfaceSymbol.typeRef.appliedTo(spec.unspecializedTypeArgs)) case tp: AppliedType => val tycon = tp.tycon if (tycon.isRef(defn.ArrayClass)) eraseArray(tp) @@ -866,19 +889,36 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst case tp @ ClassInfo(pre, cls, parents, decls, _) => if (cls.is(Package)) tp else { - def eraseParent(tp: Type) = tp.dealias match { // note: can't be opaque, since it's a class parent + def eraseParent(tp: Type)(using Context) = tp.dealias match { // note: can't be opaque, since it's a class parent case tp: AppliedType if tp.tycon.isRef(defn.PairClass) => defn.ObjectType case _ => apply(tp) } val erasedParents: List[Type] = if ((cls eq defn.ObjectClass) || cls.isPrimitiveValueClass) Nil - else parents.mapConserve(eraseParent) match { - case tr :: trs1 => - assert(!tr.classSymbol.is(Trait), i"$cls has bad parents $parents%, %") - val tr1 = if (cls.is(Trait)) defn.ObjectType else tr - tr1 :: trs1.filterNot(_.isAnyRef) - case nil => nil - } + else + // Match corresponding tree erasure in Erasure::typedClassDef + val parents1 = + if cls.isSpecializedTraitInterface then // {source: inline trait Bar[T: Specialized] extends Foo[T] both specialized traits} inline trait Bar$sp$Int extends Object, Bar, Foo$sp$Int + val (obj :: originalTrait :: inheritedParents) = parents : @unchecked + eraseParent(obj) :: apply(originalTrait)(using disallowSpecializedCtx) :: inheritedParents.mapConserve(eraseParent(_)(using allowSpecializedCtx)) + else if cls.isSpecializedTraitImplementationClass && !cls.isRawSpecializedTraitImplementationClass then // {source: Bar, Foo both specialized traits} class Bar$impl$Int extends Object, Bar$sp$Int, Bar(10) + val (objectParent :: traitSpParent :: originalTraitSpecializedParent :: Nil) = parents : @unchecked + eraseParent(objectParent) :: eraseParent(traitSpParent)(using allowSpecializedCtx) :: apply(originalTraitSpecializedParent)(using disallowSpecializedCtx) :: Nil + else + val originalSpecializedTraits = parents.filter(p => p.typeSymbol.isSpecializedTrait).map(eraseParent(_)(using allowSpecializedCtx)) + + // {source: class Bar extends Foo[Int](10) with Baz[Int](10)} + // class Bar extends Object, Foo(10), Baz(10), Foo$sp$Int, Baz$sp$Int + parents.mapConserve(p => if p.typeSymbol.isSpecializedTrait then + apply(p)(using disallowSpecializedCtx) + else eraseParent(p)) ::: originalSpecializedTraits + parents1 match { + case tr :: trs1 => + assert(!tr.classSymbol.is(Trait), i"$cls has bad parents $parents%, %") + val tr1 = if (cls.is(Trait)) defn.ObjectType else tr + tr1 :: trs1.filterNot(_.isAnyRef) + case nil => nil + } val erasedDecls = decls.filteredScope( keep = sym => !sym.isType || sym.isClass, rename = sym => diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 21f67b92d09d..1a665d7dcad3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1176,8 +1176,26 @@ class TreeUnpickler(reader: TastyReader, }) NamerOps.addConstructorProxies(cls) NamerOps.addContextBoundCompanions(cls) + + // Because opaque types can appear in inline traits and these are only allowed to be completed once (otherwise cyclic reference error) + // we need to force the body stats now if we have an inline trait so that we don't complete them twice, once in the LazyBodyAnnot and once + // in the main code. + val strictOrLazyStats = + if cls.isInlineTrait then + val strictStats = lazyStats.complete + cls.addAnnotation(LazyBodyAnnotation { (ctx0: Context) ?=> + val ctx1 = localContext(cls)(using ctx0).addMode(Mode.ReadPositions) + inContext(sourceChangeContext(Addr(0))(using ctx1)) { + // avoids space leaks by not capturing the current context + val inlinedMembers = strictStats.filter(member => inlines.Inlines.isInlineableFromInlineTrait(cls, member)) + Block(inlinedMembers, unitLiteral).withSpan(cls.span) + } + }) + strictStats + else + lazyStats setSpan(start, - untpd.Template(constr, mappedParents, self, lazyStats) + untpd.Template(constr, mappedParents, self, strictOrLazyStats) .withType(localDummy.termRef)) } diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 998cb0b945e4..e054b873bf2d 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -119,9 +119,10 @@ object Inliner: oldOwners: List[Symbol], newOwners: List[Symbol], substFrom: List[Symbol], - substTo: List[Symbol])(using Context) + substTo: List[Symbol], + val inlineCopier: TreeCopier)(using Context) extends TreeTypeMap( - typeMap, treeMap, oldOwners, newOwners, substFrom, substTo, InlineCopier()): + typeMap, treeMap, oldOwners, newOwners, substFrom, substTo, inlineCopier): override def transform(tree: Tree)(using Context): Tree = tree match @@ -147,7 +148,7 @@ object Inliner: newOwners: List[Symbol], substFrom: List[Symbol], substTo: List[Symbol])(using Context) = - new InlinerMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) + new InlinerMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo, inlineCopier) override def transformInlined(tree: Inlined)(using Context) = if tree.inlinedFromOuterScope then @@ -406,7 +407,7 @@ class Inliner(val call: tpd.Tree)(using Context): private def classNestingLevel(cls: Symbol) = cls.ownersIterator.count(_.isClass) // Compute val-definitions for all this-proxies and append them to `bindingsBuf` - private def computeThisBindings() = { + protected def computeThisBindings() = { // All needed this-proxies, paired-with and sorted-by nesting depth of // the classes they represent (innermost first) val sortedProxies = thisProxy.toList @@ -573,7 +574,7 @@ class Inliner(val call: tpd.Tree)(using Context): else arg else arg - private def canElideThis(tpe: ThisType): Boolean = + protected def canElideThis(tpe: ThisType): Boolean = inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || tpe.cls.isContainedIn(inlinedMethod) || tpe.cls.is(Package) @@ -622,7 +623,6 @@ class Inliner(val call: tpd.Tree)(using Context): if thisTypeProxyExists then mapBackToOpaques.typeMap(thisTypeUnpacker.typeMap(inlined.expansion.tpe)) else inlined.tpe - /** Populate `thisProxy` and `paramProxy` as follows: * * 1a. If given type refers to a static this, thisProxy binds it to corresponding global reference, @@ -678,7 +678,7 @@ class Inliner(val call: tpd.Tree)(using Context): * from its `originalOwner`, and, if it comes from outside the inlined method * itself, it has to be marked as an inlined argument. */ - private def integrate(tree: Tree, originalOwner: Symbol)(using Context): Tree = + protected def integrate(tree: Tree, originalOwner: Symbol)(using Context): Tree = // assertAllPositioned(tree) // debug tree.changeOwner(originalOwner, ctx.owner) @@ -690,9 +690,73 @@ class Inliner(val call: tpd.Tree)(using Context): val reducer = new InlineReducer(this) - /** The Inlined node representing the inlined call */ - def inlined(rhsToInline: tpd.Tree): (List[MemberDef], Tree) = + protected class InlinerTypeMap extends DeepTypeMap { + override def stopAt = + if opaqueProxies.isEmpty then StopAt.Static else StopAt.Package + def apply(t: Type) = t match { + case t: ThisType => thisProxy.get(t.cls).getOrElse(t) + case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) + case t: SingletonType => + if t.termSymbol.isAllOf(InlineParam) then apply(t.widenTermRefExpr) + else paramProxy.getOrElse(t, mapOver(t)) + case t => mapOver(t) + } + } + + protected class InlinerTreeMap extends (Tree => Tree) { + def apply(tree: Tree) = tree match { + case tree: This => + tree.tpe match { + case thistpe: ThisType => + thisProxy.get(thistpe.cls) match { + case Some(t) => + val thisRef = ref(t).withSpan(call.span) + inlinedFromOutside(thisRef)(tree.span) + case None => tree + } + case _ => tree + } + case tree: Ident => + /* Span of the argument. Used when the argument is inlined directly without a binding */ + def argSpan = + if (tree.name == nme.WILDCARD) tree.span // From type match + else if (tree.symbol.isTypeParam && tree.symbol.owner.isClass) tree.span // TODO is this the correct span + else paramSpan(tree.name) + val inlinedCtx = ctx.withSource(inlinedMethod.topLevelClass.source) + paramProxy.get(tree.tpe) match { + case Some(t) if tree.isTerm && t.isSingleton => + val inlinedSingleton = singleton(t).withSpan(argSpan) + inlinedFromOutside(inlinedSingleton)(tree.span) + case Some(t) if tree.isType => + inlinedFromOutside(new InferredTypeTree().withType(t).withSpan(argSpan))(tree.span) + case _ => tree + } + case tree @ Select(qual: This, name) if tree.symbol.is(Private) && tree.symbol.isInlineMethod => + // This inline method refers to another (private) inline method (see tests/pos/i14042.scala). + // We insert upcast to access the private inline method once inlined. This makes the selection + // keep the symbol when re-typechecking in the InlineTyper. The method is inlined and hence no + // reference to a private method is kept at runtime. + cpy.Select(tree)(qual.asInstance(qual.tpe.widen), name) + + case tree => tree + } + + private def inlinedFromOutside(tree: Tree)(span: Span): Tree = + Inlined(EmptyTree, Nil, tree)(using ctx.withSource(inlinedMethod.topLevelClass.source)).withSpan(span) + } + + protected val inlinerTypeMap: InlinerTypeMap = InlinerTypeMap() + protected val inlinerTreeMap: InlinerTreeMap = InlinerTreeMap() + protected def substFrom: List[Symbol] = Nil + protected def substTo: List[Symbol] = Nil + protected def inlineCopier: TreeCopier = InlineCopier() + + protected def inlineCtx(inlineTyper: InlineTyper)(using Context): Context = + inlineContext(Inlined(call, Nil, ref(defn.Predef_undefined))).fresh.setTyper(inlineTyper).setNewScope + + /** The Inlined node representing the inlined call */ + def inlined(rhsToInline: tpd.Tree)(using Context): (List[MemberDef], Tree) = inlining.println(i"-----------------------\nInlining $call\nWith RHS $rhsToInline") def paramTypess(call: Tree, acc: List[List[Type]]): List[List[Type]] = call match @@ -735,69 +799,25 @@ class Inliner(val call: tpd.Tree)(using Context): val inlineTyper = new InlineTyper(ctx.reporter.errorCount) - val inlineCtx = inlineContext(Inlined(call, Nil, ref(defn.Predef_undefined))).fresh.setTyper(inlineTyper).setNewScope - - def inlinedFromOutside(tree: Tree)(span: Span): Tree = - Inlined(EmptyTree, Nil, tree)(using ctx.withSource(inlinedMethod.topLevelClass.source)).withSpan(span) + val inlineCtx = this.inlineCtx(inlineTyper) // A tree type map to prepare the inlined body for typechecked. // The translation maps references to `this` and parameters to // corresponding arguments or proxies on the type and term level. It also changes // the owner from the inlined method to the current owner. - val inliner = new InlinerMap( - typeMap = - new DeepTypeMap { - override def stopAt = - if opaqueProxies.isEmpty then StopAt.Static else StopAt.Package - def apply(t: Type) = t match { - case t: ThisType => thisProxy.getOrElse(t.cls, t) - case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) - case t: SingletonType => - if t.termSymbol.isAllOf(InlineParam) then apply(t.widenTermRefExpr) - else paramProxy.getOrElse(t, mapOver(t)) - case t => mapOver(t) - } - }, - treeMap = { - case tree: This => - tree.tpe match { - case thistpe: ThisType => - thisProxy.get(thistpe.cls) match { - case Some(t) => - val thisRef = ref(t).withSpan(call.span) - inlinedFromOutside(thisRef)(tree.span) - case None => tree - } - case _ => tree - } - case tree: Ident => - /* Span of the argument. Used when the argument is inlined directly without a binding */ - def argSpan = - if (tree.name == nme.WILDCARD) tree.span // From type match - else if (tree.symbol.isTypeParam && tree.symbol.owner.isClass) tree.span // TODO is this the correct span? - else paramSpan(tree.name) - val inlinedCtx = ctx.withSource(inlinedMethod.topLevelClass.source) - paramProxy.get(tree.tpe) match { - case Some(t) if tree.isTerm && t.isSingleton => - val inlinedSingleton = singleton(t).withSpan(argSpan) - inlinedFromOutside(inlinedSingleton)(tree.span) - case Some(t) if tree.isType => - inlinedFromOutside(new InferredTypeTree().withType(t).withSpan(argSpan))(tree.span) - case _ => tree - } - case tree @ Select(qual: This, name) if tree.symbol.is(Private) && tree.symbol.isInlineMethod => - // This inline method refers to another (private) inline method (see tests/pos/i14042.scala). - // We insert upcast to access the private inline method once inlined. This makes the selection - // keep the symbol when re-typechecking in the InlineTyper. The method is inlined and hence no - // reference to a private method is kept at runtime. - cpy.Select(tree)(qual.asInstance(qual.tpe.widen), name) - case tree => tree - }, - oldOwners = inlinedMethod :: Nil, - newOwners = ctx.owner :: Nil, - substFrom = Nil, - substTo = Nil + // This is reused through InlineTraitAncestors for inline traits, so inlinedMethod might not exist there + val oldOwners = if (inlinedMethod.exists) then inlinedMethod :: Nil else Nil + val newOwners = if (inlinedMethod.exists) then ctx.owner :: Nil else Nil + + val inliner = new InlinerMap( + typeMap = inlinerTypeMap, + treeMap = inlinerTreeMap, + oldOwners = oldOwners, + newOwners = newOwners, + substFrom = substFrom, + substTo = substTo, + inlineCopier = inlineCopier )(using inlineCtx) inlining.println( @@ -878,7 +898,6 @@ class Inliner(val call: tpd.Tree)(using Context): if (inlinedMethod == defn.Compiletime_error) issueError() addInlinedTrees(treeSize(finalExpansion)) - (finalBindings, finalExpansion) } end inlined diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 74a18fe68d4f..7007ccb4893f 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -3,8 +3,9 @@ package dotc package inlines import ast.*, core.* -import Flags.*, Symbols.*, Types.*, Decorators.*, Constants.*, Contexts.* -import StdNames.{tpnme, nme} +import Flags.*, Symbols.*, Types.*, Decorators.*, Constants.*, Contexts.*, TypeOps.* +import Names.Name +import StdNames.{str, tpnme, nme} import NameOps.* import typer.* import NameKinds.BodyRetainerName @@ -23,9 +24,15 @@ import cc.CleanupRetains import collection.mutable import reporting.{NotConstant, trace} import util.Property -import util.Spans.Span +import util.Spans.{Span, spanCoord} import dotty.tools.dotc.core.Periods.PhaseId import dotty.tools.dotc.util.chaining.* +import NameOps.expandedName +import dotty.tools.dotc.reporting.OverrideError +import dotty.tools.dotc.typer.RefChecks.OverridingPairsChecker +import dotty.tools.dotc.typer.ErrorReporting.err +import dotty.tools.dotc.core.NameKinds.DefaultGetterName +import dotty.tools.dotc.core.Phases.Phase /** Support for querying inlineable methods and for inlining calls to such methods */ object Inlines: @@ -46,7 +53,7 @@ object Inlines: /** `sym` is an inline method with a known body to inline. */ def hasBodyToInline(sym: SymDenotation)(using Context): Boolean = - sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot) + (sym.isInlineMethod || sym.isInlineTrait) && sym.hasAnnotation(defn.BodyAnnot) /** The body to inline for method `sym`, or `EmptyTree` if none exists. * @pre hasBodyToInline(sym) @@ -62,39 +69,100 @@ object Inlines: else EmptyTree + def defsToInline(traitSym: SymDenotation)(using Context): List[Tree] = + bodyToInline(traitSym) match + case Block(defs, _) if traitSym.isInlineTrait => defs + case _ => Nil + /** Are we in an inline method body? */ def inInlineMethod(using Context): Boolean = ctx.owner.ownersIterator.exists(_.isInlineMethod) + def inInlineContext(using Context): Boolean = + ctx.owner.ownersIterator.exists(sym => sym.isInlineMethod || sym.isInlineTrait) + /** Can a call to method `meth` be inlined? */ def isInlineable(meth: Symbol)(using Context): Boolean = - meth.is(Inline) && meth.hasAnnotation(defn.BodyAnnot) && !inInlineMethod + meth.isInlineMethod && meth.hasAnnotation(defn.BodyAnnot) && !inInlineMethod + + def isInlineableFromInlineTrait(inlinedTraitSym: ClassSymbol, member: tpd.Tree)(using Context): Boolean = + !(member.isInstanceOf[tpd.TypeDef] && inlinedTraitSym.typeParams.contains(member.symbol)) + && !member.symbol.isAllOf(Inline) /** Should call be inlined in this context? */ - def needsInlining(tree: Tree)(using Context): Boolean = tree match { - case Block(_, expr) => needsInlining(expr) - case _ => - def isUnapplyExpressionWithDummy: Boolean = - // The first step of typing an `unapply` consists in typing the call - // with a dummy argument (see Applications.typedUnApply). We delay the - // inlining of this call. - def rec(tree: Tree): Boolean = tree match - case Apply(_, ProtoTypes.dummyTreeOfType(_) :: Nil) => true - case Apply(fn, _) => rec(fn) - case _ => false - tree.symbol.name.isUnapplyName && rec(tree) - - isInlineable(tree.symbol) - && !tree.tpe.widenTermRefExpr.isInstanceOf[MethodOrPoly] - && StagingLevel.level == 0 + def needsInlining(tree: Tree)(using Context): Boolean = + def isInlineableInCtx = + StagingLevel.level == 0 && ( ctx.phase == Phases.inliningPhase || (ctx.phase == Phases.typerPhase && needsTransparentInlining(tree)) + || (ctx.inlineTraitState.inlineTraitsPhase == InlineTraitState.InlineContext.InlineTraits && !tree.symbol.is(Macro) && !(tree.symbol.isSpecializedTraitImplementationClass || tree.symbol.isSpecializedTraitInterface)) + || (ctx.inlineTraitState.inlineTraitsPhase == InlineTraitState.InlineContext.SpecializedTraits && !tree.symbol.is(Macro) && (tree.symbol.isSpecializedTraitImplementationClass || tree.symbol.isSpecializedTraitInterface) ) ) && !ctx.typer.hasInliningErrors && !ctx.base.stopInlining - && !ctx.mode.is(Mode.NoInline) - && !isUnapplyExpressionWithDummy + + tree match + case Block(_, expr) => + needsInlining(expr) + case tdef @ TypeDef(_, impl: Template) => + impl.parents.map(symbolFromParent).exists(sym => sym.isInlineTrait) && isInlineableInCtx // We also allow inlining of inline traits into inline traits for consistency when implementing specialized traits (see docs) + case _ => + def isUnapplyExpressionWithDummy: Boolean = + // The first step of typing an `unapply` consists in typing the call + // with a dummy argument (see Applications.typedUnApply). We delay the + // inlining of this call. + def rec(tree: Tree): Boolean = tree match + case Apply(_, ProtoTypes.dummyTreeOfType(_) :: Nil) => true + case Apply(fn, _) => rec(fn) + case _ => false + tree.symbol.name.isUnapplyName && rec(tree) + // We don't inline inline method calls into inline traits because we can inline them into the children and this gets around the issue of + // needing to apply the symbol replacement map to those calls (which is tricky because we don't apply any inline maps beyond Inlined() calls) + // See tests/pos/i11866.scala and tests/run/specialized-trait-vector-dot-product.scala + isInlineable(tree.symbol) && !tree.tpe.widenTermRefExpr.isInstanceOf[MethodOrPoly] && isInlineableInCtx && !(ctx.owner.ownersIterator.exists(_.isInlineTrait)) && !ctx.mode.is(Mode.NoInline) && !isUnapplyExpressionWithDummy + + private[dotc] def symbolFromParent(parent: Tree)(using Context): Symbol = + if parent.symbol.isConstructor then parent.symbol.owner else parent.tpe.typeSymbol + + // Inline trait ancestors in linearization order. + private def inlineTraitAncestors(cls: TypeDef)(using Context): List[Tree] = cls match { + case tpd.TypeDef(_, tmpl: Template) => + val parentTrees: Map[Symbol, Tree] = tmpl.parents.map(par => symbolFromParent(par) -> par).toMap.filter(_._1.isInlineTrait) + + // TODO: We need to stop inlining if there is a non-inline trait or class that sits between the inline trait and the current class. + // Because we also inline into other inline traits, it should be possible to do this by just + // looking at the direct parents of the class instead of also needing to look at the indirect parents (baseClasses). + // See inline-trait-non-inline-blocks-inlining.scala + val ancestors: List[ClassSymbol] = + cls.tpe.baseClasses.filter(sym => sym != cls.symbol && sym.isInlineTrait + && !(cls.symbol.asClass.ownersIterator.toList.tail.exists(p => p.isInlineTrait)) // We can skip anything that would be inlined into a class that lives somewhere inside an inline trait + // because it must be on the RHS of a member definition in the inline trait and so pruned out later + ) + + ancestors.flatMap(ancestor => + def baseTree = + cls.tpe.baseType(ancestor) match + case AppliedType(tycon, targs) => + Some(AppliedTypeTree(TypeTree(tycon), targs.map(TypeTree(_)))) + case tref: TypeRef => + Some(Ident(tref)) + case baseTpe => + report.error(s"unknown base type ${baseTpe.show} for ancestor ${ancestor.show} of ${cls.symbol.show}") + None + parentTrees.get(ancestor).orElse(baseTree.map(_.withSpan(cls.span))) + ).flatMap { tree => + tree.tpe match { + case Specialization(spec) if spec.hasSpecializedParams && !spec.isFullySpecialized => None // these can only exist in cases where we don't want to inline because: + // 1) they will be pruned out later anyway and if we inline them we will create a loop (as in tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad.scala) + // 2) they are covered by another specialized type parameter in inlining and therefore we will specialize later when the inline function is called + // tests/run/specialized-trait-inline-specialized-instance-with-specialization + // 3) we already dealt with them in desugarSpecializedTraits + case other => Some(tree) + } + } + case _ => + Nil } private def needsTransparentInlining(tree: Tree)(using Context): Boolean = @@ -217,6 +285,143 @@ object Inlines: tree3 end inlineCall + private def updateFlagsFromInlinedParent(child: FlagSet, parent: FlagSet): FlagSet = + var updatedFlags = child + // Parent needs to be initialised so child must also as initialisers have been inlined + if (!parent.is(NoInits)) + updatedFlags &~= NoInits + + // Parent is impure; contaminates child as non-abstract methods have been inlined + if (!parent.is(PureInterface)) + updatedFlags &~= PureInterface + updatedFlags + + private def checkInnerClasses(tmpl: Template)(using Context) = + tmpl.body.foreach { + // If we want to add these back, some work was done on this in the original Master Thesis + // (https://infoscience.epfl.ch/server/api/core/bitstreams/9413f583-46bc-4106-b994-0be32f20eeba/content) + case innerClass: TypeDef if innerClass.symbol.isClass => report.error("Inline traits may not define inner classes or traits.", innerClass.srcPos) + case _ => + } + + def checkAndTransformInlineTrait(inlineTrait: TypeDef)(using Context): TypeDef = + val tpd.TypeDef(_, tmpl: Template) = inlineTrait: @unchecked + checkInnerClasses(tmpl) + val body1 = tmpl.body.flatMap { + case member: MemberDef => + List(member) + case _ => + // Remove non-memberdefs, as they are normally placed into $init() + Nil + } + val tmpl1 = cpy.Template(tmpl)(body = body1) + cpy.TypeDef(inlineTrait)(rhs = tmpl1) + end checkAndTransformInlineTrait + + + private def checkInlineTraitOverrides(clsSym: ClassSymbol)(using Context) = + // We need to enforce `override` modifier constraints to ensure that the behaviour is the same as ordinary traits. + // The usual checks only apply in refChecks which is too late for us. + def checkInlineTraitOverride(member: Symbol, other: Symbol) = + if !member.is(Override) && !other.is(Deferred) && member.owner == clsSym then + report.error( + OverrideError("needs `override` modifier", + other.info, + member, + other, + NoType, + NoType), + member.srcPos + ) + else if member.owner != clsSym && other.owner != clsSym + && !other.owner.derivesFrom(member.owner) + && !(member.isAnyOverride || member.hasAnnotation(defn.UncheckedOverrideAnnot)) + && (!other.is(Deferred) || other.isAllOf(Given | HasDefault)) + && !member.is(Deferred) + && !other.name.is(DefaultGetterName) then + + report.error( + OverrideError( + s"${clsSym} inherits conflicting members:\n " + + err.infoString(other, clsSym.asClass.thisType, showLocation = true) + " and\n " + + err.infoString(member, clsSym.thisType, showLocation = true) + + "\n(Note: this can be resolved by declaring an override in " + clsSym + ".)", + other.info, + member, + other, + NoType, + NoType) + , + clsSym.srcPos + ) + OverridingPairsChecker(clsSym, clsSym.thisType).checkAll(checkInlineTraitOverride) + + def inlineParentInlineTraits(cls: Tree)(using Context): Tree = + cls match { + case cls @ tpd.TypeDef(_, impl: Template) => + checkInlineTraitOverrides(cls.symbol.asClass) + val clsOverriddenSyms = cls.symbol.info.decls.toList.flatMap(_.allOverriddenSymbols).toSet + val ancestors = inlineTraitAncestors(cls) + + if cls.symbol.isAnonymousClass && ancestors.exists(tree => Specialization.unapply(tree.tpe).exists(anc => anc.isSpecialized || anc.isFullySpecializedToTopClassesOrNothing)) then + // No need to inline into specialized trait anonymous class instances; these will later be replaced by $impl$ classes. + return cls + + val cycleFound = ancestors.exists { parent => + val parentSym = symbolFromParent(parent) + val errorPos = + // Trying to inline into the tree which defines parentSym (need to catch this separately + // as need to catch it before we inline the second time to avoid tripping an assertion) + if cls.symbol.ownersIterator.contains(parentSym) then + Some(cls.srcPos) + else if ctx.inlineTraitState.inlineOrigins(cls.symbol).contains(parentSym) then + // Select the user code that caused this error so we get two errors if there are two problematic inlines, not one + val userPos = tpd.enclosingInlineds.last.srcPos + // Trying to inline into the inlined body of parentSym not in the defn tree + Some(userPos) + else None // Fine + + errorPos.foreach(pos => + report.error(s"Inlining of inline traits looped. Tried to inline ${parentSym} into its own body.", pos) + ) + + errorPos.nonEmpty + } + + if cycleFound then + return cls + + val newDefs = inContext(ctx.withOwner(cls.symbol)) { + ancestors.foldLeft((List.empty[Tree], impl.body)) { + case ((inlineDefs, childDefs), parent) => + val parentTraitInliner = InlineParentTrait(parent) + // Inline body + val overriddenSymbols = clsOverriddenSyms ++ inlineDefs.flatMap(_.symbol.allOverriddenSymbols) + // Need to put the new defs first because we process in linearization order to make overridees correct, + // but we want parent definitions to come first so that if child inline traits refer to values defined in a parent + // inline trait these are defined. + val inlinedDefs1 = parentTraitInliner.expandDefs(overriddenSymbols) ::: inlineDefs + cls.symbol.flags = updateFlagsFromInlinedParent(cls.symbol.flags, parent.symbol.flags) + + val childDefs1 = parentTraitInliner.adaptSuperCalls(childDefs) + (parentTraitInliner.adaptSuperCalls(inlinedDefs1), childDefs1) + } + } + + val newbody = newDefs._1 ::: newDefs._2 + val paramAccessors = newbody.filter(_.symbol.is(ParamAccessor)) + + for pacc <- paramAccessors + otherstat <- newbody if !otherstat.symbol.is(ParamAccessor) && otherstat.denot.matches(pacc.denot.asSingleDenotation) + do report.error(s"Inlining of inline trait created name conflict on ${pacc.denot.name}. Constructor parameters of inline receivers may not collide with members of inline traits.", pacc.srcPos) + + val impl1 = cpy.Template(impl)(body = newbody) + + cpy.TypeDef(cls)(rhs = impl1) + case _ => + cls + } + /** Try to inline a pattern with an inline unapply method. Fail with error if the maximal * inline depth is exceeded. * @@ -680,4 +885,286 @@ object Inlines: inlined end expand end InlineCall + + private class InlineParentTrait(parent: tpd.Tree)(using Context) extends Inliner(parent): + import tpd._ + import Inlines.* + + private val parentSym = symbolFromParent(parent) + private val paramAccessorsMapper = ParamAccessorsMapper() + + private val child = ctx.owner + private val childThisType = ctx.owner.thisType + private val childThisTree = This(ctx.owner.asClass).withSpan(parent.span) + + def inlinedSelfType = + inlinerTypeMap(parentSym.asClass.classDenot.givenSelfType) + + def expandDefs(overriddenDecls: Set[Symbol]): List[Tree] = + paramAccessorsMapper.registerParamValuesOf(parent) + val stats = Inlines.defsToInline(parentSym) + .filterNot(stat => overriddenDecls.contains(stat.symbol) && stat.symbol.is(Deferred)) + .filterNot(stat => stat.isDef && stat.symbol.isClass) // Inline traits may not define inner classes; error elsewhere but skip now for best effort error msgs + + val stats1 = stats.map{ // Private symbols must be entered before the RHSs are inlined + case member: MemberDef => Left((member, inlinedSym(member.symbol, overriddenDecls))) + case stat => Right(stat) + } + ctx.owner.info = ctx.owner.asClass.classInfo.integrateOpaqueMembers + stats1.map{ + case Left((tree, inlinedSym)) => expandStat(tree, inlinedSym) + case Right(tree) => inlinedRhs(tree) + } + end expandDefs + + def adaptSuperCalls(defs: List[Tree]) = + val ttmap = TreeTypeMap(treeMap = { + // We go through all ancestor inline traits so eventually we will find the one with matching parentSym + case sel@Select(Super(qual, mix), name) if sel.symbol.owner == parentSym => + // At that point either the method is overridden so needs mangling (and we just copied and mangled it in this inlining phase), + // or not, in which case call directly by original name. In both cases we are calling the method resulting from inlining, on the + // inline receiver class. + Select(This(ctx.owner.asClass), paramAccessorsMapper.getParamAccessorName(sel.symbol.owner, name).getOrElse(name)) + case tree => tree + }) + defs.map(ttmap(_)) + + protected class InlineTraitTypeMap extends InlinerTypeMap { + override def apply(t: Type) = super.apply(t) match { + case t: ThisType if t.cls == parentSym => childThisType + case t => mapOver(t) + } + } + + protected class InlineTraitTreeMap extends InlinerTreeMap { + override def apply(tree: Tree) = super.apply(tree) match { + case tree: This if tree.symbol == parentSym => + Inlined(EmptyTree, Nil, childThisTree).withSpan(parent.span) + case tree: This => + tree.tpe match { + case thisTpe: ThisType if thisTpe.cls.isInlineTrait => + integrate(This(ctx.owner.asClass).withSpan(parent.span), thisTpe.cls) + case _ => + tree + } + case sel@Select(Super(qual, mix), name) => + if sel.symbol.owner.isInlineTrait then // We need to leave this intact so that adaptSuperCalls can redirect it later + sel + else + report.error("Inline traits may not contain superclass references to classes or non-inline traits.", sel.srcPos) + sel + case sel@Select(qual, name) => + inContext(ctx.withSource(tree.source)) { // Need to ensure we preserve the fact that this Select was inlined + // potentially from a different file. Recreating it discards that info. + // See: inline-trait-multiple-stages-generic-defs. + paramAccessorsMapper.getParamAccessorName(qual.symbol, name) match { + case Some(newName) => Select(this(qual), newName).withSpan(parent.span) + case None => cpy.Select(sel)(this(qual), name) + } + } + case ident: Ident if ident.isType => + // A type parameter of the inline trait written as an identifier in source (e.g. the `S` + // in `new C[S] {}` if nested inside an inline trait) is not handled by the usual inlining machinery + // because S is not a method type parameter. We have our own way of handling this in types + // in the type map but need to also map the tree reference. + // See tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad.scala. + val mapped = inlinerTypeMap(ident.tpe) + if mapped ne ident.tpe then TypeTree(mapped).withSpan(ident.span) + else ident + case tree => + tree + } + } + + override protected val inlinerTypeMap: InlinerTypeMap = InlineTraitTypeMap() + + override protected val inlinerTreeMap: InlinerTreeMap = InlineTraitTreeMap() + + override protected def computeThisBindings(): Unit = () + + override protected def canElideThis(tpe: ThisType): Boolean = true + + override protected def inlineCtx(inlineTyper: InlineTyper)(using Context): Context = + ctx.fresh.setTyper(inlineTyper).setNewScope + + extension (sym: Symbol) + private def isTermParamAccessor: Boolean = !sym.isType && sym.is(ParamAccessor) + + private def expandStat(stat: tpd.Tree, inlinedSym: Symbol)(using Context): tpd.Tree = stat match + case stat: ValDef => + inlinedValDef(stat, inlinedSym) + case stat: DefDef => + inlinedDefDef(stat, inlinedSym) + case stat: TypeDef => + inlinedTypeDef(stat, inlinedSym) + + private def inlinedSym(sym: Symbol, overriddenDecls: Set[Symbol], withoutFlags: FlagSet = EmptyFlags)(using Context): Symbol = + assert(!sym.isClass) + inlinedMemberSym(sym, overriddenDecls, withoutFlags) + + private def inlinedMemberSym(sym: Symbol, overriddenDecls: Set[Symbol], withoutFlags: FlagSet = EmptyFlags)(using Context): Symbol = + var name = sym.name + var flags = sym.flags | Synthetic + if sym.isTermParamAccessor then flags &~= ParamAccessor + if sym.is(Local) || (overriddenDecls.contains(sym)) then + name = paramAccessorsMapper.registerNewName(sym) + flags |= (Private | Local) + flags &~= Override // private override is illegal; if the inlined method was already override then we might make it private override by accident. + else + flags |= Override + sym.copy( + owner = ctx.owner, + name = name, + flags = flags &~ withoutFlags, + info = inlinerTypeMap(sym.info), + coord = spanCoord(parent.span)).entered + + private def inlinedValDef(vdef: ValDef, inlinedSym: Symbol)(using Context): ValDef = + val rhs = + paramAccessorsMapper + .getParamAccessorRhs(vdef.symbol.owner, vdef.symbol.name) + .getOrElse(inlinedRhs(vdef, inlinedSym)) + + val rhs1 = rhs.changeNonLocalOwners(inlinedSym) + + tpd.ValDef(inlinedSym.asTerm, rhs1).withSpan(parent.span) + + private def inlinedDefDef(ddef: DefDef, inlinedSym: Symbol)(using Context): DefDef = + val rhsFun: List[List[Tree]] => Tree = + if ddef.symbol.isSetter then + _ => unitLiteral + else + paramss => + val oldParamSyms = ddef.paramss.flatten.map(_.symbol) + val newParamSyms = paramss.flatten.map(_.symbol) + val ddef1 = cpy.DefDef(ddef)(rhs = ddef.rhs.subst(oldParamSyms, newParamSyms)) + inlinedRhs(ddef1, inlinedSym) + tpd.DefDef(inlinedSym.asTerm, rhsFun).withSpan(parent.span) + + private def inlinedTypeDef(tdef: TypeDef, inlinedSym: Symbol)(using Context): TypeDef = + if inlinedSym.isOpaqueAlias then + val inlinedRhsType = inlinerTypeMap(tdef.rhs.tpe) + inlinedSym.info = inlinedSym.opaqueToBounds(TypeAlias(inlinedRhsType), tdef.rhs, List()) + inlinedSym.typeRef.recomputeDenot() + ctx.typeAssigner.assignType(untpd.TypeDef(inlinedSym.name.asTypeName, TypeTree(inlinedRhsType)), inlinedSym).withSpan(parent.span) + else + tpd.TypeDef(inlinedSym.asType).withSpan(parent.span) + + + private def inlinedRhs(vddef: ValOrDefDef, inlinedSym: Symbol)(using Context): Tree = + val rhs = vddef.rhs.changeOwner(vddef.symbol, inlinedSym) + inlinedRhs(rhs)(using ctx.withOwner(inlinedSym)) + + private def inlinedRhs(rhs: Tree)(using Context): Tree = + if rhs.isEmpty then + rhs + else + val symbolMap = mutable.Map[Symbol, Symbol]() + // TODO: This inlines also some calls to inline defs that were made in the inline trait body, is that ok? + val rhs1 = Inlined(tpd.ref(parentSym).withSpan(parent.span), Nil, inlined(rhs)._2.withSpan(parent.span).cloneIn(parentSym.source)).withSpan(parent.span) + + // In case of nested inline trait inlines, because BodyAnnotation is out of date, + // body inlined misses nested expansion, but we have the symbols for the items that should be there + // Remove them so that they can be inlined properly later. + val ttmap = TreeTypeMap(treeMap = { + case tree@TypeDef(name, tmpl: Template) if Inlines.needsInlining(tree) => + val newSym = tree.symbol.copy(coord = spanCoord(tree.span)) // Coord should correspond to original location because we will inline from there. + newSym.info = ClassInfo(tree.symbol.owner.thisType, newSym.asClass, tree.symbol.asClass.parentTypes, Scopes.newScope) + + val newConstructorSymbol = tree.symbol.primaryConstructor.copy(owner = newSym) + val rt = newSym.typeRef.appliedTo(newSym.typeParams.map(_.typeRef)) + def resultType(tpe: Type): Type = tpe match { + case mt @ MethodType(paramNames) => mt.derivedLambdaType(paramNames, mt.paramInfos, rt) + case pt : PolyType => pt.derivedLambdaType(pt.paramNames, pt.paramInfos, resultType(pt.resType)) + } + newConstructorSymbol.info = resultType(newConstructorSymbol.info) + newConstructorSymbol.info = PolyType.fromParams(newConstructorSymbol.owner.typeParams, newConstructorSymbol.info) + + symbolMap(tree.symbol.primaryConstructor) = newConstructorSymbol + + val childSyms = tree.symbol.info.decls + .filter(sym => tmpl.body.exists(vddef => vddef.symbol == sym)) + .tapEach(sym => symbolMap(sym) = sym.copy(owner = newSym, coord=sym.coord)) + .map(symbolMap) + + childSyms.foreach(p => p.entered) + newConstructorSymbol.entered + + val tmpl1 = tmpl.changeOwner(tree.symbol, newSym) + + val rhsFun: List[List[Tree]] => Tree = + paramss => + val oldParamSyms = tmpl1.constr.paramss.flatten.map(_.symbol) + val newParamSyms = paramss.flatten.map(_.symbol) + tmpl1.constr.rhs.subst(oldParamSyms, newParamSyms) + + val ctor = tpd.DefDef(newConstructorSymbol.asTerm, rhsFun) + symbolMap(tree.symbol) = newSym + + ctx.inlineTraitState.registerInlineOrigin(newSym, child, parentSym) + + tpd.ClassDefWithParents(newSym.asClass, ctor, tmpl1.parents, tmpl1.body) + case tree => tree + }) + + val rhs2 = ttmap(rhs1) + TreeTypeMap(substFrom = symbolMap.keys.toList, + substTo = symbolMap.values.toList, + oldOwners = symbolMap.keys.toList, + newOwners = symbolMap.values.toList)(rhs2) + + private class ParamAccessorsMapper: + private val paramAccessorsTrees: mutable.Map[Symbol, Map[Name, Tree]] = mutable.Map.empty + private val paramAccessorsNewNames: mutable.Map[(Symbol, Name), Name] = mutable.Map.empty + + def registerParamValuesOf(parent: Tree): Unit = + def allArgs(tree: Tree, acc: Vector[List[Tree]]): List[List[Tree]] = tree match + case Apply(fun, args) => allArgs(fun, acc :+ args) + case TypeApply(fun, _) => allArgs(fun, acc) + case _ => acc.toList + def allParams(info: Type, acc: List[List[Name]]): List[List[Name]] = info match + case mt: MethodType => allParams(mt.resultType, mt.paramNames :: acc) + case pt: PolyType => allParams(pt.resultType, acc) + case _ => acc + val info = + if parent.symbol.isClass then parent.symbol.primaryConstructor.info + else parent.symbol.info + val paramAccessors = allParams(info, Nil).flatten.zip(allArgs(parent, Vector.empty).flatten).toMap + paramAccessorsTrees.put(symbolFromParent(parent), paramAccessors) + + def registerNewName(paramAccessorSym: Symbol): paramAccessorSym.ThisName = + val oldName = paramAccessorSym.name + val newName = oldName.expandedName(parentSym) + paramAccessorsNewNames.put((paramAccessorSym.owner, oldName), newName) + newName + + def getParamAccessorRhs(parent: Symbol, paramAccessorName: Name): Option[Tree] = + paramAccessorsTrees.get(parent).flatMap(_.get(paramAccessorName)) + + def getParamAccessorName(parent: Symbol, paramAccessorName: Name): Option[Name] = + paramAccessorsNewNames.get(parent, paramAccessorName) + end ParamAccessorsMapper + end InlineParentTrait + + + object InlineTraitState: + enum InlineContext: + case InlineTraits, SpecializedTraits, None + + class InlineTraitState( + // For a class symbol created during inlining of an inline trait, + // the chain of inlined traits which produced it. We don't actually care about the order. + // Used as a "seen list" for cycle checking. Persists across invocations of InlineParentTrait + val inlineOrigins: mutable.Map[Symbol, Set[Symbol]] = mutable.HashMap[Symbol, Set[Symbol]]().withDefaultValue(Set.empty), + val inlineTraitsPhase: InlineTraitState.InlineContext = InlineTraitState.InlineContext.None + ): + + def registerInlineOrigin(newSym: Symbol, owner: Symbol, parentSym: Symbol): Unit = + inlineOrigins(newSym) = inlineOrigins(owner) + parentSym + + def copyInPhase(phase: InlineTraitState.InlineContext) = + InlineTraitState(inlineOrigins, phase) + + end InlineTraitState + end Inlines diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index f2782d43c25d..90f6136657df 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -248,6 +248,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case IllegalIdentifierID // errorNumber: 230 case ConcreteClassHasUnimplementedMethodsID // errorNumber: 231 case UseOfAnyMethodAsInterpolatorID // errorNumber: 232 + case IllegalUseOfSpecializedID // errorNumber: 233 + case VarianceInSpecializedTraitsLimitationID // errorNumber: 234 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 7e5fbb1d17ed..c452831237ed 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3944,4 +3944,70 @@ class UseOfAnyMethodAsInterpolator(interpolator: Name)(using Context) i"""String interpolation resolves to methods calls on ${hl("StringContext")}, |which can target methods declared by ${hl("Any")} such as ${hl(i"$interpolator")}. |This is unlikely to be what you intended.""" -} \ No newline at end of file +} + +final class IllegalUseOfSpecialized(using Context) + extends SyntaxMsg(IllegalUseOfSpecializedID): + override protected def msg(using Context): String = + i"Specialized may only be used as a context bound" + override protected def explain(using Context): String = + i"""Specialized allows for a performance improvement by specializing generic type parameters + to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + generic type in inline traits or inline methods: + + inline trait Vec[T: Specialized](val x: T) + + inline def foo[T: Specialized](v: Vec[T]) = v.x + + In this instance it was used in a way which is unsupported, such as + trying to create a type synonym or a value with explicit type Specialized[X]. + """ + +/** Shows up as a TypeError (in the notes field) if variance is attempted + * in a way which is incompatible with specialized traits. */ +final class IllegalVarianceInSpecializedTraitsNote(using Context) extends Note: + def render(using Context): String = + i""" + This use of variance is incompatible with specialized traits. + + Specialized traits achieve a performance gain through a special erasure. + - Primitives are specialized: Foo[Int] erases to Foo$$sp$$Int + - Reference types are specialized to the highest non-top class: Foo[Lion] erases to Foo$$sp$$Animal + - Top classes are erased normally: Foo[Any] / Foo[AnyVal] / Foo[Object] / Foo[AnyRef] erase to Foo. + This means that variance patterns that cross these erasure categories will fail at + runtime due to a ClassCastException, so they are not permitted. + + Please see the docs for more information on how specialized traits are erased. + Suggested fixes: + - Make the type of the target site more general e.g. Foo[Object] instead of Foo[Animal]. + - Reconsider if you really need to use Nothing / Object / Any / AnyRef / AnyVal in your code. + - Remove Specialized from the definition of the corresponding parameter. + """ + + override def covers(other: Note)(using Context): Boolean = + other.isInstanceOf[IllegalVarianceInSpecializedTraitsNote] + +final class VarianceInSpecializedTraitsLimitation(using Context) + extends Message(VarianceInSpecializedTraitsLimitationID): + override def kind = MessageKind.PotentialIssue + override protected def msg(using Context): String = + i"Type parameter is both Specialized and variant. This imposes additional typing restrictions." + override protected def explain(using Context): String = + i"""Specialized traits achieve a performance gain through a special erasure. + - Primitives are specialized: Foo[Int] erases to Foo$$sp$$Int + - Reference types are specialized to the highest non-top class: Foo[Lion] erases to Foo$$sp$$Animal + - Top classes are erased normally: Foo[Any] / Foo[AnyVal] / Foo[Object] / Foo[AnyRef] erase to Foo. + This means that certain variance patterns that cross these erasure categories will fail at + runtime due to a ClassCastException, so they are not permitted. + + For example, treating Foo[Any] as Foo[Animal] via contravariance is not allowed with Specialized. + + Please see the docs for more information on how specialized traits are erased. + + If you accept this limitation you can silence this warning with @nowarn. For example: + + @nowarn("id=E${VarianceInSpecializedTraitsLimitationID.errorNumber}") + inline trait Foo[-T: Specialized]: + + Otherwise, remove Specialized, or remove the variance. + """ diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 5c01aa61f1e4..d12eb7c5cfe9 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -816,10 +816,14 @@ private class ExtractAPICollector(nonLocalClassSymbols: mutable.HashSet[Symbol]) p match case ref: RefTree @unchecked => val sym = ref.symbol - if sym.is(Inline, butNot = Param) && !seenInlineCache.contains(sym) then + if sym.is(Inline, butNot = Param | Trait) && !seenInlineCache.contains(sym) then // An inline method that calls another inline method will eventually inline the call // at a non-inline callsite, in this case if the implementation of the nested call // changes, then the callsite will have a different API, we should hash the definition + // In contrast, if an inline trait A extends an inline trait B, B's body is inlined into + // A, so a change to B will cause a change to A directly, so we can skip this, and if + // an inline method accesses a parameter of an inline trait it doesn't care about the definition + // of the trait. h = MurmurHash3.mix(h, apiDefinition(sym, inlineOrigin).hashCode) case _ => diff --git a/compiler/src/dotty/tools/dotc/transform/Bridges.scala b/compiler/src/dotty/tools/dotc/transform/Bridges.scala index 9a76a5a6be9e..e2bad9415de4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Bridges.scala +++ b/compiler/src/dotty/tools/dotc/transform/Bridges.scala @@ -12,6 +12,7 @@ import ContextFunctionResults.{contextResultCount, contextFunctionResultTypeAfte import StdNames.nme import Constants.Constant import TypeErasure.transformInfo +import TypeErasure.disallowSpecializedCtx import Erasure.Boxing.adaptClosure /** A helper class for generating bridge methods in class `root`. */ @@ -19,7 +20,7 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) { import ast.tpd.* assert(ctx.phase == erasurePhase.next) - private val preErasureCtx = ctx.withPhase(erasurePhase) + private val preErasureCtx = disallowSpecializedCtx(using ctx.withPhase(erasurePhase.prev)) private lazy val elimErasedCtx = ctx.withPhase(elimErasedValueTypePhase.next) private class BridgesCursor(using Context) extends OverridingPairs.Cursor(root) { diff --git a/compiler/src/dotty/tools/dotc/transform/Constructors.scala b/compiler/src/dotty/tools/dotc/transform/Constructors.scala index c81b2de79d6b..984f3061dd50 100644 --- a/compiler/src/dotty/tools/dotc/transform/Constructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/Constructors.scala @@ -242,7 +242,7 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase = constrStats += intoConstr(stat, sym) } else dropped += sym - case stat @ DefDef(name, _, tpt, _) if stat.symbol.isGetter && !stat.symbol.is(Lazy) => + case stat @ DefDef(name, _, tpt, _) if stat.symbol.isGetter && !stat.symbol.is(Lazy) && !stat.symbol.owner.isInlineTrait => val sym = stat.symbol assert(isRetained(sym), sym) if sym.isConstExprFinalVal then diff --git a/compiler/src/dotty/tools/dotc/transform/DesugarSpecializedTraits.scala b/compiler/src/dotty/tools/dotc/transform/DesugarSpecializedTraits.scala new file mode 100644 index 000000000000..850b73fe0aad --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/DesugarSpecializedTraits.scala @@ -0,0 +1,999 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Decorators.i +import dotty.tools.dotc.core.Decorators.className +import dotty.tools.dotc.core.Symbols.{Symbol, ClassSymbol, newNormalizedClassSymbol} +import dotty.tools.dotc.CompilationUnit +import dotty.tools.dotc.core.StdNames.* +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.core.Symbols.newClassSymbol +import scala.Function.const +import dotty.tools.dotc.core.Names.TypeName +import dotty.tools.dotc.core.Symbols.TypeSymbol +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.ast.untpd +import dotty.tools.dotc.core.Symbols.defn +import dotty.tools.dotc.core.Flags.EmptyFlags +import dotty.tools.dotc.ast.TreeTypeMap +import dotty.tools.dotc.core.Scopes.EmptyScope +import dotty.tools.dotc.core.StdNames.str.SPECIALIZED_TRAIT_SUFFIX +import dotty.tools.dotc.core.Names.Name +import tpd._ +import scala.collection.mutable +import scala.annotation.unspecialized +import dotty.tools.dotc.typer.Synthesizer +import dotty.tools.dotc.core.NameKinds +import dotty.tools.dotc.core.Flags.GivenOrImplicit +import dotty.tools.dotc.core.NameKinds.ContextBoundParamName +import dotty.tools.dotc.core.NameKinds.FlatName +import dotty.tools.dotc.inlines.Inlines +import dotty.tools.dotc.util.Spans.Span +import dotty.tools.dotc.report +import dotty.tools.dotc.core.Flags.InlineTrait +import dotty.tools.dotc.core.Annotations.Annotation +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.util.SrcPos +import dotty.tools.dotc.core.Decorators.nestedMap +import dotty.tools.dotc.core.NameOps.expandedName +import dotty.tools.dotc.core.DenotTransformers.DenotTransformer +import dotty.tools.dotc.core.Denotations.SingleDenotation +import dotty.tools.dotc.core.Flags.InlineMethod +import dotty.tools.dotc.core.DenotTransformers.IdentityDenotTransformer +import dotty.tools.dotc.core.Names.TermName +import dotty.tools.dotc.util.Spans.spanCoord +import dotty.tools.dotc.util.Spans.NoSpan +import dotty.tools.dotc.transform.DesugarSpecializedTraits.specializationOf +import dotty.tools.dotc.transform.DesugarSpecializedTraits.isTopClass +import dotty.tools.dotc.reporting.VarianceInSpecializedTraitsLimitation +import dotty.tools.dotc.transform.DesugarSpecializedTraits.isTopClassOrNothing +import dotty.tools.dotc.inlines.Inlines.InlineTraitState +import dotty.tools.dotc.core.Decorators.em + +class DesugarSpecializedTraits extends MiniPhase, IdentityDenotTransformer: + + override def phaseName: String = DesugarSpecializedTraits.name + override def description: String = DesugarSpecializedTraits.description + override def changesMembers: Boolean = false + override def changesParents: Boolean = true + + override def allowsImplicitSearch: Boolean = true + + private def newInterfaceTrait(specialization: Specialization, cache: SpecializationCache)(using Context): (ClassSymbol, SpecializationCache) = { + val tm = specialization.getTypeMap + val inheritedParents = specialization.symbol.denot.info.parents.filterNot(_.typeSymbol == defn.ObjectClass).map(tm(_)) + // Parents may be specializable and so we need to specialize them as well + // See ArrayIterator extends Iterator in specialized-trait-collections-example.scala + val specializations1 = inheritedParents.foldLeft(cache)((specializations, parent) => + (parent, specialization.span) match { + case Specialization(spec) if spec.isSpecialized => specializations.addInterface(spec) + case _ => specializations + } + ) + + // Order is depended on in Erasure::typedClassDef and TypeErasure:eraseParent + val parents = defn.ObjectType + // original trait, specialized to Foo[Int] + :: AppliedType(specialization.symbol.typeRef, specialization.specialization) + // parents of the original trait in the form Foo[Int] (later specialized to Foo$sp$Int) + :: inheritedParents + + // Create new trait + val traitSymbol = newNormalizedClassSymbol( + // For specialized traits defined inside objects/classes etc, pre-Flatten the $sp$ and $impl$ def trees (i.e. + // make them live in the enclosing package with the flattened name). We do this because it's easier than + // finding the defining tree of the object, which would require scanning the whole file, and it + // might be in another compilation unit / already compiled. + specialization.symbol.owner.enclosingPackageClass, + specialization.newSpecializedTraitName, + Flags.Synthetic | Flags.Trait | Flags.Inline, + parents, + tm(specialization.symbol.asClass.classInfo.selfType), + specialization.symbol.privateWithin, + // TODO: Show errors where they actually show up in the inline trait and not at the type of the user + // which is very confusing. Need to use the inline stack (i.e. "this location contains code inlined from ..."). + // In particular if the user does not specify the type explicitly but a specialization of an inline trait + // is the inferred type, the error cursor can point to somewhere very unexpected as the type + // does not appear in the source code. Probably need to change some spans and coords in other places as well. + spanCoord(specialization.span), + specialization.symbol.compilationUnitInfo + ) + + buildTypeParameters(traitSymbol, specialization) + (traitSymbol.entered, specializations1) + } + + private def buildInterfaceTraitTree(specialization: Specialization, interfaceSymbol: ClassSymbol)(using Context) = { + val init = newConstructor(interfaceSymbol, EmptyFlags, Nil, Nil, coord=spanCoord(specialization.span)) + fixConstructor(init, interfaceSymbol) + ClassDef(interfaceSymbol, DefDef(init.entered), Nil).withSpan(specialization.span) + } + + /* Fix constructor so that it: + 1) Has correct generic type parameters + 2) Returns the correct type corresponding to those type parameters applied + 3) Has correct parameter names corresponding to targetParamNames */ + private def fixConstructor(init: Symbol, cls: ClassSymbol, params: List[List[TermName]] = Nil)(using Context) = + val rt = cls.typeRef.appliedTo(cls.typeParams.map(_.typeRef)) + def resultType(tpe: Type, params: List[List[TermName]]): Option[Type] = + tpe match { + case mt @ MethodType(paramNames) => params match { + case head :: tail => + Some(mt.derivedLambdaType(head, mt.paramInfos, resultType(mt.resultType, tail).getOrElse(rt))) + case Nil => + Some(mt.derivedLambdaType(paramNames, mt.paramInfos, resultType(mt.resultType, params).getOrElse(rt))) + } + case pt : PolyType => + Some(pt.derivedLambdaType(pt.paramNames, pt.paramInfos, resultType(pt.resType, params).get)) + case _ => None + } + init.info = resultType(init.info, params).get + init.info = PolyType.fromParams(init.owner.typeParams, init.info) + + private def buildTypeParameters(owner: ClassSymbol, specialization: Specialization)(using Context) = + val tps = newTypeParams( + owner, + specialization.unspecializedTypeParams.map(_.typeSymbol.name.asTypeName), + EmptyFlags, + targets => targets.map(t => specialization.symbol.typeParams.find(_.name == t.name).get.info.bounds) + ) + + tps.foreach(owner.enter(_, EmptyScope)) + + // Replace old type parameters that were copied from original trait with new ones + // inside the parents of the new trait + val tpMap: Map[Type, Type] = specialization.unspecializedTypeParams.zip(tps.map(_.typeRef)).toMap + val freshTypeVarMap = new TypeMap: + def apply(t: Type) = tpMap.applyOrElse(t, mapOver) + + def mapSelfType(st: Type | Symbol): Type | Symbol = st match + case sym: Symbol => sym.copy(info = freshTypeVarMap(sym.info)) + case tpe: Type => freshTypeVarMap(tpe) + + owner.info = ClassInfo( + owner.owner.thisType, + owner, + owner.info.parents.map(freshTypeVarMap(_)), + owner.info.decls, + mapSelfType(owner.classInfo.selfInfo) + ) + + // Order is depended on in Erasure::typedClassDef and TypeErasure:eraseParent + private def getImplementationClassParents(specialization: Specialization, owner: Option[ClassSymbol])(using Context) = + // Set using old unspecializedTypeParams and replace after. + val specializedTrait = owner.map(_.typeRef.appliedTo(specialization.unspecializedTypeParams)) + + val originalTraitSpecializedParent = AppliedType( + specialization.symbol.typeRef, + specialization.mapTypeArguments(tpe => specializationOf(tpe), specialization.unspecializedTypeParams) + ) + + (defn.ObjectType, specializedTrait, originalTraitSpecializedParent) + + private def newImplementationClass(specialization: Specialization, owner: Option[ClassSymbol])(using Context) = + val (objectParent, traitSpParent, originalTraitSpecializedParent) = getImplementationClassParents(specialization, owner) + + val parents = + if traitSpParent.nonEmpty then + List(objectParent, traitSpParent.get, originalTraitSpecializedParent) + else + List(objectParent, originalTraitSpecializedParent) + + val newImplementationClassSymbol = newNormalizedClassSymbol( + specialization.symbol.owner.enclosingPackageClass, + specialization.newImplementationClassName, + Flags.Synthetic, + parents, + NoType, + specialization.symbol.privateWithin, + spanCoord(specialization.span), + specialization.symbol.compilationUnitInfo + ) + + specialization.symbol + .addAnnotation(Annotation.Child(newImplementationClassSymbol, newImplementationClassSymbol.span.startPos)) + + buildTypeParameters(newImplementationClassSymbol, specialization) + + newImplementationClassSymbol.entered + + // intefaceSymbol: None if no interface; this only happens with the fully non-specialized $impl$ (raw) case + private def buildImplementationClassTree(specialization: Specialization, interface: Option[ClassSymbol], cls: ClassSymbol)(using Context) = { + val (objectParent, traitSpParent_, originalTraitSpecializedParent_) = getImplementationClassParents(specialization, interface) + + /* Apply Type Param Fix */ + val tpMap: Map[Type, Type] = specialization.unspecializedTypeParams.zip(cls.typeParams.map(_.typeRef)).toMap + val freshTypeVarMap = new TypeMap: + def apply(t: Type) = tpMap.applyOrElse(t, mapOver) + val traitSpParent = traitSpParent_.map(tp => freshTypeVarMap(tp)) + val originalTraitSpecializedParent = freshTypeVarMap(originalTraitSpecializedParent_) + + val init = newConstructor(cls, EmptyFlags, Nil, Nil, coord=spanCoord(specialization.span)) + val typeMap = new TypeMap { + override def apply(t: Type) = specialization.specializedConstructorParamToArgumentTypeMap.applyOrElse(t, mapOver) + } + + /* Create constructor and setup constructor type */ + val nonTypeParams = specialization.symbol.primaryConstructor.rawParamss.tail + + val oldTypeParams = specialization.unspecializedConstructorParams + val initTypeParams = cls.typeParams.map { + sym => sym.copy(owner = init, flags = (sym.flags &~ (Flags.Private | Flags.Deferred))) + } + + // We need to map the parameter names to avoid a name clash with val params from parents + // (see tests/pos/specialized-trait-val-parameter.scala) + val valueParams = nonTypeParams map { + symbols => symbols map { + param => param.copy( + owner = init, + info = typeMap(param.info).substSym(oldTypeParams, initTypeParams), + name=param.name.expandedName(cls) + ) + } + } + + initTypeParams.foreach(_.entered) + init.setParamss(initTypeParams :: valueParams) + + // Type Arg if specialized; otherwise we want our type param. + init.info = specialization.symbol.primaryConstructor.info.appliedTo( + specialization.constructorTypeParams map { + param => specialization.specializedConstructorParamToArgumentTypeMap.applyOrElse( + param, _.subst(oldTypeParams, cls.typeParams.map(_.typeRef)) + ) + } + ) + fixConstructor(init, cls, valueParams.map(_.map(_.name.asTermName))) + + /* Build param accessors */ + val paramAccessorss = valueParams.map { + params => params map { + sym => sym.copy( + owner = cls, + flags = (sym.flags|Flags.LocalParamAccessor) &~ Flags.Param, + info = sym.info.subst(initTypeParams, cls.typeParams.map(_.typeRef)) + ) + } + } + paramAccessorss.foreach(_.foreach(cls.enter(_))) + + /* Build class def tree */ + val newParamss = paramAccessorss.nestedMap(ref(_)) + val newParams1 = if (newParamss.length == 1) then newParamss ++ List(List()) else newParamss + + /* Re-expand varargs parameters from Seq[T] to *T for passing into parent constructor */ + val newParams2 = newParams1.nestedMap( param => + if param.symbol.info.hasAnnotation(defn.RepeatedAnnot) then ctx.typer.seqToRepeated(param) else param + ) + + val opTree = New(objectParent, objectParent.classSymbol.primaryConstructor.asTerm, Nil) + val tspTree = traitSpParent.map(tsp => New(tsp, tsp.classSymbol.primaryConstructor.asTerm, Nil)) + val opSpTree = + New(originalTraitSpecializedParent.typeConstructor) + .select(TermRef(originalTraitSpecializedParent.typeConstructor, specialization.symbol.primaryConstructor.asTerm)) + .appliedToTypes(originalTraitSpecializedParent.argTypes) + .appliedToArgss(newParams2) + + ClassDefWithParents( + cls, + DefDef(init.asTerm.entered), + if tspTree.nonEmpty then List(opTree, tspTree.get, opSpTree) else List(opTree, opSpTree), + paramAccessorss.flatMap(syms => syms.map(sym => tpd.ValDef(sym.asTerm))) + ).withSpan(specialization.span) + } + + // Returns (new stmts including original, new symbols including original) + private def transformStatements(stats1: List[Tree], cache: SpecializationCache)(using Context): (List[Tree], SpecializationCache) = { + + val inlineSpecializedMethods = new TreeMapWithPreciseStatContexts { + override def transform(tree: Tree)(using Context): Tree = tree match { + case MethodSpecialization(methSpec) + if methSpec.isSpecialized || methSpec.isFullySpecializedToTopClassesOrNothing => + val inlinedTree = Inlines.inlineCall(tree)(using specializedTraitCtx) + super.transform(inlinedTree) + case tree => super.transform(tree) + } + } + + val stats = inlineSpecializedMethods.transform(stats1) + + val specializations1 = collectReferencedSpecializations(stats, cache) + val generatedTraitStats = specializations1.getNewInterfaceSymbols.toList.map(buildInterfaceTraitTree) + val generatedClassStats = specializations1.getNewImplementationSymbols.toList.map(buildImplementationClassTree) + + val specializations2 = specializations1.installNewInterfaceSymbols.installNewImplementationSymbols + + val generatedTraitStats1 = generatedTraitStats.map { + case tree: TypeDef => + assert(tree.symbol.isInlineTrait) + val inlined = { + given Context = specializedTraitCtx + Inlines.inlineParentInlineTraits(Inlines.checkAndTransformInlineTrait(tree)) + } + + // Inlined body may contain references to inline traits that need to be inlined as well + // See tests/neg/specialized-trait-inlining-causes-implementation-required-loop-bad-manual.scala + // specializeInlineTraits is responsible for inlining into D because it's not $sp$ or $impl$ + // Because this code is synthetic we won't run the phase on this code as part of the usual + // megaphase transform so we need to do it manually. + val newInlineCtx = specializedTraitCtx.fresh.setInlineTraitState( + specializedTraitCtx.inlineTraitState.copyInPhase(InlineTraitState.InlineContext.InlineTraits) + ) + transformFollowing(inlined)(using newInlineCtx) + + } + + val generatedClassStats1 = generatedClassStats.map { + case tree: TypeDef => + val inlined = Inlines.inlineParentInlineTraits(tree)(using specializedTraitCtx) + val newInlineCtx = ctx.fresh.setInlineTraitState( + ctx.inlineTraitState.copyInPhase(InlineTraitState.InlineContext.InlineTraits) + ) + transformFollowing(inlined)(using newInlineCtx) + } + + val (generatedTraitStatsFinal, generatedClassStatsFinal, specializationsFinal) = + if (generatedTraitStats1.isEmpty && generatedClassStats1.isEmpty) + (generatedTraitStats1, generatedClassStats1, specializations2) + else + val (generatedTraitStats2, specializations3) = transformStatements(generatedTraitStats1, specializations2) + val (generatedClassStats2, specializations4) = transformStatements(generatedClassStats1, specializations3) + + (generatedTraitStats2, generatedClassStats2, specializations4) + + // Since the only change we make to stats1 => stats is inlining we could arguably "undo" + // the inlining and then redo it at the "correct" point later - so we don't actually modify + // the tree in that way here. Then we would only be generating new class and trait stats and + // we wouldn't need a transform statements method at all + // (just a "generateInlineTraitsInterfaceAndImplementation" or something), + // but not sure if that's worth doing (it would be throwing away work). + (generatedTraitStatsFinal ++ generatedClassStatsFinal ++ stats, specializationsFinal) + } + + private def checkSpecializedTraitRules(tree: Tree)(using Context) = + def checkType(t: Type, pos: SrcPos) = t.widen.dealias match { + case SpecializedEvidence(_) => + report.error(s"Only inline traits and inline functions may take Specialized type parameters", pos) + case _ => + } + + // TODO: Depending on how we ultimately organize the phasing + // If we settle on using miniphases, this could be moved into transformDefDef and transformBlock. + tree.foreachSubTree { + case ddef: DefDef if ddef.symbol.isConstructor && !ddef.symbol.owner.is(Flags.Inline) => + ddef.paramss.flatten.foreach(p => checkType(p.tpe, ddef.srcPos)) + case ddef: DefDef if !ddef.symbol.isConstructor && !ddef.symbol.is(Flags.Inline) => + ddef.paramss.flatten.foreach(p => checkType(p.tpe, ddef.srcPos)) + case AnonymousClassInstance(anon) => + def deandify(tp: Type): Iterator[Type] = tp match { + case AndType(l, r) => deandify(l) ++ deandify(r) + case _ => Iterator.single(tp) + } + anon.typeTree.tpe match { + case a: AndType => /* Multiple mixed in traits will be typed as an AndType */ + deandify(a) foreach {trt => + Specialization.unapply(trt, anon.typeTree.span) foreach { spec => + if spec.hasSpecializedParams then + report.error( + """ + Anonymous classes acting as instances of Specialized traits may not mix in other traits; + You can make a named object instead if you like. + """, + anon.srcPos + ) + } + } + + case tpe => + Specialization.unapply(tpe, anon.typeTree.span).map(spec => + { + if spec.hasSpecializedParams then + // Only allowed to contain evidence parameters + if anon.body.filterNot(x => x.symbol.name.is(ContextBoundParamName)).nonEmpty then + report.error( + """ + Anonymous classes acting as instances of Specialized traits may not have additional members; + you can make a named object instead if you like. + """, + anon.srcPos + ) + + anon.parentCalls match { + case (obj :: parentsOfSpecTrait) :+ (app@Apply(_, _)) + if (obj.symbol.owner == ctx.definitions.ObjectClass) && + (parentsOfSpecTrait.forall(x => spec.symbol.asClass.baseClasses.exists(p => p == x.symbol.owner))) => + case _ => + report.error( + """ + Anonymous classes acting as instances of Specialized traits may not mix in other traits; + you can make a named object instead if you like.""", + anon.srcPos + ) + } + else + tree + }).getOrElse(tree) + } + + case _ => + } + + private def specializedTraitCtx(using Context): Context = + ctx.fresh.setInlineTraitState(ctx.inlineTraitState.copyInPhase(InlineTraitState.InlineContext.SpecializedTraits)) + + private var specializedTraitCache = SpecializationCache( + genInterfaceSymbol = newInterfaceTrait, + genImplementationSymbol = newImplementationClass + ) + + // TODO: I reckon to get the best miniphase style processing we can do everything except + // outputting the final $impl$ / $sp$ classes in the normal miniphase methods. + // i.e. instead of folding with transformStatements + // (which is odd now that we don't actually transform anything except creating the new specializations) + // we have all of the cases for collecting specialized references in ordinary transformMethods or prepare methods and + // then we just spit out the final classes in this transformUnit function. + // As long as we remember to call transformFollowing on the synthetic classes (which we do) then + // we should be composable in the way that we want to be. + override def transformUnit(tree: Tree)(using Context): Tree = + tree match { + case pkg@PackageDef(pid, stats) => + checkSpecializedTraitRules(tree) + + val (stats1, specializedTraitCache2) = transformStatements(stats, specializedTraitCache) + + specializedTraitCache = specializedTraitCache2 + + val grouped = stats1.groupBy(tree => tree.symbol.enclosingPackageClass) + + // We need to copy the existing package so we don't lose any attachments + // e.g. attachments used to calculate Wunused + cpy.PackageDef(pkg)(Ident(defn.EmptyPackageVal.namedType), + grouped.getOrElse(defn.RootClass, List()) ::: + grouped.getOrElse(defn.EmptyPackageClass, List()) ::: + grouped + .toList + .filter((pk, stmts) => pk != defn.RootClass && pk != defn.EmptyPackageClass) + .map((pkg, stmts) => tpd.PackageDef(Ident(pkg.sourceModule.namedType), stmts)) + ).withType(defn.EmptyPackageVal.namedType) + case t => t + } + + private def collectReferencedSpecializations(stats: List[Tree], specializations: SpecializationCache)(using Context): SpecializationCache = + stats.foldLeft(specializations) { + (specializations, tree) => { + tree.deepFold(specializations) { + (specializations, tree) => tree match + case tdef@TypeDef(name, tmpl: Template) if tdef.symbol.isSpecializedTrait => + if !tdef.symbol.isStatic then + // The approach we use for flattening makes this quite tricky: + // see e.g. tests/neg/specialized-trait-scoped-inside-object-deep-nesting.scala. + // In theory can scan the tree to find where to put the generated traits instead, + // but this still doesn't work cross-CU, so for now we ban. + report.error( + """ + Specialized traits may not be defined inside classes or traits + this would make them path-dependent which is not currently supported); + They may be defined inside objects. + """, + tdef.symbol.srcPos + ) + + tdef.symbol.typeParams.foreach { + param => + val isVariant = param.paramVariance.isOneOf(Flags.Contravariant | Flags.Covariant) && + Specialization.classSpecializedTypeParams(tdef.symbol).exists(tpe => tpe.typeSymbol == param) + if isVariant then + report.warning(VarianceInSpecializedTraitsLimitation(), param.srcPos) + } + specializations + case Typed(Apply(Select(New(anon),ctor),List()), tpt: TypeTree) if anon.symbol.isAnonymousClass => + (tpt.tpe, tpt.span) match { + case Specialization(spec) if spec.isFullySpecializedToTopClassesOrNothing => + // We never inline into anonymous class instances (avoids cycles in inline trait inlining), + // so all anonymous class instances must have a non-anonymous class final representation as an $impl$ class. + specializations.addErasedImplementation(spec) + + case Specialization(spec) if spec.isSpecialized => + specializations.addInterfaceAndImplementation(spec) + + case _ => specializations + } + case Specialization(specialization) => + if (specialization.hasSpecializedParams) { + // Block Vec[?] and similar + specialization.specializedTypeArgs filter { + case _: WildcardType => true + case _: RealTypeBounds => true + case tpe => + false + } foreach { tpe => + report.error( + "Wildcard types may not be substituted for Specialized type parameters.", + ctx.source.atSpan(specialization.span) + ) + } + } + if (specialization.isSpecialized) { + specializations.addInterface(specialization) + } else { + // Check foo[S: Specialized] <= Vec[S: Specialized] + specialization.specializedTypeArgs flatMap { + // For each type we are using in a Specialized position + // Find all type params within that type that are not marked as Specialized so we can error + arg => + arg.widen.dealias.namedPartsWith { + part => + def hasCorrectlyMarkedTypeParam: Boolean = + val owner = + if part.typeSymbol.owner.isClass then + part.typeSymbol.owner.primaryConstructor + else + part.typeSymbol.owner + + owner.paramSymss.flatten.exists { + sym => sym.info match { + case SpecializedEvidence(tpeArg) => + tpeArg.typeSymbol.isTypeParam && tpeArg.typeSymbol.name == part.name + case _ => false + } + } + + part.typeSymbol.isTypeParam && !hasCorrectlyMarkedTypeParam + } + } foreach { tpe => + if tpe.denot.symbol.srcPos.span.exists then + report.error( + s"${tpe.typeSymbol} used in a Specialized position, so it must be marked as Specialized at its definition.", + tpe.denot.symbol.srcPos + ) + } + + specializations + } + + case app @ Apply(_, _) => tpd.methPart(app) match { // class / object Bar extends Foo[Int](params) + case fun @ Select(New(tpt), init) if fun.symbol.isConstructor => tpd.allArgss(tree) match { + case typeArgs :: valueArgss => + val spec = Specialization(fun.symbol.owner, typeArgs.map(_.tpe), app.span) + if spec.isSpecialized then specializations.addInterface(spec) else specializations + case _ => specializations + } + case _ => specializations + } + + case _ => specializations + } + } + } +end DesugarSpecializedTraits + +object DesugarSpecializedTraits: + val name: String = "desugarSpecializedTraits" + val description: String = + """ + Identifies traits having type parameters that have the Specialized annotation and generates corresponding specialized versions + """ // Replacement with specialized versions occurs in erasure. + + def isTopClass(s: Symbol)(using Context): Boolean = + (s eq defn.AnyClass) || (s eq defn.AnyValClass) || (s eq defn.ObjectClass) || (s eq defn.AnyRefAlias) + + def isTopClassOrNothing(s: Symbol)(using Context): Boolean = (s eq defn.NothingClass) || isTopClass(s) + + def specializationOf(tp: Type)(using Context): Type = + if (tp.isInstanceOf[ErrorType]) then + return defn.ObjectClass.typeRef + + def isSimpleClassType(s: Symbol): Boolean = + s.isClass && !s.is(Flags.Trait) && s.typeParams.isEmpty && s.isStatic + + def isSelfOrParentTopType(s: Symbol): Boolean = + isTopClassOrNothing(s) || isTopClassOrNothing(s.asClass.superClass) + + tp.baseClasses + .find(c => isSimpleClassType(c) && isSelfOrParentTopType(c)) match + case None => ErrorType(em"Missing specialization type for $tp") + case Some(tpe) => tpe.typeRef + + def isSameErasureBucket(tp1: Type, tp2: Type)(using Context): Boolean = + val sp1 = specializationOf(tp1) + val sp2 = specializationOf(tp2) + (sp1 eq sp2) || isTopClassOrNothing(tp1.classSymbol) && isTopClassOrNothing(tp2.classSymbol) + +end DesugarSpecializedTraits + +/* + Stores the specializations we have found in the program and the symbols for the interface traits and implementation classes + that will replace them. We generate these symbols when we enter the specializations into the cache, via the functions + we store in genInterfaceSymbol and genImplementationSymbol. + + Model: Contains two levels: + - interface/implementation symbols we have found since the last installNewInterface/ImplementationSymbols call + (i.e. typically on this iteration) ("new") + - Those we found prior to that call, that were thus installed by it or previously + + Invariant: (newImplementationSymbols ∪ implementationSymbols) ⊆ (interfaceSymbols ∪ newInterfaceSymbols) except + for the "erased" implementationsclass Foo$impl which correspond to the ordinary erased interface Foo which is not stored. +*/ +object SpecializationCache: + type SymbolMap = Map[Specialization, ClassSymbol] + type GenInterfaceSymbol = (Specialization, SpecializationCache) => Context ?=> (ClassSymbol, SpecializationCache) + type GenImplementationSymbol = (Specialization, Option[ClassSymbol]) => Context ?=> ClassSymbol + +class SpecializationCache( + private val newInterfaceSymbols: SpecializationCache.SymbolMap = Map.empty, + private val newImplementationSymbols: SpecializationCache.SymbolMap = Map.empty, + private val interfaceSymbols: SpecializationCache.SymbolMap = Map.empty, + private val implementationSymbols: SpecializationCache.SymbolMap = Map.empty, + private val genInterfaceSymbol: SpecializationCache.GenInterfaceSymbol, + private val genImplementationSymbol: SpecializationCache.GenImplementationSymbol +): + + def copy( + newInterfaceSymbols: SpecializationCache.SymbolMap = this.newInterfaceSymbols, + newImplementationSymbols: SpecializationCache.SymbolMap = this.newImplementationSymbols, + interfaceSymbols: SpecializationCache.SymbolMap = this.interfaceSymbols, + implementationSymbols: SpecializationCache.SymbolMap = this.implementationSymbols, + genInterfaceSymbol: SpecializationCache.GenInterfaceSymbol = this.genInterfaceSymbol, + genImplementationSymbol: SpecializationCache.GenImplementationSymbol = this.genImplementationSymbol) + = SpecializationCache( + newInterfaceSymbols, + newImplementationSymbols, + interfaceSymbols, + implementationSymbols, + genInterfaceSymbol, + genImplementationSymbol + ) + + def getInterfaceSymbol(spec: Specialization): Option[ClassSymbol] = + newInterfaceSymbols.orElse(interfaceSymbols).lift(spec) + + def getImplementationSymbol(spec: Specialization): Option[ClassSymbol] = + newImplementationSymbols.orElse(implementationSymbols).lift(spec) + + def getNewInterfaceSymbols: List[(Specialization, ClassSymbol)] = newInterfaceSymbols.toList + + def getNewImplementationSymbols: List[(Specialization, Option[ClassSymbol], ClassSymbol)] = + newImplementationSymbols.map((k, v) => (k, getInterfaceSymbol(k), v)).toList + + def addInterface(spec: Specialization)(using Context): SpecializationCache = + if (newInterfaceSymbols.contains(spec) || interfaceSymbols.contains(spec)) then + this + else + val (targetSymbol, resultingCache) = genInterfaceSymbol(spec, this) + resultingCache.copy(newInterfaceSymbols = resultingCache.newInterfaceSymbols + (spec -> targetSymbol)) + + def addErasedImplementation(spec: Specialization)(using Context): SpecializationCache = + val erased = Specialization( + spec.symbol, + spec.mapTypeArguments(_ => defn.AnyClass.typeRef, spec.unspecializedTypeArgs), + spec.span + ) + + if (newImplementationSymbols.contains(erased) || implementationSymbols.contains(erased)) then + this + else + copy( + newImplementationSymbols = + newImplementationSymbols + (erased -> genImplementationSymbol(erased, getInterfaceSymbol(erased))) + ) + + def addInterfaceAndImplementation(spec: Specialization)(using Context): SpecializationCache = + if (newImplementationSymbols.contains(spec) || implementationSymbols.contains(spec)) then + this + else + val withInterface = addInterface(spec) + withInterface.copy( + newImplementationSymbols = + withInterface.newImplementationSymbols + (spec -> genImplementationSymbol(spec, withInterface.getInterfaceSymbol(spec))) + ) + + def installNewInterfaceSymbols = + this.copy( + newInterfaceSymbols = Map.empty, + interfaceSymbols = interfaceSymbols ++ newInterfaceSymbols) + + def installNewImplementationSymbols = + this.copy( + newImplementationSymbols = Map.empty, + implementationSymbols = implementationSymbols ++ newImplementationSymbols) + +end SpecializationCache + +/* Represents an application traitSymbol[typeArguments] */ +class Specialization(val symbol: Symbol, val args: List[Type], val span: Span)(using Context): + + private val paramToArgList = symbol.typeParams.map(_.typeRef).zip(args) + + // Type parameters marked with Specialized + val specializedTypeParams: List[Type] = Specialization.classSpecializedTypeParams(symbol) + + // Type parameters not marked with Specialized + val unspecializedTypeParams: List[Type] = + paramToArgList + .filterNot((tParam, tArg) => specializedTypeParams.exists(_ =:= tParam)) + .map(_._1) + + // Type arguments provided to parameters that are marked with Specialized at their definition + val specializedTypeArgs: List[Type] = + paramToArgList + .filter((tParam, tArg) => specializedTypeParams.exists(_ =:= tParam)) + .map(_._2) + + // Type arguments provided to parameters that are not marked with Specialized at their definition + val unspecializedTypeArgs: List[Type] = + paramToArgList + .filterNot((tParam, tArg) => specializedTypeParams.exists(_ =:= tParam)) + .map(_._2) + + val specializedTypeParamsToTypeArgumentsMap: Map[Type, Type] = + paramToArgList + .toMap + .filter((k, v) => specializedTypeParams.exists(_ =:= k)) + .map { + case (param, arg) => param -> specializationOf(arg) + } + + val specialization: List[Type] = + symbol.typeParams + .map(_.typeRef) + .map(param => specializedTypeParamsToTypeArgumentsMap.applyOrElse(param, _ => param)) + + val hasSpecializedParams: Boolean = specializedTypeParams.nonEmpty + + def constructorTypeParams: List[Type] = symbol.primaryConstructor.rawParamss.head.map(_.typeRef) + + def unspecializedConstructorParams: List[Symbol] = + symbol.primaryConstructor.rawParamss + .head + .zip(symbol.typeParams) + .filterNot((constrParam, typeParam) => specializedTypeParams.exists(_ =:= typeParam.typeRef)) + .map((constrParam, typeParam) => constrParam) + + def specializedConstructorParamToArgumentTypeMap: Map[Type, Type] = + symbol.primaryConstructor.rawParamss + .head + .map(_.typeRef) + .zip(paramToArgList) + .filter((constrParam, paramArg) => specializedTypeParams.exists(_ =:= paramArg._1)) + .map((constrParam, paramArg) => (constrParam, specializationOf(paramArg._2))) + .toMap + + def mapTypeArguments(specialize: Type => Type, nonspecialized: List[Type]): List[Type] = { + val (result, _) = paramToArgList.foldLeft(Vector.empty[Type], nonspecialized) { + case ((accumulator, defaults), (param, arg)) => + if specializedTypeParams.exists(_ =:= param) then + (accumulator :+ specialize(arg), defaults) + else defaults match + case head :: next => (accumulator :+ head, next) + case Nil => (accumulator, Nil) + } + + result.toList + } + + /* If inline trait Foo[T: Specialized] has a method taking another Foo[T] there's no point specializing the reference + since the resulting sp$T$ would be the same as the starting trait. Also A[Object] specializes to A. */ + def isSpecialized: Boolean = + hasSpecializedParams && specializedTypeArgs.forall(tpe => !isTopClassOrNothing(specializationOf(tpe).classSymbol)) + + // Only works before erasure. + def isFullySpecialized: Boolean = + !specializedTypeArgs.exists(_.existsPart(part => (part.typeSymbol.isTypeParam))) + + def isFullySpecializedToTopClassesOrNothing: Boolean = + hasSpecializedParams && + isFullySpecialized && + specializedTypeArgs.forall(tpe => isTopClassOrNothing(specializationOf(tpe).classSymbol)) + + // TODO: Do we want to compress this more by adopting e.g. specializedTypeNames from scala 2? + // Using the exact Scala 2 naming scheme might be tricky since we currently check if "$$sp$" is contained within + // the name in order to figure out if it is a Scala 3 specialization specialized trait interface, + // therefore we need to avoid name conflicts. + // However we could keep our existing scheme but use Scala 2 specialization naming scheme for the typenames + // after the separator e.g. I instead of scala.Int. + // That is probably worth doing from a compression standpoint. + // Alternatively to avoid the conflict we can create our own NameKind for scala 3 specialized names and + // then use the presence of that to determine if a symbol comes from scala 3 specialization. + def canonicalName(tp: Type)(using Context): String = tp.dealias match + case AppliedType(tycon, args) => + canonicalName(tycon) + args.map(canonicalName).mkString("$_$") + case other => + other.typeSymbol.fullName.toString.replace('.', '$') + + def getTypeMap(using Context) = new TypeMap { + override def apply(tp: Type): Type = specializedTypeParamsToTypeArgumentsMap.applyOrElse(tp, mapOver) + } + + def generateName(suffix: String)(using Context) = + val className = (symbol.name ++ suffix ++ "$").asTypeName + val args = specializedTypeArgs + .map(tpe => canonicalName(specializationOf(tpe))) + .mkString(str.SPECIALIZED_TRAIT_TYPE_SEP) + + val name = className ++ args + if symbol.owner.is(Flags.Package) then + name + else + FlatName(symbol.owner.flatName.toTermName, name.toTermName).toSimpleName.toTypeName + + def newImplementationClassName(using Context): TypeName = + if isFullySpecializedToTopClassesOrNothing then + (symbol.name ++ str.SPECIALIZED_TRAIT_IMPL_SUFFIX).asTypeName + else + generateName(str.SPECIALIZED_TRAIT_IMPL_SUFFIX) + + def newSpecializedTraitName(using Context): TypeName = + generateName(str.SPECIALIZED_TRAIT_SUFFIX) + + // We only care about the specialized arguments for equality; + // a specialization of Vec[A: Specialized, B] with B = Int and one with B = String + // can be considered to be the same as they use the same specialized trait + override def equals(obj: Any): Boolean = obj match + case obj: Specialization => + obj.symbol == symbol && + specializedTypeArgs.zip(obj.specializedTypeArgs).forall((a1, a2) => specializationOf(a1) == specializationOf(a2)) + case _ => false + + override def hashCode(): Int = + (symbol, specializedTypeArgs.map(tpe => specializationOf(tpe))).hashCode() + + override def toString(): String = + s"Specialization(${symbol}, ${args}, ${span})" +end Specialization + +/* Represents an application methodSymbol[typeArguments](termArgs1)(termArgs2) etc */ +class MethodSpecialization(val methodSymbol: Symbol, val typeArgss: List[List[Tree]])(using Context): + val specializedTypeParams: List[Type] = + methodSymbol.paramSymss + .flatten + // Type parameters marked with Specialized + .collect { + denot => denot.info match { + case SpecializedEvidence(typeVar) => typeVar + } + } + private val paramToArgList = + methodSymbol.paramSymss + .filter(l => l.nonEmpty && l.head.is(Flags.TypeParam)) + .zip(typeArgss) + .map((params, args) => params.map(_.typeRef.asInstanceOf[Type]).zip(args)) + .flatten + + val specializedTypeArgs: List[Tree] = + paramToArgList + .filter((tParam, tArg) => specializedTypeParams.exists(_ =:= tParam)) + .map(_._2) + + val hasSpecializedParams: Boolean = specializedTypeParams.nonEmpty + + def isSpecialized: Boolean = + methodSymbol.isSpecializedMethod && + hasSpecializedParams && + specializedTypeArgs.exists(tree => !isTopClass(specializationOf(tree.tpe).classSymbol)) + + def isFullySpecialized: Boolean = + !specializedTypeArgs.exists(_.tpe.existsPart(part => (part.typeSymbol.isTypeParam))) + + def isFullySpecializedToTopClassesOrNothing: Boolean = + methodSymbol.isSpecializedMethod && + hasSpecializedParams && + isFullySpecialized && + specializedTypeArgs.forall(tr => isTopClassOrNothing(specializationOf(tr.tpe).classSymbol)) + +end MethodSpecialization + +object SpecializedEvidence { + def unapply(tpe: Type)(using Context): Option[Type] = tpe match { + case AppliedType(tycon, List(tpeArg)) if (tycon =:= ctx.definitions.SpecializedClass.typeRef && tpeArg.typeSymbol.isTypeParam) => Some(tpeArg) + case _ => None + } +} + +object Specialization: + + def unapply(tpt: Tree)(using Context): Option[Specialization] = tpt match { + case AppliedTypeTree(specializedTrait: Ident, concreteTypeTrees: List[Tree]) => + Some(Specialization(specializedTrait.denot.symbol, concreteTypeTrees.map(_.tpe), tpt.span)) + case t: TypeTree => Specialization.unapply(t.tpe, t.span) + case _ => None + } + + def unapply(typeSpan: (Type, Span))(using Context): Option[Specialization] = typeSpan match { + case (AppliedType(tycon: Type, args: List[Type]), span) => Some(Specialization(tycon.typeSymbol, args, span)) + case _ => None + } + + def unapply(tpe: Type)(using Context): Option[Specialization] = tpe match { + case AppliedType(tycon: Type, args: List[Type]) => Some(Specialization(tycon.typeSymbol, args, NoSpan)) + case _ => None + } + + def classSpecializedTypeParams(classSym: Symbol)(using Context): List[Type] = + if !classSym.isClass || classSym.is(Flags.JavaDefined) then + List.empty + else + classSym.unforcedDecls.implicitDecls.collect(_.info match { case SpecializedEvidence(typeVar) => typeVar }) + + def methodSpecializedTypeParams(methodSym: Symbol)(using Context): List[Type] = + methodSym.paramSymss + .flatten + .collect { + denot => denot.info match { + case SpecializedEvidence(typeVar) => typeVar + } + } + + def anonymousClassIsSpecialized(tree: Tree)(using Context) = + tree match { + case TypeDef(anon, Template(_, parentCalls: List[Tree], _, _)) => + parentCalls match { + case _ :+ Apply(Apply(t, ctorArgs), ev) => // extends Object, parents of spec trait, spec trait + val spec = Specialization.unapply(t.tpe.resultType.resultType, t.span) + spec.get.hasSpecializedParams + case _ => false + } + case _ => false + } + + def isSpecializedTrait(sym: Symbol)(using Context) = + sym.isClass && + sym.isAllOf(InlineTrait) && + classSpecializedTypeParams(sym).nonEmpty + + def isSpecializedMethod(sym: Symbol)(using Context) = + !sym.is(Flags.JavaDefined) && + sym.isAllOf(InlineMethod) && + methodSpecializedTypeParams(sym).nonEmpty + + def traitParamIsSpecialized(traitSym: Symbol, tParam: Symbol)(using Context) = + classSpecializedTypeParams(traitSym).exists(tp => tp.typeSymbol eq tParam) + +end Specialization + +object MethodSpecialization: + + def unapply(tree: Tree)(using Context) = tree match { + case app: Apply => + val methSym = tpd.methPart(app).symbol + if methSym.is(Flags.Method) then + Some(MethodSpecialization(methSym, tpd.typeArgss(app))) + else + None + case _ => None + } + +end MethodSpecialization + +class AnonymousClassInstance( + val srcPos: SrcPos, + val symbol: Symbol, + val body: List[Tree], + val parentCalls: List[Tree], + val ctor: Name, + val typeTree: TypeTree +) + +object AnonymousClassInstance: + + def unapply(tree: Tree)(using Context) = tree match { + case Block( + List(an@TypeDef(anon, tmpl@Template(_, parentCalls: List[Tree], _, _))), + Typed(Apply(Select(New(anon1),ctor), _), t: TypeTree) + ) if anon1.symbol.isAnonymousClass && (anon1.symbol eq an.symbol) => + Some(AnonymousClassInstance(an.srcPos, an.symbol, tmpl.body, parentCalls, ctor, t)) + + // Coverage testing creates this extra case + case Block( + List(an@TypeDef(anon, tmpl@Template(_, parentCalls: List[Tree], _, _))), + Typed(Block(bindings, Apply(Select(New(anon1),ctor), _)), t: TypeTree) + ) if anon1.symbol.isAnonymousClass && (anon1.symbol eq an.symbol) => + Some(AnonymousClassInstance(an.srcPos, an.symbol, tmpl.body, parentCalls, ctor, t)) + + case _ => None + } + +end AnonymousClassInstance diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 443b6df36397..8967b5a52b82 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -37,6 +37,7 @@ import core.Mode import util.Property import reporting.* import scala.annotation.tailrec +import dotty.tools.dotc.ast.tpd.* class Erasure extends Phase with DenotTransformer { @@ -729,6 +730,12 @@ object Erasure { erasure( inContext(preErasureCtx): tree.qualifier.typeOpt.widen.finalResultType) + + def hasImmediateInlineParent: Boolean = { + owner.isInlineTrait && + (qual1.tpe.widenDealias.classSymbol ne sym.owner) && + qual1.tpe.widenDealias.classSymbol.info.parents.exists(_.classSymbol eq sym.owner) + } if qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType then recur(box(qual)) @@ -740,6 +747,17 @@ object Erasure { adaptIfSuper(qual) match case qual1: Super => select(qual1, sym) + case qual1 if hasImmediateInlineParent => + // If A is an inline trait and A.foo was inlined into B, references to b.foo (val b = B()) will still + // point to A.foo until now. We want them to point to B.foo so we get the benefit of specialization. + // We fix that here rather than in a separate phase because + // it needs to happen coordinated with erasure of Specialized traits, so that: + // a) we see the erased A$sp$Int traits and can point at their members + // b) we make the replacement before boxing in case A.foo is typed with T and B.foo specializes this to e.g. Int + // Otherwise we will end up with Int.unbox(B.foo) instead of directly B.foo which won't typecheck. + val specializedInterfaceSym = qual1.tpe.widenDealias.classSymbol.asClass + val newSym = inContext(preErasureCtx) { sym.overridingSymbol(specializedInterfaceSym) } + qual1.select(newSym) case qual1 if !isJvmAccessible(qual1.tpe.typeSymbol) || !qual1.tpe.derivesFrom(sym.owner) => val castTarget = // Avoid inaccessible cast targets, see i8661 @@ -789,6 +807,35 @@ object Erasure { } } + /* Erase anonymous instances of specialized traits to $impl$ classes */ + override def typedBlock(tree: untpd.Block, pt: Type)(using Context): Tree = tree.asInstanceOf[Block] match + case AnonymousClassInstance(anon) => + inContext(preErasureCtx) { + Specialization.unapply(anon.typeTree.tpe, anon.typeTree.span).flatMap(spec => { + anon.parentCalls match { + case (obj :: parentsOfSpecTrait) if (spec.isSpecialized || spec.isFullySpecializedToTopClassesOrNothing) && (obj.symbol.owner == ctx.definitions.ObjectClass) && (parentsOfSpecTrait.forall(x => spec.symbol.asClass.baseClasses.exists(p => p == x.symbol.owner))) => + val app: Tree = parentsOfSpecTrait.find(p => p.symbol.owner == spec.symbol).get + assert(app.isInstanceOf[Apply]) // At the very least we pass the Specialized instance. + val targetImplName = spec.newImplementationClassName + val implClass = spec.symbol.enclosingPackageClass.info.decls.lookup(targetImplName) + assert(implClass.exists && implClass.isClass) + val erased = inContext(ctx.withSource(anon.typeTree.source)) { + Typed( + Select(New(ref(implClass)), anon.ctor) + .appliedToTypeTrees(spec.unspecializedTypeArgs.map(tpd.TypeTree(_))) + .appliedToArgss(tpd.allArgss(app).tail.nestedMap(_.changeNonLocalOwners(anon.symbol.owner))) // Skip the type params which are not needed + , anon.typeTree) + }.withSpan(anon.typeTree.span) + Some(erased) + case _ => None + }}) + } match { + case Some(erased) => typedTyped(erased, anon.typeTree.tpe) + case None => super.typedBlock(tree, pt) + } + case _ => super.typedBlock(tree, pt) + + override def typedBind(tree: untpd.Bind, pt: Type)(using Context): Bind = atPhase(erasurePhase): checkBind(promote(tree)) @@ -1021,9 +1068,49 @@ object Erasure { override def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(using Context): Tree = EmptyTree + + def typedSpecializedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(using Context): TypeDef = { + // drop Foo[Int] leading to duplicate Foo$sp$Int + val TypeDef(_, implInit: Template) = cdef: @unchecked + + // Match corresponding class info erasure in TypeErasure::apply ClassInfo case + val oldParents = implInit.asInstanceOf[Template].parents + val superCtxNoSpec = disallowSpecializedCtx(using ctx.superCallContext) + val newParents = + // {source: Bar, Foo both specialized traits} inline trait Bar$sp$Int extends Object, Bar, Foo$sp$Int + if cls.isSpecializedTraitInterface then + val (obj :: originalTrait :: inheritedParents) = oldParents : @unchecked + obj :: typedType(originalTrait)(using superCtxNoSpec) :: inheritedParents + // {source: Bar, Foo both specialized traits} class Bar$impl$Int extends Object, Bar$sp$Int, Bar(10) + else if cls.isSpecializedTraitImplementationClass && !cls.isRawSpecializedTraitImplementationClass then + val (objectParent :: traitSpParent :: originalTraitSpecializedParent :: Nil) = oldParents : @unchecked + val newParent = originalTraitSpecializedParent match { + case _: untpd.Apply => typedExpr(originalTraitSpecializedParent)(using superCtxNoSpec) + case _ => typedType(originalTraitSpecializedParent)(using superCtxNoSpec) + } + objectParent :: traitSpParent :: newParent :: Nil + else + inContext(preErasureCtx) { + val extraSpTraits = oldParents + .filter(p => p.symbol.isPrimaryConstructor && p.symbol.owner.isSpecializedTrait) + .map(p => p.tpe.resultType) + + def isPrimaryConsOfSpecTrait(tree: tpd.Tree): Boolean = + tree.symbol.isPrimaryConstructor && tree.symbol.owner.isSpecializedTrait + + // {source: class Bar extends Foo[Int](10) with Baz[Int](10)} + // class Bar extends Object, Foo(10), Bar(10), Foo$sp$Int, Bar$sp$Int + oldParents + .map(tp => if isPrimaryConsOfSpecTrait(tp) then typedExpr(tp)(using superCtxNoSpec) else tp) + ::: extraSpTraits.map(sym => TypeTree(sym)) + } + + cpy.TypeDef(cdef.asInstanceOf[TypeDef])(rhs = cpy.Template(implInit.asInstanceOf[Template])(parents = newParents)) + } override def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(using Context): Tree = - val typedTree@TypeDef(name, impl @ Template(constr, _, self, _)) = super.typedClassDef(cdef, cls): @unchecked + val cdef1 = typedSpecializedClassDef(cdef, cls) + val typedTree@TypeDef(name, impl @ Template(constr, _, self, _)) = super.typedClassDef(cdef1, cls): @unchecked // In the case where a trait extends a class, we need to strip any non trait class from the signature // and accept the first one (see tests/run/mixins.scala) val newTraits = impl.parents.tail.filterConserve: tree => diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 81cc73c26ed6..0e0d211faf35 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -22,6 +22,7 @@ import inlines.Inlines.inInlineMethod import util.Property import inlines.Inlines import reporting.InlinedAnonClassWarning +import dotty.tools.dotc.transform.Specialization.anonymousClassIsSpecialized object FirstTransform { val name: String = "firstTransform" @@ -210,7 +211,9 @@ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => } override def transformTypeDef(tree: TypeDef)(using Context): Tree = - if tree.symbol.isAnonymousClass && Inlines.inInlineMethod then + if tree.symbol.isAnonymousClass && Inlines.inInlineMethod && + !anonymousClassIsSpecialized(tree) + then report.warning(InlinedAnonClassWarning(), tree.symbol.sourcePos) tree diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index bf2c28b64936..0d10d1ce6ef3 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -80,7 +80,7 @@ class Inlining extends MacroTransform, IdentityDenotTransformer { new TreeTraverser { def traverse(tree: Tree)(using Context): Unit = tree match - case tree: RefTree if !Inlines.inInlineMethod && StagingLevel.level == 0 => + case tree: RefTree if !Inlines.inInlineContext && StagingLevel.level == 0 => assert(!tree.symbol.isInlineMethod, tree.show) case _ => traverseChildren(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index b514c2100611..2fad258a6e36 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -94,7 +94,7 @@ object Mixin { * def x_=(y: T) = () * * 4.5 (done in `mixinForwarders`) For every method - * ` def f[Ts](ps1)...(psN): U` in M` that needs to be disambiguated: + * ` def f[Ts](ps1)...(psN): U` in M that needs to be disambiguated: * * def f[Ts](ps1)...(psN): U = super[M].f[Ts](ps1)...(psN) * @@ -191,6 +191,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => sym.isGetter && !wasOneOf(sym, DeferredOrLazy | ParamAccessor) && atPhase(thisPhase) { !sym.setter.exists } && !sym.isConstExprFinalVal + && !sym.owner.isInlineTrait private def makeTraitSetter(getter: TermSymbol)(using Context): Symbol = getter.copy( @@ -286,9 +287,9 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => val rhs = if (wasOneOf(getter, ParamAccessor)) nextArgument() - else if (getter.is(Lazy, butNot = Module)) + else if (!mixin.isInlineTrait && getter.is(Lazy, butNot = Module)) transformFollowing(superRef(getter).appliedToNone) - else if (getter.is(Module)) + else if (!mixin.isInlineTrait && getter.is(Module)) if ctx.settings.scalajs.value && getter.moduleClass.isJSType then if getter.is(Scala2x) then report.error( @@ -300,24 +301,27 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => New(getter.info.resultType, List(This(cls))) else Underscore(getter.info.resultType) - // transformFollowing call is needed to make memoize & lazy vals run - val forwarder = mkForwarderSym(getter.asTerm) - // Store the unerased form for generic signature use later, - // but only if it's not private (which we must check at erasure time, as here we've removed that flag already), - // since otherwise it might refer to private classes - if atPhase(erasurePhase) { !getter.is(Private) } then - mixinGenericInfos(forwarder) = atPhase(erasurePhase) { cls.thisType.memberInfo(getter) } - transformFollowing(DefDef(forwarder, rhs)) + if (!mixin.isInlineTrait) then + // transformFollowing call is needed to make memoize & lazy vals run + val forwarder = mkForwarderSym(getter.asTerm) + // Store the unerased form for generic signature use later, + // but only if it's not private (which we must check at erasure time, as here we've removed that flag already), + // since otherwise it might refer to private classes + if atPhase(erasurePhase) { !getter.is(Private) } then + mixinGenericInfos(forwarder) = atPhase(erasurePhase) { cls.thisType.memberInfo(getter) } + + transformFollowing(DefDef(forwarder, rhs)) + else + EmptyTree } else if wasOneOf(getter, ParamAccessor) then - // mixin parameter field is defined by an override; evaluate the argument and throw it away - nextArgument() + if (mixin.isInlineTrait) then {nextArgument(); EmptyTree} else nextArgument() else EmptyTree } def setters(mixin: ClassSymbol): List[Tree] = val mixinSetters = mixin.info.decls.filter { sym => - sym.isSetter && (!wasOneOf(sym, Deferred) || sym.name.is(TraitSetterName)) + sym.isSetter && (!wasOneOf(sym, Deferred) || sym.name.is(TraitSetterName)) && !sym.owner.isInlineTrait } mixinSetters.map(setter => { val copied = transformFollowing(DefDef(mkForwarderSym(setter.asTerm), unitLiteral.withSpan(cls.span))) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index e1906ddf6763..237b55b68607 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -27,6 +27,7 @@ import dotty.tools.dotc.transform.MacroAnnotations.hasMacroAnnotation import dotty.tools.dotc.core.NameKinds.DefaultGetterName import ast.TreeInfo import dotty.tools.dotc.cc.derivedFunctionOrMethod +import dotty.tools.dotc.core.NameKinds.ContextBoundParamName object PostTyper { val name: String = "posttyper" @@ -264,6 +265,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => tree match case tree: ValOrDefDef if !sym.is(Synthetic) => checkInferredWellFormed(tree.tpt) + if tree.symbol.owner.isInlineTrait then checkInlTraitPrivateMemberIsLocal(tree) if sym.is(Method) then if sym.isSetter then sym.keepAnnotationsCarrying(thisPhase, Set(defn.SetterMetaAnnot)) @@ -310,6 +312,9 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => => Checking.checkAppliedTypesIn(tree) case _ => + private def checkInlTraitPrivateMemberIsLocal(tree: Tree)(using Context): Unit = + if tree.symbol.owner.isInlineTrait && tree.symbol.isAllOf(Private, butNot = Local) then + report.error(em"implementation restriction: inline traits cannot have non-local private members. This also means no retained inline methods.", tree.srcPos) private def transformSelect(tree: Select, targs: List[Tree])(using Context): Tree = { val qual = tree.qualifier @@ -573,6 +578,8 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => if tree.isType then checkNotPackage(tree) else + if tree.symbol == defn.SpecializedModule && (ctx.owner ne defn.SpecializedModule.moduleClass) then + report.error(IllegalUseOfSpecialized(), tree.srcPos) registerNeedsInlining(tree) val tree1 = checkUsableAsValue(tree) tree1.tpe match { @@ -675,6 +682,10 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => val tree1 = cpy.DefDef(tree)(tpt = explicifyTpt(tree)) processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef])) case tree: TypeDef => + if tree.symbol.isInlineTrait then + ctx.compilationUnit.needsInlining = true // Check and transform inline traits + if tree.rhs.tpe.existsPart(t => t.typeSymbol == defn.SpecializedClass.asType) && (tree.symbol ne defn.SpecializedClass) then + report.error(IllegalUseOfSpecialized(), tree.srcPos) registerIfHasMacroAnnotations(tree) val sym = tree.symbol if (sym.isClass) @@ -686,6 +697,8 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => tree.rhs match case impl: Template => for parent <- impl.parents do + if Inlines.symbolFromParent(parent).isInlineTrait then + ctx.compilationUnit.needsInlining = true Checking.checkTraitInheritance(parent.tpe.classSymbol, sym.asClass, parent.srcPos) // Constructor parameters are in scope when typing a parent. // While they can safely appear in a parent tree, to preserve @@ -738,6 +751,8 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => else if (tree.tpt.symbol == defn.orType) () // nothing to do else + if tree.tpt.symbol == defn.SpecializedClass && !(ctx.owner.name.is(ContextBoundParamName) || ctx.owner.ownersIterator.contains(defn.SpecializedModule_apply)) then + report.error(IllegalUseOfSpecialized(), tree.srcPos) Checking.checkAppliedType(tree) super.transform(tree) case SingletonTypeTree(ref) => diff --git a/compiler/src/dotty/tools/dotc/transform/PruneInlineTraits.scala b/compiler/src/dotty/tools/dotc/transform/PruneInlineTraits.scala new file mode 100644 index 000000000000..87d0997a6f30 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PruneInlineTraits.scala @@ -0,0 +1,65 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts._ +import DenotTransformers.SymTransformer +import Flags._ +import SymDenotations._ +import Symbols._ +import MegaPhase.MiniPhase +import ast.tpd +import dotty.tools.dotc.core.StdNames.str + +class PruneInlineTraits extends MiniPhase with SymTransformer { thisTransform => + import tpd._ + import PruneInlineTraits._ + + override def phaseName: String = PruneInlineTraits.name + + override def description: String = PruneInlineTraits.description + + override def runsAfter: Set[String] = Set(PruneInlinedMethods.name) + + override def transformSym(sym: SymDenotation)(using Context): SymDenotation = + if isEraseable(sym) then sym.copySymDenotation(initFlags = sym.flags | Deferred) + else if sym.isInlineTrait then + val clsInfo = sym.asClass.classInfo + val clsInfo2 = clsInfo.derivedClassInfo(decls = + clsInfo.decls.filteredScope(!isDeletable(_)) + ) + sym.copySymDenotation(initFlags = sym.flags | PureInterface | NoInits, info = clsInfo2) + else sym + + override def transformTemplate(tree: Template)(using Context): Tree = + cpy.Template(tree)(body = tree.body.flatMap({ + case stmt: ValDef if isEraseable(stmt.symbol) => Some(cpy.ValDef(stmt)(rhs = EmptyTree)) + case stmt: DefDef if isEraseable(stmt.symbol) => Some(cpy.DefDef(stmt)(rhs = EmptyTree)) + case stmt: (ValDef | DefDef) if isDeletable(stmt.symbol) => None + case stmt => Some(stmt) + })) + + private def isEraseable(sym: SymDenotation)(using Context): Boolean = + !sym.isType + && !sym.isConstructor + && !sym.is(Param) + && !sym.is(ParamAccessor) + && !sym.is(Local) + && !sym.isLocalDummy + && sym.exists + && sym.owner.isInlineTrait + + private def isDeletable(sym: SymDenotation)(using Context): Boolean = + !sym.isType + && sym.owner.isInlineTrait + && (sym.is(Local) || (sym.is(Inline) && !sym.isRetainedInline)) // isRetainedInline not an issue while we block non-local privates as retainedBody is non local private. + && !sym.is(Param) + && !sym.is(ParamAccessor) +} + +object PruneInlineTraits { + import tpd._ + + val name: String = "pruneInlineTraits" + val description: String = "drop rhs definitions in inline traits" +} diff --git a/compiler/src/dotty/tools/dotc/transform/PruneInlinedMethods.scala b/compiler/src/dotty/tools/dotc/transform/PruneInlinedMethods.scala new file mode 100644 index 000000000000..e3acb7fe27bd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PruneInlinedMethods.scala @@ -0,0 +1,46 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts._ +import DenotTransformers.InfoTransformer +import Flags._ +import SymDenotations._ +import Symbols._ +import MegaPhase.MiniPhase +import ast.tpd +import dotty.tools.dotc.core.StdNames.str +import dotty.tools.dotc.core.Types._ + +class PruneInlinedMethods extends MiniPhase with InfoTransformer { thisTransform => + import tpd._ + import PruneInlinedMethods._ + + override def phaseName: String = PruneInlinedMethods.name + + override def description: String = PruneInlinedMethods.description + + override def transformInfo(tp: Type, sym: Symbol)(using Context) = tp match { + case clsInfo: ClassInfo if sym.isClass && !sym.is(Package) && !sym.is(JavaDefined) => + clsInfo.derivedClassInfo(decls = + clsInfo.decls.filteredScope(!isDeletable(_)) + ) + case _ => tp + } + + override def transformTemplate(tree: Template)(using Context): Tree = + cpy.Template(tree)(body = tree.body.flatMap({ + case stmt: DefDef if isDeletable(stmt.symbol) => None + case stmt => Some(stmt) + })) + + private def isDeletable(sym: Symbol)(using Context): Boolean = + Specialization.isSpecializedMethod(sym) +} + +object PruneInlinedMethods { + import tpd._ + + val name: String = "pruneInlinedMethods" + val description: String = "drop methods which have already been inlined" // We can't wait until erasure because they can be broken by pruneInlineTraits removing members from the specialized traits they instantiate +} diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeInlineTraits.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeInlineTraits.scala new file mode 100644 index 000000000000..99664e041ca3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeInlineTraits.scala @@ -0,0 +1,109 @@ +package dotty.tools.dotc +package transform + + +import core._ +import Flags._ +import Contexts._ +import Symbols._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.quoted._ +import dotty.tools.dotc.inlines.Inlines +import dotty.tools.dotc.ast.TreeMapWithImplicits +import dotty.tools.dotc.core.DenotTransformers.SymTransformer +import dotty.tools.dotc.staging.StagingLevel +import dotty.tools.dotc.core.SymDenotations.SymDenotation +import dotty.tools.dotc.core.StdNames.{str, nme} +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.core.Names.{Name, TermName} + +import scala.collection.mutable +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.inlines.Inlines.InlineTraitState +import dotty.tools.dotc.ast.TreeTypeMap + +import scala.util.chaining.* + +class SpecializeInlineTraits extends MiniPhase { + + import tpd._ + + override def phaseName: String = SpecializeInlineTraits.name + + override def description: String = SpecializeInlineTraits.description + + override def changesMembers: Boolean = true + + override def changesParents: Boolean = true + + private def inlineTraitCtx(using Context): Context = ctx.fresh.setInlineTraitState(ctx.inlineTraitState.copyInPhase(InlineTraitState.InlineContext.InlineTraits)) + + private val seen = mutable.HashSet[Symbol]() // Ensure we don't inline into the same child class multiple times even though we repeatedly check for nested inlines. + + private def inlineInlineTraitsIfNew(tree: Tree)(using Context) = + if !seen.contains(tree.symbol) then + seen.add(tree.symbol) + Inlines.inlineParentInlineTraits(tree)(using inlineTraitCtx) + else + tree + + private def shouldInline(tree: Tree)(using Context) = Inlines.needsInlining(tree)(using inlineTraitCtx) + + override def transformTypeDef(tree: TypeDef)(using Context): Tree = + // We need to inline recursively because inlining may create further opportunities for inlining. + // Notably this does limit the composition potential of this miniphase. + new TreeMapWithPreciseStatContexts { + override def transform(tree: Tree)(using Context): Tree = + tree match { + case tree: TypeDef if tree.symbol.isInlineTrait => + Inlines.checkAndTransformInlineTrait(tree) + .pipe(tree => if shouldInline(tree) then inlineInlineTraitsIfNew(tree) else tree) + // We may need to inline inline traits into the bodies of methods defined inside inline traits. + .pipe(super.transform) + case tree: TypeDef if shouldInline(tree) => + if tree.symbol.isAllOf(Trait, butNot = Inline) then + def constructorHasTermParam(sym: ClassSymbol): Boolean = + sym.primaryConstructor.paramSymss + .exists(paramList => paramList.nonEmpty && paramList.head.isTerm) + + val problemParents = tree.symbol.info.parents + .filter { parent => parent.classSymbol match + case NoSymbol => false + case classSymbol: ClassSymbol => classSymbol.isInlineTrait && constructorHasTermParam(classSymbol) + } + + problemParents.foreach { parent => + val message = s""" + Only parameterless inline traits may be extended by ordinary traits. + Make ${tree.symbol} inline or remove inline ${parent.typeSymbol}'s parameter list. + """ + + report.error(message, tree.srcPos) + } + + inlineInlineTraitsIfNew(tree).pipe(super.transform) + case t => super.transform(t) + } + }.transform(tree) + + override def checkPostCondition(tree: Tree)(using Context): Unit = + tree match { + // TODO: check that things are inlined properly + case _ => + } + + private object ConcreteParentStripper extends TreeAccumulator[Tree] { + def apply(tree: Tree)(using Context): Tree = apply(tree, tree) + + override def apply(x: Tree, tree: Tree)(using Context): Tree = tree match { + case ident: Ident => ident + case tpt: TypeTree => tpt + case _ => foldOver(x, tree) + } + } +} + +object SpecializeInlineTraits: + val name: String = "specializeInlineTraits" + val description: String = "inline the code of inline traits" diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala index 0077cb969e3a..1cf292194743 100644 --- a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -156,6 +156,7 @@ class SuperAccessors(thisPhase: DenotTransformer) { val needAccessor = name.isTermName // Types don't need super accessors && !sym.isInlineMethod // Inline methods are not called at runtime so they don't need superaccessors. + && !clazz.isInlineTrait // Inline traits deal with super calls in their own way && (clazz != currentClass || !validCurrentClass || mix.name.isEmpty && clazz.is(Trait)) if (needAccessor) atPhase(thisPhase.next)(superAccessorCall(sel, mix.name)) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 58911f58772e..4a0e0b710500 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -72,7 +72,11 @@ class TreeChecker extends Phase with SymTransformer { sym.isRefinementClass assert(validSuperclass, i"$sym has no superclass set") - testDuplicate(sym, seenClasses, "class") + + // Multiple references to specialized traits will specialize multiple times, but they lead to the same + // interface and implementation classes every time, so we can allow duplicates and pick one arbitrarily. + if !(sym.isSpecializedTraitInterface || sym.isSpecializedTraitImplementationClass) then + testDuplicate(sym, seenClasses, "class") } val badDeferredAndPrivate = @@ -636,7 +640,7 @@ object TreeChecker { def isNonMagicalMember(x: Symbol) = !x.isValueClassConvertMethod && !x.name.is(DocArtifactName) - + val decls = cls.classInfo.decls.toList.toSet.filter(isNonMagicalMember) val defined = impl.body.map(_.symbol) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index a8a855b6ae5b..12e93f7e1f3b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -635,8 +635,9 @@ object Semantic: reporter.report(error) Hot else - val error = AccessNonInit(target)(trace) - reporter.report(error) + if !receiver.classSymbol.isInlineTrait then // See tests/pos/inline-trait-y-equals-x-inlined-nowarn.scala + val error = AccessNonInit(target)(trace) + reporter.report(error) Hot else report.warning("[Internal error] Unexpected resolution failure: ref.klass = " + ref.klass.show + ", field = " + field.show + Trace.show, Trace.position) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index b1ede8f90c70..d2494c8a74c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -690,7 +690,9 @@ object Checking { if (sym.isConstructor && !sym.isPrimaryConstructor && sym.owner.is(Trait, butNot = JavaDefined)) val addendum = if ctx.settings.Ydebug.value then s" ${sym.owner.flagsString}" else "" fail(em"Traits cannot have secondary constructors$addendum") - checkApplicable(Inline, sym.isTerm && !sym.is(Module) && !sym.isMutableVarOrAccessor) + if (!Feature.inlineTraitsEnabledSomewhere && sym.isAllOf(Inline | Trait)) + fail(em"Inline traits are experimental and must be enabled") + checkApplicable(Inline, sym.isTerm && !sym.is(Module) && !sym.isMutableVarOrAccessor || sym.is(Trait)) checkApplicable(Lazy, !sym.isOneOf(Method | Mutable)) if (sym.isType && !sym.isOneOf(Deferred | JavaDefined)) for (cls <- sym.allOverriddenSymbols.filter(_.isClass)) { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index cd19e2cfdb83..cc7b0dbced8c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1023,7 +1023,7 @@ class Namer { typer: Typer => val sym = denot.symbol def register(child: Symbol, parentCls: ClassSymbol) = { - if (parentCls.is(Sealed)) + if (parentCls.is(Sealed) && !(child.isAnonymousClass && parentCls.isSpecializedTrait)) if ((child.isInaccessibleChildOf(parentCls) || child.isAnonymousClass) && !sym.hasAnonymousChild) addChild(parentCls, parentCls) else if (!parentCls.is(ChildrenQueried)) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 2940135f53ed..29ffd210d1e1 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -417,6 +417,8 @@ object RefChecks { * of class `clazz` are met. */ def checkOverride(member: Symbol, other: Symbol): Unit = + def isInlinedFromInlineTrait = other.owner.isAllOf(InlineTrait) && member.is(Synthetic) + def memberType(self: Type) = if (member.isClass) TypeAlias(member.typeRef.etaExpand) else self.memberInfo(member) @@ -507,12 +509,13 @@ object RefChecks { def overrideTargetNameError() = val otherTargetName = i"@targetName(${other.targetName})" - if member.hasTargetName(member.name) then - overrideError(i"misses a target name annotation $otherTargetName") - else if other.hasTargetName(other.name) then - overrideError(i"should not have a @targetName annotation since the overridden member hasn't one either") - else - overrideError(i"has a different target name annotation; it should be $otherTargetName") + if !isInlinedFromInlineTrait then + if member.hasTargetName(member.name) then + overrideError(i"misses a target name annotation $otherTargetName") + else if other.hasTargetName(other.name) then + overrideError(i"should not have a @targetName annotation since the overridden member hasn't one either") + else + overrideError(i"has a different target name annotation; it should be $otherTargetName") //Console.println(infoString(member) + " overrides " + infoString(other) + " in " + clazz);//DEBUG @@ -548,13 +551,13 @@ object RefChecks { // direct overrides were already checked on completion (see Checking.chckWellFormed) // the test here catches indirect overriddes between two inherited base types. overrideError("cannot be used here - class definitions cannot be overridden") - else if (other.isOpaqueAlias) + else if (other.isOpaqueAlias && !isInlinedFromInlineTrait) // direct overrides were already checked on completion (see Checking.chckWellFormed) // the test here catches indirect overriddes between two inherited base types. overrideError("cannot be used here - opaque type aliases cannot be overridden") else if (!other.is(Deferred) && member.isClass) overrideError("cannot be used here - classes can only override abstract types") - else if other.isEffectivelyFinal then // (1.2) + else if (other.isEffectivelyFinal && !isInlinedFromInlineTrait) then // (1.2) overrideError(i"cannot override final member ${other.showLocated}") else if (member.is(ExtensionMethod) && !other.is(ExtensionMethod)) // (1.3) overrideError("is an extension method, cannot override a normal method") @@ -597,7 +600,7 @@ object RefChecks { overrideError("needs `override` modifier") else if (other.is(AbsOverride) && other.isIncompleteIn(clazz) && !member.is(AbsOverride)) overrideError("needs `abstract override` modifiers") - else if isMarkedOverride(member) && other.isMutableVarOrAccessor then + else if isMarkedOverride(member) && other.isMutableVarOrAccessor && !isInlinedFromInlineTrait then overrideError("cannot override a mutable variable") else if isMarkedOverride(member) && !(member.owner.thisType.baseClasses.exists(_.isSubClass(other.owner))) @@ -649,6 +652,7 @@ object RefChecks { overrideError("cannot have a @targetName annotation since external names would be different") else if other.is(ParamAccessor) && !isInheritedAccessor(member, other) && !member.is(Tracked) // see remark on tracked members above + && !other.owner.isInlineTrait // Allow inline traits to override val params because we prune the params from the parent traits later so they need to live in the children. then // (1.12) report.errorOrMigrationWarning( em"cannot override val parameter ${other.showLocated}", diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index ea6d97ec4321..a3f89506ff82 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -80,7 +80,11 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): if defn.SpecialClassTagClasses.contains(sym) then classTagModul.select(sym.name.toTermName).withSpan(span) else - val ctype = escapeJavaArray(erasure(tp.normalizedTupleType)) + val normalTupleType = tp.normalizedTupleType + val specializedType = + if tp.typeSymbol.denot.isSpecializedTrait then normalTupleType + else erasure(normalTupleType) + val ctype = escapeJavaArray(specializedType) if ctype.exists then classTagModul.select(nme.apply) .appliedToType(tp) @@ -681,6 +685,16 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val synthesizedMirror: SpecialHandler = (formal, span) => orElse(synthesizedProductMirror(formal, span), synthesizedSumMirror(formal, span)) + val synthesizedSpecialized: SpecialHandler = (formal, span) => formal match { + case AppliedType(tycon, arg :: Nil) if tycon =:= defn.SpecializedClass.typeRef => + withNoErrors(TypeApply(ref(defn.SpecializedModule_apply), TypeTree(arg) :: Nil)) + case _ => EmptyTreeNoError + } + + private def escapeJavaArray(tp: Type)(using Context): Type = tp match + case JavaArrayType(elemTp) => defn.ArrayOf(escapeJavaArray(elemTp)) + case _ => tp + private enum ManifestKind: case Full, Opt, Clss @@ -804,6 +818,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): defn.OptManifestClass -> synthesizedOptManifest, defn.SingletonClass -> synthesizedSingleton, defn.PreciseClass -> synthesizedPrecise, + defn.SpecializedClass -> synthesizedSpecialized ) def tryAll(formal: Type, span: Span)(using Context): TreeWithErrors = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b6c4a972e276..d78ed8cf21be 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3532,6 +3532,29 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer addAccessorDefs(cls, typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1))) + if !ctx.isAfterTyper && cls.isInlineTrait then + body1.map(_.symbol).filter(_.isInlineTrait).foreach(innerInlTrait => + report.error( + em"Implementation restriction: an inline trait cannot be defined inside of another inline trait", + innerInlTrait.srcPos + ) + ) + val membersToInline = body1.filter(member => Inlines.isInlineableFromInlineTrait(cls, member)) + membersToInline.foreach { + case tdef: TypeDef if tdef.symbol.isClass => + def rec(paramss: List[List[Symbol]]): Unit = paramss match { + case (param :: _) :: _ if param.isTerm => + report.error(em"Implementation restriction: inner classes inside inline traits cannot have term parameters", param.srcPos) + case _ :: paramss => + rec(paramss) + case _ => + } + rec(tdef.symbol.primaryConstructor.paramSymss) + case _ => + } + val wrappedMembersToInline = Block(membersToInline, unitLiteral).withSpan(cdef.span) + PrepareInlineable.registerInlineInfo(cls, wrappedMembersToInline) + checkNoDoubleDeclaration(cls) val impl1 = cpy.Template(impl)(constr1, parents1, Nil, self1, body1) .withType(dummy.termRef) @@ -4934,13 +4957,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tree } else TypeComparer.testSubType(tree.tpe.widenExpr, pt) match - case CompareResult.Fail(_) => + case CompareResult.Fail(notes) => wtp match case wtp: MethodType => missingArgs(wtp) case _ => typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt") //typr.println(TypeComparer.explained(tree.tpe <:< pt)) - adaptToSubType(wtp) + adaptToSubType(wtp, notes) case CompareResult.OKwithGADTUsed if pt.isValueType && !inContext(ctx.fresh.setGadtState(GadtState(GadtConstraint.empty))) { @@ -5041,7 +5064,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree: Closure => cpy.Closure(tree)(tpt = TypeTree(samParent)).withType(samParent) } - def adaptToSubType(wtp: Type): Tree = + def adaptToSubType(wtp: Type, cmpNotes: List[Message.Note] = Nil): Tree = // try converting a constant to the target type tree.tpe.widenTermRefExpr.normalized match case ConstantType(x) => @@ -5110,7 +5133,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else val tree1 = healAdapt(tree, pt) if tree1 ne tree then readapt(tree1) - else err.typeMismatch(tree, pt, failure.notes) + else err.typeMismatch(tree, pt, cmpNotes ++ failure.notes) pt match case _: SelectionProto => diff --git a/docs/_docs/reference/error-codes/E233.md b/docs/_docs/reference/error-codes/E233.md new file mode 100644 index 000000000000..7bea8cc2fc45 --- /dev/null +++ b/docs/_docs/reference/error-codes/E233.md @@ -0,0 +1,230 @@ +--- +title: E233: Illegal Use of Specialized Error +kind: Error +since: 3.10.0 +--- +# E233: Illegal Use of Specialized Error + +This error occurs when Specialized is used outside of a context bound. + +--- + +## Example + +```scala sc:fail sc-opts:-experimental,-language:experimental.inlineTraits +def bar(x: Specialized[Int]): Int = ??? // error: Specialized may only be used as a context bound +class Foo(x: Specialized[Int]) // error: Specialized may only be used as a context bound +class Bar(val x: Specialized[Int]) // error: Specialized may only be used as a context bound +val y: Specialized[Char] = ??? // error: Specialized may only be used as a context bound +val v = Specialized.apply[Int] // error: Specialized may only be used as a context bound +def z = println(Specialized.apply[Float]) // error: Specialized may only be used as a context bound +def a = Specialized.apply // error: Specialized may only be used as a context bound +type V = Specialized // error: Specialized may only be used as a context bound +type W = Specialized[Int] // error: Specialized may only be used as a context bound +val b = Specialized // error: Specialized may only be used as a context bound +``` + +### Error + +```scala sc:nocompile +-- [E233] Syntax Error: example.scala:2:13 ------------------------------------- +2 |class Foo(x: Specialized[Int]) // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E233] Syntax Error: example.scala:3:17 ------------------------------------- +3 |class Bar(val x: Specialized[Int]) // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E233] Syntax Error: example.scala:1:11 ------------------------------------- +1 |def bar(x: Specialized[Int]): Int = ??? // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E233] Syntax Error: example.scala:4:7 -------------------------------------- +4 |val y: Specialized[Char] = ??? // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E233] Syntax Error: example.scala:5:8 -------------------------------------- +5 |val v = Specialized.apply[Int] // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E233] Syntax Error: example.scala:6:16 ------------------------------------- +6 |def z = println(Specialized.apply[Float]) // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E233] Syntax Error: example.scala:7:8 -------------------------------------- +7 |def a = Specialized.apply // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^ + | Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E233] Syntax Error: example.scala:8:5 -------------------------------------- +8 |type V = Specialized // error: Specialized may only be used as a context bound + |^^^^^^^^^^^^^^^^^^^^ + |Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E233] Syntax Error: example.scala:9:5 -------------------------------------- +9 |type W = Specialized[Int] // error: Specialized may only be used as a context bound + |^^^^^^^^^^^^^^^^^^^^^^^^^ + |Specialized may only be used as a context bound + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ----------------------------------------------------------------------------- +-- [E233] Syntax Error: example.scala:10:8 ------------------------------------- +10 |val b = Specialized // error: Specialized may only be used as a context bound + | ^^^^^^^^^^^ + | Specialized may only be used as a context bound + |---------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized allows for a performance improvement by specializing generic type parameters + | to avoid boxing/unboxing. It should only be used as a context (typeclass) bound on a + | generic type in inline traits or inline methods: + | + | inline trait Vec[T: Specialized](val x: T) + | + | inline def foo[T: Specialized](v: Vec[T]) = v.x + | + | In this instance it was used in a way which is unsupported, such as + | trying to create a type synonym or a value with explicit type Specialized[X]. + | + ---------------------------------------------------------------------------- +``` + +### Solution + +Use Specialized correctly, i.e. as a context bound in an inline trait or method: + +```scala sc:compile sc-opts:-experimental,sc-opts:-experimental,-language:experimental.inlineTraits +inline trait Vec[T: Specialized](val x: T) + +inline def foo[T: Specialized](v: Vec[T]) = v.x +``` diff --git a/docs/_docs/reference/error-codes/E234.md b/docs/_docs/reference/error-codes/E234.md new file mode 100644 index 000000000000..a226559f65b6 --- /dev/null +++ b/docs/_docs/reference/error-codes/E234.md @@ -0,0 +1,97 @@ +--- +title: E234: Variance in Specialized Traits Limitation +kind: Warning +since: 3.10.0 +--- +# E234: Variance in Specialized Traits Limitation + +Emitted when a type parameter of an inline trait is defined both Specialized and contravariant, +to remind the user that this comes with a reduction in expressivity. + +Ordinary contravariance works because of erasure, but specialized traits have a special erasure which prevents +some contravariance patterns involving Object, Any, AnyVal and AnyRef. + +The following example illustrates the problem: + +```scala sc:fail sc-opts:-experimental,-language:experimental.specializedTraits +inline trait RecyclingBin[-T: Specialized]: + def recycle(x: T) = println(s"Recycling ${x}") + +def recycleAnInteger(bin: RecyclingBin[Int]) = + bin.recycle(100) + +recycleAnInteger(new RecyclingBin[AnyVal]() {}) // RecyclingBin[AnyVal] can be interpreted as RecyclingBin[Int] due to contravariance +recycleAnInteger(new RecyclingBin[Any]() {}) // RecyclingBin[Any] can be interpreted as RecyclingBin[Int] due to contravariance + +// Yet, this erases to: + +def recycleAnInteger(bin: RecyclingBin$sp$Int) = + bin.recycle(100) + +recycleAnInteger(new RecyclingBin() {}.asInstanceOf[RecyclingBin$sp$Int]) // RecyclingBin cannot be cast to RecyclingBin$sp$Int +recycleAnInteger(new RecyclingBin() {}.asInstanceOf[RecyclingBin$sp$Int]) // RecyclingBin cannot be cast to RecyclingBin$sp$Int + +``` + +- `RecyclingBin[Any]`, `RecyclingBin[AnyVal]` may not be passed to `RecyclingBin[Int]` whereas normally they would be able to be passed +- `RecyclingBin[Any]`, `RecyclingBin[Object / AnyRef]` may not be passed to RecyclingBin[Paper] whereas normally they would. + +So we impose an additional restriction on contravariance with specialized parameters: +- If `A[F1]` is to be interpreted as `A[F2]` under `A[-T: Specialized]`,we require that `SpecType(F1) = SpecType(F2)`. Given that we also require `F1 >:> F2`, and looking at the definition of SpecType this is roughly equivalent to saying F1 may not be any of the top classes `Any`, `AnyVal`, `AnyRef` unless F2 is also. + +If this restriction is not satisfied, warning E233 is emitted, along with a type error. + +In order that the creator of the contravariant specialized trait is aware of this restriction, the warning E233 is emitted at the point of definition. + +Covariance has a similar problem: +- In general it works fine because it corresponds to interpreting `A[F1]` as `A[F2]` where `F1 <:< F2`. Either`A[F1]` and `A[F2]` both erase to the same type (`A$sp$SpecType(F2)` or `A` if `F1` and `F2` are both top classes), or `A[F1]` erases to `A$sp$F1` and `A[F2]` erases to `A`. But `A$sp$F1` is a subtype of `A` by definition so the upcast will succeed (and upcasts are generally cheap compared to downcasts on the JVM so this is acceptable from a performance perspective). +- The only exception is with `Nothing`, because we want to interpret `A[Nothing]` as e.g. `A[Int]`, but we erase `A[Nothing]` to `A`. `A >:> A$sp$Int` so this doesn't work and we also have to ban it. This makes the code less ergonomic in some cases. For example `case object Nil extends List[Nothing]` has to become `inline trait NilC[T: Specialized] extends List[T]` and `inline def Nil[T: Specialized] = new NilC[T] () {}`, but we don't lose too much expressivity. + +--- + +## Example + +```scala sc:fail sc-opts:-explain,-Werror,-experimental,-language:experimental.specializedTraits +inline trait Foo[-T: Specialized] // warning: Type parameter is both Specialized and contravariant. +``` + +### Error + +```scala sc:nocompile +-- [E234] Potential Issue Warning: example.scala:1:18 -------------------------- +1 |inline trait Foo[-T: Specialized] // warning: Type parameter is both Specialized and contravariant. + | ^ + |Type parameter is both Specialized and variant. This imposes additional typing restrictions. + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Specialized traits achieve a performance gain through a special erasure. + | - Primitives are specialized: Foo[Int] erases to Foo$sp$Int + | - Reference types are specialized to the highest non-top class: Foo[Lion] erases to Foo$sp$Animal + | - Top classes are erased normally: Foo[Any] / Foo[AnyVal] / Foo[Object] / Foo[AnyRef] erase to Foo. + | This means that certain variance patterns that cross these erasure categories will fail at + | runtime due to a ClassCastException, so they are not permitted. + | + | For example, treating Foo[Any] as Foo[Animal] via contravariance is not allowed with Specialized. + | + | Please see the docs for more information on how specialized traits are erased. + | + | If you accept this limitation you can silence this warning with @nowarn. For example: + | + | @nowarn("id=E234") + | inline trait Foo[-T: Specialized]: + | + | Otherwise, remove Specialized, or remove the variance. + | + ----------------------------------------------------------------------------- +``` + +### Solution + +Accept downsides and ignore warning as follows, or remove one of Specialized / variant. + +```scala sc:nocompile custom-sc:compile sc-opts:-experimental,-language:experimental.specializedTraits +import scala.annotation.nowarn +@nowarn("id=E234") +inline trait Foo[-T: Specialized] +``` diff --git a/docs/_docs/reference/experimental/inline-traits.md b/docs/_docs/reference/experimental/inline-traits.md new file mode 100644 index 000000000000..7ce982c7e830 --- /dev/null +++ b/docs/_docs/reference/experimental/inline-traits.md @@ -0,0 +1,392 @@ +# Inline Traits + +## Motivation +Inline traits is a new attempt to solve the specialization problem for the JVM in a more convenient way than the `@specialized` annotation +from Scala 2 (the main problem of this being code bloat due to generating all possible specializations at declaration time of the specialized +class). Inline traits work alongside `Specialized` traits, the latter being detailed in an accompanying document. + +The problem is as follows: due to the JVM's lack of support for generics, generic type parameters are erased by the compiler: +```scala +class A[T](val x: T) + +class C: + val w1 = A[Int](1) + val w2 = A[Int](2) + val w3 = A[Int](w1.x + w2.x) +``` +is converted to: +```scala +class A: + val x: Object +class C() extends Object() { + def w1(): A = new A(Int.box(1)) + def w2(): A = new A(Int.box(2)) + def w3(): A = new A(Int.box(Int.unbox(this.w1().x()).+(Int.unbox(this.w2().x())))) +} +``` +Thus type `T` is converted to `Object`, and so every use of `A[Int]()` must first build an `Object` containing the `Int` we want to pass (*boxing*), +in order to be able to call `A(x: Object)`. There is no `A(x: int)`. When referencing `x` on an `A` we get an `Object` back, which we have to *unbox* +(extract from the wrapping `Object`). + +This object creation and deletion is very slow. We desire a way to avoid this by generating specialized instances of classes which +use primitive types instead of `Object`. These can be used in situations where the (un)boxing overhead is likely to be high. + +## Solution +An inline trait is defined just like a normal trait, but with an `inline` modifier. + +Inline traits may be freely extended by objects, classes or other inline traits. An inline trait may also be extended by an ordinary trait, but only if the inline trait does not take parameters [***]. + +The following is an example of the use of an `inline trait`. + +```scala +inline trait A[T](val x: T): + def foo: T = x + +class B extends A[Int](1) +``` +Let the term *inline trait* refer to traits such as `A` above, and let *inline receiver* refer to class-likes +we inline into, such as `B` above. + +When an inline trait is inherited by an object, class or another inline trait, all its contents are inlined, and adapted +to the context of the inline receiver. In particular this means that: +- references to type parameters of the inline trait are specialized to the type arguments provided during extension +- `this` calls are updated to refer to the inline receiver. + +Inline traits are themselves translated to pure interfaces. However their bodies are of course retained in TASTy files; this +enables us to inline them into inline receivers that exist in different compilation units (for example when an inline recevier +in user code extends an inline trait from a library). + +The example above generates: +```scala +// Inline trait converted to pure interface +inline trait A[T](x: T): + val x: T + def foo: T + +// Inline receiver class now contains inlined body +// with references to T specialized to Int. +// this.x refers to B.x +class B extends A[Int](1): + override val x: Int = 1 + override def foo: Int = this.x +``` + +With multiple inline traits: + +```scala +inline trait A[T](val x: T): + def foo: T = x + +inline trait B extends A[Int] +class C extends B, A[Int](1) +``` + +```scala +inline trait A[T](x: T): + val x: T + def foo: T + +inline trait B extends A[Int]: + override def x: Int + override def foo: Int + +class C extends B, A[Int](1): + override def x = 1 + override def foo = x +``` +[!] This may be surprising compared to inline methods, as calls to inline methods from other inline methods are only inlined +when the outer inline method is inlined. While it is not immediately obvious why permitting inline traits to be inlined into other inline traits is useful (we could simply +inline everything into the first receiver which is not inline in the hierarchy), it becomes advantageous when we bring in the `Specialized` annotation; see the accompanying document. + + +Of course, patterns creating cycles of inlining are banned: + +```scala +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new D[S] {} + println("w") + +inline trait D[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} + println("w") + +val x = new D[Char]() {} +``` +Because of the pruning it's possible to define these circular inline traits. An error only occurs when they are inlined into `x` which creates a cycle. + +## Other Behaviours of Inline Traits +### Member reference replacement +```scala +inline trait A[T](val x: T): + def foo#1: T + +class B extends A[Int](1) + def foo#2: T = x // generated by inlining from foo#1 + +def fun(x: B) = + x.foo // points to foo#2 +``` +### Private members +Inline traits may define private members, and these are handled specially: +- Private fields in the inline trait are inlined as private fields with a mangled name in the inline receiver. This ensures they do not collide with privates inherited from other inline traits. +- The private fields are then no longer accessible in the inline trait, as it is transformed into a pure interface, so we delete them. + +```scala +inline trait A(b: Boolean): + private val x: Int = 1 + def foo(): Int = if b then x + 1 else 0 + +class B extends A(true) +``` +Is converted to: + +```scala +inline trait A(b: Boolean): + def foo(): Int + +class B extends A(true): + private val A$$b: Boolean = true + private val A$$x: Int = 1 + override def foo(): Int = if this.A$$b then this.A$$x.+(1) else 0 +``` + +### Mixing in of multiple inline traits & super calls +An inline receiver may mix in multiple inline traits with colliding member names. This follows the same rules as normal traits. In particular, this must usually be disambiguated with an override. +```scala +inline trait A: + def foo = "Hello World" + +inline trait B: + def foo = "Bonjour" + +class C extends A, B // error: C inherits conflicting members A.foo and B.foo +``` +A typical way to disambiguate would be using `super`. For example: +```scala +class C extends A, B: + override def foo = super[A].foo +``` +This syntax is supported for inline traits. Note however that as inline traits are converted to pure interfaces it is not possible to make a direct call to the +method on A or B. Furthermore if we allowed this, specialization would be lost. Therefore, overridden methods are inlined into the inline receiver with a mangled name, +e.g. `A$$foo`, `B$$foo` and the `override def foo` in `C` will delegate to one of these methods. +Super calls to non-overridden methods are also supported. +These are transformed to point directly to the corresponding inlined methods with no need for name mangling. + +However, inline traits may not contain `super` references to classes or non-inline traits. This is because `super` references in scala may only reference +direct parents, and, after inlining, those references that were to direct parents in the original inline traits would now have to point to ancestor classes which are further than 1 hop away. We cannot call into the inline trait and have the inline trait call the grandparent because the method in the inline trait will be abstract after pruning. + +### Interaction with other types of inline +- Inline traits may define inline members (e.g. `inline def`, `inline val`). References to these are inlined as the body of the trait is inlined into the inline receiver, but the members themselves are not inlined and are deleted from the parent trait. E.g.: + ```scala + inline trait A: + inline val x = 1 + + class B extends A: + def f = x + ``` + becomes: + ```scala + inline trait A + class B extends A: + def f = 1 + ``` +- As is usual, `inline val`s must have constant value types. In particular this means that they may not take the value of a parameter to the inline trait, even though these will be known at inlining time: + ```scala + inline trait A[T](x: T): + inline val y = 1 + inline val a = y // ok + inline val z = x // Not ok + ``` + +### Name clashes in inlining +There is the potential for name clashes between members / parameters of inline traits and parameters / members of inline receivers. These are handled in the following way: + +| Inline Trait Member Type | Inline Receiver Member Type | Behaviour | Justification | Same as `trait` | +|---------------------------------------|----------------------------------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------| +| `val` / `var` incl. `val`/`var` param | `val` / `var` incl. `val` / `var` param | Needs `override`| Ensures behaviour matches that of normal traits. However note that normal traits will warn if we try to override a val parameter. This warning is turned off for inline traits, otherwise every inline trait with a val parameter would warn after inlining. | ✅ | +| `val` / `var` incl. `val`/`var` param | Primary constructor (local) param | Not allowed | We cannot rename the constructor param because users may specify it by name when constructing the class, and we can't have a conflict with the generated parameter accessor, yet the parameter accessor and parameter must have the same name, so we end up with a conflict between the inlined val/var param and the constructor param. Therefore we have to ban this. | ❌ | +| `val` / `var` incl. `val`/`var` param | Method | Not allowed | As normal traits, without `override` will be told "needs override"; with `override` will be told "not a stable immutable value" | ✅ | +| Primary ctor (local) param | `val` / `var` incl. `val` / `var` param | Allowed | We need to inline the generated parameter accessors into the receiver, but these are renamed (prefixed with the inline trait name) when doing so, therefore fine. This does not affect the name in the original trait. | ✅ | +| Primary ctor (local) param | Primary constructor (local) param | Allowed | We need to inline the generated parameter accessors into the receiver, but these are renamed (prefixed with the inline trait name) when doing so, therefore fine. This does not affect the name in the original trait. | ✅ | +| Primary ctor (local) param | Method | Allowed | We need to inline the generated parameter accessors into the receiver, but these are renamed (prefixed with the inline trait name) when doing so, therefore fine. This does not affect the name in the original trait. | ✅ | +| Method | `val` / `var` incl. `val` / `var` param | Requires `override` | | ✅ | +| Method | Primary constructor (local) param | Allowed unless sig match | We have the same issue as with `val`/`var` params if the signature matches, so we need to ban it. Otherwise we allow it as in `trait`. | ❌ | +| Method | Method | Requires `override` | | ✅ | +| Type | Type | Allowed | Usual rules apply | ✅ | + +### Access to parent members +Inline receivers may not access the parameters of their parents (these are private): +```scala +inline trait A(x: Int) + +class C extends A(10): + val y = x // error: Not Found Error +``` + +### When does trait inlining happen? +Trait inlining must happen before pickling to get its benefit across compilation units. +Otherwise with the following under separate compilation we will induce boxing. +Consider the following example: + +```scala +// File A.scala +inline trait IT[T]: + def foo(x: T): T = x + +class A extends IT[Int] + +// File B.scala +def main = + val a = new A() + val x: Int = a.foo(10) // leads to Int.unbox(a.foo(Int.box(10))) +``` + + This happens because when `B.scala` is compiled separately against the interface of `A` (derived from the pickled A.tasty) + it will appear that `A` only supports the generic `T` interface (erasing to `Object` and so boxed), + whereas it actually also has a specialized `Int` interface from inline trait inlining. + If instead, trait inlining occurs before pickling, the generated interface is present in the pickle. + There is precedent for inlining before pickling with transparent inlines. + In addition, because inline traits are optional, code duplication should be expected alongside an increase in tasty file. + +## How do inline traits solve the problem? +We can now do the following with no boxing and unboxing: +```scala +inline trait A[T](val x: T) +class IntA(x: Int) extends A[Int](x) + +class C: + val w1 = IntA(1) + val w2 = IntA(2) + val w3 = IntA(w1.x + w2.x) +``` + +Inline trait expansions are only generated on demand when a class or object extends an inline trait. This avoids the up-front cost and code explosion due to creating specialized copies which might never be needed. + +From the original inline traits specification: +> Inline traits avoid all of the pain points of Scala 2 specialization. They can do more than primitive specialization since they also specialize on value parameters and reference types. This helps avoid megamorphic dispatch. Inline traits also profit from all the optimizations available for inline methods, including inline matches, summonFrom, embedded splices. Indeed the analogy of inline traits and inline methods is strong: inline calls correspond to supercalls in extending classes and objects, inline parameters are inline parameters of the trait, inline traits can have nested inline matches, etc. + +## Shortcoming of inline traits +Compared to full specialization, inline traits have one shortcoming, namely that interfaces are not specialized. For example: + +```scala +inline trait Foo[T](x: T): + def foo = x + +class Bar extends Foo[Int](42) + +def f(b: Foo[Int]) = 37 + b.foo + +@main def main = + val x = Bar() + f(x) +``` + +In this code the call to `b.foo` will refer to the version of `foo` typed `foo: T` which becomes `foo: Object` during erasure, because we accessed `foo` on +an object of declared type `Foo` (even though `b`'s actual runtime type is `Bar`). This will in turn call a bridge method which means the `foo: Int` method will be called, but unnecessary boxing and unboxing will be added: + +```scala +inline trait Foo + def foo#1(): Object + +class Bar extends Foo[Int](42): + override def foo#2(): Int = 42 + override def foo#3(): Object = Int.box(this.foo()) + +def f(b: Foo): Int = 37 + Int.unbox(b.foo#1()) // virtual call to foo#1 resolves to bridge method foo#3, which in turn calls actual method foo#2, with boxing. +``` + +This problem is addressed via `Specialized` traits; see the accompanying document on those. + +## Interaction with other language features + +| Language feature | Is currently supported inside inline traits? | +|-------------------------------------|----------------------------------------------| +| Methods | ✅ | +| `val` / `var` Properties | ✅ | +| Non-local private members | ❌ | +| `type`s | ✅ | +| Inner classes/traits | ❌ | +| Self types | ✅ | +| Inheritance (of inline traits) | ✳️ | +| Opaque types | ✅ | + +✳️ Only allowed by classes, inline traits, traits if the inline trait has no parameters + +### Why can inline traits not be extended by traits if they take parameters? +Consider: +```scala +inline trait A[T](x: T): + val y = x +trait B extends A[Int] +class C extends A[Int](10), B +``` +After inlining, `B.y` is defined in terms of `B.A$$x` (the inlined copy of the parameter accessor of `x`), but this is undefined as we don't have the parameter value in `B` due to the trait parameter passing rules that insist that `C` passes the parameters directly. + +In `C` this is not a problem as we have the parameter value `10`. Even if we try to provide the value by leaving `A$$x` abstract and overriding in `C` +this will not work as `B.A$$x` will still be undefined. + +Therefore there is no case in which this could be useful, and it is likely to cause confusion, so we ban it. +However, there is a reasonable case for `trait extends inline trait` in general, to control the inlining and reduce code duplication so we allow +this pattern if the inline trait has no parameters. + +Note that this restriction does not block `inline trait extends trait` or `inline trait extends class`. + +### Non-local private members +Non-local private members members which are labelled private and accessed from within the class on other instances of the class. +Local private members (members with the same access patterns as the former `private[this]`) are allowed. + +### Inner classes +Inner classes and traits are supported with same behaviour as in normal traits. In particular, the following is completely fine, and will be inlined into B. +```scala +inline trait A[T](val x: T): + opaque type Special = T + + def getSpecial: Special = x + def eatSpecial(y: Special) = "Mmm, that was tasty!" + +class B extends A[Int](100) +``` + +### Opaque types +In contrast, it is not possible to use inlining to "cheat" opaque types, even though it is tempting to try to argue that +the opaque type will be inlined into B and therefore its alias should be visible. This is not allowed because type checking +is performed before inline traits, and this follows the logic that inline traits are an optimisation on top of normal traits, +rather than a semantic change to them. +```scala +inline trait A[T](val x: T): + opaque type Special = T + +class B extends A[Int](100): + def foo: Special = 10 // error: 10 does not conform to Special +``` + +### Self types +Self types are supported but are not inlined. We argue this is desirable as it ensures that the behaviour of inline traits +with self types mirrors that of ordinary traits with self types. Inlining of self types would effectively remove any restrictions that +these self types seek to impose because the subclass would automatically have a matching self type to that of the parent class. This +would prevent an error from being thrown, irrespective of whether the subclass implements the desired traits. +Therefore, when using self types on inline traits, the behaviour observed is the following (as in ordinary traits) e.g.: +```scala +trait A[T]: + this: T1 => + +trait D extends A[Int] // error: self type of D does not conform to that of A +``` + +While inline traits may not directly define inner classes, they may contain methods which define classes within their bodies. + +## Processing of inline traits in the compiler +Inline traits in user code are inlined in the phase `specializeInlineTraits`. + +We also need to replace members accessed on inline receivers with the corresponding inlined symbols, +(see member reference replacements above). +We opt however to do this in erasure instead of its own phase because it needs to be coordinated with erasure of specialized traits with the following constraints: + - we see the erased `A$sp$Int` traits and can point at their members + - we make the replacement before boxing in case `A.foo` is typed with `T` and `B.foo` specializes this to e.g. `Int`. Otherwise we will end up with `Int.unbox(B.foo)` instead of directly `B.foo` which won't typecheck. + +Finally the phase `pruneInlineTraits` is responsible for converting inline traits into pure interfaces by +removing their definitions and handling name mangling of private members. diff --git a/docs/_docs/reference/experimental/specialized-traits.md b/docs/_docs/reference/experimental/specialized-traits.md new file mode 100644 index 000000000000..53144c68bbea --- /dev/null +++ b/docs/_docs/reference/experimental/specialized-traits.md @@ -0,0 +1,731 @@ +# Specialized Traits +Specialized traits accompany inline traits as a new attempt to solve to the specialization problem in Scala 3, replacing the `@specialized` annotation from Scala 2. + +As mentioned in the accompanying document on inline traits, inline traits have one shortcoming, namely that interfaces are not specialized. For example: + +```scala +inline trait Foo[T](x: T): + def foo: T = x + +class Bar extends Foo[Int](42) + +def f(b: Foo[Int]) = 37 + b.foo + +@main def main = + val x = Bar() + f(x) +``` + +In this code the call to `b.foo` will refer to the version of `foo` typed `foo: T` which becomes `foo: Object` during erasure, because we accessed `foo` on +an object of declared type `Foo` (even though `b`'s actual runtime type is `Bar`). This will in turn call a bridge method +which means the `foo: Int` method will be called, but unnecessary boxing and unboxing will be added: + +```scala +inline trait Foo + def foo#1(): Object + +class Bar extends Foo[Int](42): + override def foo#2(): Int = 42 + override def foo#3(): Object = Int.box(this.foo()) + +def f(b: Foo): Int = 37 + Int.unbox(b.foo#1()) // virtual call to foo#1 resolves to bridge method foo#3, which in turn calls actual method foo#2, adding boxing. +``` + +We introduce the typeclass `Specialized` that can be used as a context bound on type parameters of inline traits and inline methods. + +The `Specialized` context bound indicates that we want to create specialized versions of the inline trait where the type parameter is instantiated to the type argument, and replace (in erasure) uses of the inline trait with the corresponding specialized version so that member accesses pass through the specialized interface. + +A `Specialized` context bound on an inline method allows it to call other specialized methods or instantiate specialized traits using the specialized type variable (any code may instantiate specialized traits with concrete types). This allows us to transport information about possible specialization types through multiple layers of generic inline methods (see Transportation of Specialized through Generic Code). + +## Example + +To give a flavour of what specialization does, consider a `Vec` trait for vectors over a numeric type: +```scala +import scala.math.Numeric + +inline trait Vec[T: {Specialized, Numeric}](elems: Array[T]): + private val num = summon[Numeric[T]] + + def length = elems.length + + def apply(i: Int): T = elems(i) + + def scalarProduct(other: Vec[T]): T = + require(this.length == other.length) + var result = num.fromInt(0) + for i <- 0 until length do + result = num.plus(result, num.times(this(i), other(i))) + result + +def printVector(v: Vec[Int]) = println(v) + +object Vec: + inline def apply[T: Specialized](elems: Array[T]) = new Vec[T](elems) {} +end Vec + +val v = Vec[Int](Array(1, 2, 3, 4, 5)) +``` + +Specialization will generate: +```scala +inline trait Vec$sp$Int extends Vec[Int]: + def length: Int + def apply(i: Int): Int + def scalarProduct(other: Vec$sp$Int): Int + +class Vec$impl$Int(elems: Array[Int])(using Numeric[Int]) extends Vec[Int](elems), Vec$sp$Int: + private val Vec$$num = summon[Numeric[Int]] + + def length = elems.length + + def apply(i: Int): Int = elems(i) + + def scalarProduct(other: Vec$sp$Int): Int = + require(this.length == other.length) + var result = Vec$$num.fromInt(0) + for i <- 0 until length do + result = Vec$$num.plus(result, Vec$$num.times(this(i), other(i))) + result + +def printVector(v: Vec$sp$Int) = println(v) + +object Vec: + inline def apply[T: Specialized](elems: Array[T]) = new Vec[T](elems) {} +end Vec + +val v = Vec$impl$Int(Array(1, 2, 3, 4, 5)) +``` + +This provides a number of potential efficiency gains: + - Avoid boxing in the API for values like the result of `scalarProduct` (as we will access the method on the specialized version). + - Specialize on the concrete `Numeric` class instance for `T`, so that calls to `num`'s methods have static targets and can be inlined, and avoid boxing for internal values like `result` (if we use define and use a specialized version of Numeric). + - Use an array `elems` specialized to the actual element instead of a fully generic array that has to be accessed via reflection. (The rest of the compiler already generates `int[]` when the source references `Array[Int]` so this comes for free through inline trait specialization). + +## Creating Specialized Trait Instances +Creation of an object with specialized behaviour can occur in one of two ways: + - Instantiating a `class` or using an `object` [1, 2] which extends the specialized trait, specializing its type parameters. E.g.: +```scala +inline trait Foo[T: Specialized](x: T): + def foo: T = x + +class Bar extends Foo[Int](10): // Type parameter does of course not need to be specified explicitly here (it can be inferred from the value type of 10) + def myMethod = "Hello I am a method" + +// Both Baz and myBar will have specialized versions of foo. +object Baz extends Foo(12) +val myBar = Bar() +``` + - Instantiation of an anonymous class instance directly from the Specialized trait: `new A[Ts](ps1)...(psN) {}` where `A` is a specialized trait and the type parameters `Ts` and term parameters `ps1, ,,, psN` can also be absent. This has a special meaning, as it desugars to instantiating a _specialized instance class_ (`$impl$` class, see Expansion of Specialized Traits). This comes with the twin advantages that: + - there is no need for the boilerplate of manually defining an object/class to extend the specialized trait + - this specialized instance class is reused every time such an instance is created (there is no proliferation of anonymous classes). + + However, to facilitate this reuse, we must impose the following restrictions. Anonymous class instances acting as instances of Specialized traits: + - can extend only a single specialized trait [0], + - cannot mix in further classes or traits, and + - cannot contain member definitions. + + Should these restrictions be undesirable, the user can always create their own named `object` or `class` extending from a specialized trait (i.e. the first case for creation of a specialized object just above), which does not induce these restrictions. + +[0] Note that because the specialized traits take evidence parameters for the `Specialized` typeclass, `new Foo[Int] {}` where `Foo` extends some other specialized trait `Bar` desugars in the compiler to `new Bar[Int] with Foo[Int] {}`, which means we can't distinguish these two cases. Therefore we allow +e.g. `new Bar[Int] with Foo[Int] {}` although there is no reason to use this in source code because it's exactly the same as writing `new Foo[Int] {}`. + +Note that 'class extends specialized trait' and 'object extends specialized trait' are allowed. However, 'trait extends specialized trait' is not. This is due to the restriction on extending inline traits with parameters by ordinary traits, as discussed in `inline-traits.md`. 'inline trait extends specialized trait' is allowed as it is not subject to this restriction. + +As explained in detail in _Specialized Traits Expansion Specification_, specialized instance traits and implementation classes have a special erasure. Classes/objects extending a specialized trait also have special erasure behaviour which adds both the specialized parents and ordinary parents: + +```scala +class Bar extends Foo[Int](10) with Baz[Int](10) +class Bar extends Object, Foo(10), Bar(10), Foo$sp$Int, Bar$sp$Int +``` +This is necessary so that Bar can be treated as a `Bar$sp$Int` or `Foo$sp$Int` (in general we may not have `Bar$sp$Int` extends `Foo$sp$Int` so we add both) and to pass arguments to `Foo` and `Bar`. + +## Specialized Traits Expansion Specification +The following is a technical specification for the expansion of specialized traits. + +### Expansion of Specialized Traits +**Definition**: A _specialized trait_ is an inline trait that has at least one `Specialized` context bound. A specialized context bound (or its expansion to a context parameter) is only allowed for +type parameters of inline methods and inline traits. Regular methods or traits or classes +cannot take `Specialized[T]` parameters. + +**Definition**: A _simple class type_ is a reference to a static class that does not have type parameters. References to traits and references containing non-static prefixes or refinements are excluded. + +**Definition**: A _top class_ is one of `Any`, `AnyVal`, or `Object` (also `AnyRef` which is a synonym of `Object`). + +**Definition**: The _specializing supertype_ `SpecType(Tp)` of a type `Tp` is defined as follows: + - `SpecType(Tp) =` the smallest simple class type `C` such that: + - `C` is a supertype of `Tp` + - The superclass of `C` is a top class, or `C` is itself a top class. +- `SpecType(Nothing) = Nothing` + +> By smallest simple class type we mean in the sense of smallest set of values that the type contains, i.e. the closest to the leaves of the class hierarchy. This simply means we may have a choice between `Foo` and `Any`, with `Foo <:< Any` and we will select `Foo`. + +**Definition** A _specialization_ `SpecTrait[T1, T2, T3, ...]` is a reference to a specialized trait with concrete type arguments. + +The presence of a specialization `SpecTrait[T1, T2, T3, ...]` in the program implies the creation of a _specialized interface trait_. This trait will be named `SpecTrait$sp$C1$C2$C3`, where `$sp$` is a fixed specialization marker and `Cn` is an encoding of the fully qualified name of `SpecType(Tn)`. This means that the name of the specialized interface trait reflects in its name all specializing supertypes of the type arguments passed to specialized type parameters in sequence. Arguments passed to non-specialized type parameters are ignored. + +The presence of an anonymous class instance creation like `new Vec[T](elems) {}` in the program implies the creation of both a specialized interface trait and a _specialized instance class_, named `Vec$impl$TN`. The class name derives from the trait name by replacing `$sp$` with `$impl$`. + +Should the `SpecType` of all of the concrete type arguments to a specialization be a top class or `Nothing`, a specialized interface trait is not produced. However, should such a specialization require an instance class, an instance class will be created and will be named `SpecTrait$impl`. This is advantageous as it means that anonymous class instances extending specialized traits are _always_ replaced by an instance class. + +Replacement of specializations with their corresponding specialized interface traits and anonymous specialized instances with their corresponding specialized instance classes happens via erasure. The _erasure_ of `SpecTrait[T1, T2, T3, ...]` where `SpecType(Tn) = Cn` is: + + - If, for all n, `Cn` is one of the top classes `Any`/`AnyRef`/`AnyVal`/`Object`, or `Cn` is `Nothing`, the usual erased trait `Vec`. + - If for at least one n, `Cn` is some other class, the corresponding specialized interface trait `SpecTrait$sp$C1$C2$C3`. + +We also erase `new SpecTrait[T1, T2, T3, ....]` to: + - `SpecTrait$impl()` if all type arguments are top classes or nothing. + - `SpecTrait$impl$C1$C2$C3()` otherwise. + +The specialized interface traits and implementation classes are created on demand the first time the corresponding specialization or anonymous class is mentioned in the program. + +The idea of this transformation is that we obey the invariant `T <:< Foo[Int] <=> T <:< Foo$sp$Int`. + +### Specialized interface traits definition + +A specialized interface trait takes the form: + +```scala +inline trait Vec[T: Specialized] extends Seq[T] +inline trait Vec$sp$Int extends Vec[Int], Seq$sp$Int +``` + +In general a specialized interface trait that specializes an inline trait `A[T]` with type argument `S`: + - drops all `Specialized` trait parameters of `A` + - adds `A[S]` as first parent trait + - _also_ adds all parents of `A` *in their specialized forms*, + - contains declarations from the body of `A` specialized to the type(s) in question (via inline trait inlining) + - is also an `inline trait`. This is for consistency; see [1]. + - does not take value parameters (including evidence parameters) + +We also allow 2 variants of "partial specialization": +```scala +inline trait Vec[T: Specialized, S] // Specialized traits defined with some non-specialized parameters +val x = new Vec[Int, String]() {} + +inline trait Foo[T: Specialized, S: Specialized] +inline trait Bar[T: Specialized] extends Foo[T, Int] // Specialized to a specialized type variable. +class Baz extends Bar[Boolean] +``` +In the first of these cases, the generated specialized interface traits will get type parameters: +```scala +inline trait Vec$sp$scala$Int[S]() extends Vec[Int, S] +val x = new Vec$impl$scala$Int() +``` + +In the other case, specialization waits until all the types are known, except for `Bar` which gets an additional `Foo$sp$Any$Int` parent. +```scala +inline trait Bar[T: Specialized] extends Foo[T, Int], Foo$sp$Any$Int +inline trait Foo$sp$Any$Int extends Foo[Any, Int] +inline trait Foo$sp$Boolean$Int extends Foo[Boolean, Int] +inline trait Bar$sp$Boolean extends Bar[Int], Foo$sp$Boolean$Int +class Baz extends Foo, Bar, Foo$sp$Boolean$Int, Bar$sp$Boolean +``` + +### Specialized instance class traits definition + +A specialized instance class for an inline trait `A` at specialized argument `S` + - repeats the value parameters of inline trait `A` + - extends `A[S]` with these parameters + - extends the corresponding `$sp$` trait + +For example, here is the specialized instance class for `Vec` at `Int`: + +```scala +class Vec$impl$Int(elems: Array[T]) extends Vec$sp$Int, Vec[Int](elems) +``` +The bodies of the interface traits and instance classes are completed by inlining from the inline traits that they extend. + +## Relationship to other language features + +### Variance and Specialized Traits +Specialized traits may define variance parameters e.g.: +```scala +inline trait MyFunction1[-T1: Specialized, +R: Specialized] +``` +However, variance works in Scala because type parameters are erased, and so we can freely cast e.g. List[Lion] to List[Animal] at runtime. Because specialized traits have a special erasure (and necessarily, because this is how we get the specialization), a couple of variance patterns that are possible with standard traits are not possible with specialized traits. This concerns the top classes and `Nothing`. + +In the contravariance case: +```scala +inline trait RecyclingBin[-T: Specialized]: + def recycle(x: T) = println(s"Recycling ${x}") + +def recycleAnInteger(rbin: RecyclingBin[Int]) = + rbin.recycle(100) + +recycleAnInteger(new RecyclingBin[Any]() {}) // RecyclingBin[Any] can be interpreted as RecyclingBin[Int] due to contravariance +recycleAnInteger(new RecyclingBin[AnyVal]() {}) // RecyclingBin[AnyVal] can be interpreted as RecyclingBin[Int] due to contravariance + +// Yet, this erases to: +def recycleAnInteger(rbin: RecyclingBin$sp$Int) = + rbin.recycle(100) + +recycleAnInteger(RecyclingBin$impl().asInstanceOf[RecyclingBin$sp$Int]) // RecyclingBin$impl cannot be cast to RecyclingBin$sp$Int; Any and Int live in different erasure "buckets" so their erased types are unrelated +recycleAnInteger(RecyclingBin$impl{}.asInstanceOf[RecyclingBin$sp$Int]) // RecyclingBin$impl cannot be cast to RecyclingBin$sp$Int +``` +Therefore we have the following issues: +- `RecyclingBin[Any]`, `RecyclingBin[AnyVal]` may not be passed to `RecyclingBin[Int]` whereas normally they would be able to be passed +- `RecyclingBin[Any]`, `RecyclingBin[Object / AnyRef]` may not be passed to `RecyclingBin[Paper]` whereas normally they would. + +These uses of contravariance can never succeed. Rather than allowing them to always fail at runtime, we reject them at compilation time. We impose an additional restriction on contravariance with specialized parameters: +- If (when `F1 >:> F2`) `A[F1]` is to be interpreted as `A[F2]` under `A[-T: Specialized]`, we require that `SpecType(F1) = SpecType(F2)`. Given `F1 >:> F2` and looking at the definition of `SpecType` this means concretely: + - `RecyclingBin[Any]`, `RecyclingBin[AnyVal]` may not be passed to `RecyclingBin[Int]` whereas normally they would + - `RecyclingBin[Any]`, `RecyclingBin[Object / AnyRef]` may not be passed to `RecyclingBin[Paper]` whereas normally they would. + - `RecyclingBin[Any]` may be passed to `RecyclingBin[Object]`, `RecyclingBin[AnyRef]`, `RecyclingBin[AnyVal]` as these all erase to `RecyclingBin`. + +Covariance has a similar problem: + - In general covariance works fine because it corresponds to interpreting `A[F1]` as `A[F2]` where `F1 <:< F2`. Either`A[F1]` and `A[F2]` both erase to the same type (`A$sp$SpecType(F2)`, or `A` if `F1` and `F2` are both top classes), or `A[F1]` erases to `A$sp$F1` and `A[F2]` erases to `A` (`F2` is a top class while `F1` is not). But `A$sp$F1` is a subtype of `A` by definition so the upcast will succeed (and upcasts are generally cheap compared to downcasts on the JVM so this is acceptable from a performance perspective). + - The only exception is with `Nothing`, because we want to interpret `A[Nothing]` as e.g. `A[Int]`, but we erase `A[Nothing]` to `A`. `A >:> A$sp$Int` so this doesn't work and so we also have to reject covariance using `Nothing`. To implement this correctly would require a specialization `A$sp$Nothing` which extends every single specialization in the program, and this was deemed infeasible. The lack of covariance with `Nothing` makes the code less ergonomic in some cases. For example `case object Nil extends List[Nothing]` has to become `inline trait Nil[T: Specialized] extends List[T]` and `inline def apply[T: Specialized] = new Nil[T] () {}`, but we don't lose too much expressivity. + + +### `sealed` Specialized traits +Like other traits, specialized traits may be `sealed`, but this requires some thought. In particular, the generated `$sp$` traits and `$impl$` classes extend the `sealed` specialized trait potentially from another file without the user being aware that this is happening behind the scenes. We want to allow for example: + +```scala +// (1) +// A.scala +sealed inline trait Foo[T: Specialized] + +// B.scala +def foo(x: Foo[String]) +``` +because this would be allowed with ordinary traits, but the desugaring creates `Foo$sp$String` which is arguably an illegal child of the sealed `Foo[String]` as it's produced only when compiling B.scala. + +We are also faced with the question of whether we should allow the following: +```scala +// (2) +// A.scala +sealed inline trait Foo[T: Specialized] +val x = new Foo[Int]() {} // Forces creation of Foo$sp$Int and Foo$impl$Int + +// B.scala +val y = new Foo[Int]() {} +``` +In the bytecode `x` and `y` may or may not point to the same `Foo$impl$Int` depending on if we share the generated specialized classes, but in +the source code `y` extends `Foo[Int]` illegally. + +In both cases we essentially opt for the "source code" interpretation as this seems clearest for users. In particular we allow example (1) but not example (2). This happens automatically because `sealed` trait inheritance checking is done before specialized trait desugaring / erasure, so it is unaware of the `$sp$` traits and `$impl$` classes. + +It is tempting to think that since the `$impl$` classes do not exist in source we may be able to allow the second example, but this is dangerous for exhaustivity checking: + +```scala +// A.scala +sealed inline trait Foo[T: Specialized] +inline trait Bar[T: Specialized] extends Foo[T] + +def foo(x: Foo[Int]) = x match { + case y: Bar[Int] => println("All good!") +} + +// B.scala +val y = new Foo[Int]() {} // Bad; A.scala compiled with no warnings as exhaustivity checker assumed this was impossible +foo(y) +val z = new Bar[Int]() {} // Ok: We allow this because Bar is not sealed so the exhaustivity checking in A.scala was correct. +foo(z) + +``` + +In terms of exhaustivity checking, we also want this to work within a single file. Consider: +```scala +sealed inline trait List[+T: Specialized] +sealed inline trait Nil[T: Specialized] extends List[T] +sealed inline trait :+:[T: Specialized](h: T, t: List[T]) extends List[T] + +val xs: List[Double] = new Nil[Double]() {} + +def foo(x: List[Double]): Unit = x match { + case xs: :+:[_] => println("Cons case") + case _: Nil[_] => println("Nil case") + // warning: non-exhaustive pattern match, missing case _: List[Double] +} +``` + +Without any changes, the anonymous class `Nil[Double]` also extends the `List[Double]` interface (because anonymous class instances mixin all ancestor traits if there are parameters to pass), so pattern match exhaustivity checking on `List[Double]` requires `_: List[Double]` because the List trait has anonymous class children and so we are unable to decompose `_: List[Double]` to children in space checking. This occurs because we don't do the `$impl$` class replacements until erasure so the anonymous class is still around. For that reason we exempt anonymous classes extending specialized traits from being treated as children for pattern match exhaustivity checking, but we do treat the `$impl$` classes as children (the fact that they are defined even if unused suffices for them to be taken into account). This is correct because the anonymous classes will not exist at runtime when the pattern matches run, rather having been replaced by the `$impl$` classes. + +Furthermore, the generated `List$sp$Double` trait also interferes. After we fix this issue: + +```scala +def foo(x: List[Double]): Unit = x match { + case xs: :+:[_] => println("Cons case") + case _: Nil[_] => println("Nil case") + // warning: non-exhaustive pattern match, missing case _: List[Double] & List$sp$Double +} +``` +There are two potential solutions to this: +- make `$sp$` traits inherit `sealed` if the original specialized trait is `sealed`, but this may make future sharing of specializations challenging if we want to extend the specialized traits from another file. +- don't register the `$sp$` traits as children of the original specialized trait for exhaustivity checking. This is safe because these traits are synthetic and we have the invariant that `T <:< Foo[Int] <=> T <:< Foo$sp$Int`, so users cannot match on `Foo$sp$Int` or `Foo[Int] minus Foo$sp$Int` (the latter being empty) + +We opt for the latter. + +### Other implementation restrictions on specialized traits +These could be lifted with additional work. + +| Behaviour | Limitation | Chance of fixing / limited by | +|--------------------------|-----------------------------------------------|---| +| Use of `?` bounds | May not be used for Specialized parameters; however may be used for non-Specialized parameters in specialized traits. | It was thought that this would be challenging because it would require bridge methods that "despecialize" to ensure that `A$sp$Int` implements `A`'s interface. In the end these bridge methods have been implements, so it should be very possible to lift this restriction as future work.| +| Defining specialized traits inside traits/classes/objects | May define specialized traits inside `object`s. May not define them inside `class`es or `trait`s. | Limited by the way we flatten the owners of generated `$impl$` classes and `$sp$` traits. We really want to build the `Foo$impl$` class directly next to `Foo`. For a single CU this would be possible if we walked the entire tree and found where these belong; for multiple CUs it is more complicated as the tree may not exist in the current CU. The case of path dependent specialized traits was deemed niche enough to not be high priority, and for multiple CUs seems very tricky. | + +## A Larger Case Study + +As an example of a hierarchy of specialized traits, consider the following small group of specialized collection traits: + +```scala +inline trait Iterator[T: Specialized]: + def hasNext: Boolean + def next(): T + +inline trait ArrayIterator[T: Specialized](elems: Array[T]) extends Iterator[T]: + private var current = 0 + def hasNext: Boolean = current < elems.length + def next(): T = try elems(current) finally current += 1 + +inline trait Iterable[T: Specialized]: + def iterator: Iterator[T] + def forall(f: T => Unit): Unit = + val it = iterator + while it.hasNext do f(it.next()) + +inline trait Seq[T: Specialized](elems: Array[T]) extends Iterable[T]: + def length: Int = elems.length + def apply(i: Int): T = elems(i) + def iterator: Iterator[T] = new ArrayIterator[T](elems) {} + +val x = new Seq[Int](Array(1, 2, 3)) {} +``` + +This generates the following interface traits (after inlining, conversion to pure interfaces and erasure): + +```scala +inline trait Iterator$sp$Int extends Iterator: + def hasNext: Boolean + def next(): Int + +inline trait ArrayIterator$sp$Int extends ArrayIterator, Iterator$sp$Int: + def hasNext(): Boolean + def next(): Int + +inline trait Iterable$sp$Int extends Iterable: + def iterator: Iterator$sp$Int + def forall(f: Int => Unit): Unit + +inline trait Seq$sp$Int extends Seq, Iterable$sp$Int: + def forall(f: Function1): Unit + def length(): Int + def apply(i: Int): Int + def iterator(): Iterator$sp$Int +``` +Note that these traits repeat the parent types of their corresponding inline traits (but with specialization added). For instance, `ArrayIterator$sp$Int` extends the specialized version of its parent `Iterator$sp$Int`, so the specialized trait may be used in contexts expecting: + +- The specialized trait `ArrayIterator$sp$Int` itself (i.e. `ArrayIterator[Int]` in source code) +- A generic `ArrayIterator` (i.e. `ArrayIterator[?]` in source code) +- Specialized traits higher in the specialized hierarchy for example `Iterator$sp$Int`. + +The specialized implementation classes for `ArrayIterator` and `Seq` are as follows (since `new Seq[Int] {}` is found in the program. This triggers creation of `Seq$sp$Int` which triggers the need for `ArrayIterator$sp$Int`): + +```scala +class ArrayIterator$impl$Int(elems: Int[]) extends ArrayIterator$sp$Int, ArrayIterator(elems): + private var current = 0 + override def hasNext: Boolean = + current < elems.length + override def next(): Int = + try elems(current) finally current += 1 + +class Seq$impl$Int(elems: Int[]) extends Seq$sp$Int, Seq(elems): + override def iterator: Iterator$sp$Int = new ArrayIterator$impl$Int(elems) + override def forall(f: Int => Unit): Unit = + val it: Iterator$$sp$Int = this.iterator() + while it.hasNext do f.apply$mcVI$sp(it.next()) + override def length: Int = elems.length + override def apply(i: Int): Int = elems(i) +``` + +Here, `f.apply$mcVI$sp` is the specialized apply method of `Function1` at type `Int => Unit`. +This method is generated by Scala 2's function specialization which is also adopted by Scala 3. + +Erasure also adds some bridge methods which are necessary to maintain interface compatibility. For example: +```scala +// in class Seq$impl$Int +override def iterator(): Iterator = this.iterator() +``` + +This is especially important in the case where the specialized trait appears as a method parameter. For example: +```scala +inline trait Foo[T: Specialized] + +inline trait A[T: Specialized]: + def foo(x: Foo[T]): Int + +class B extends A[Int]: + override def foo(x: Foo[Int]) = 10 + +// erases to: +inline trait A: + def foo(x: Foo): Int +class B extends A A$sp$Int: + override def foo(x: Foo$sp$Int): Int = 10 + override def foo(x: Foo): Int = this.foo(x.asInstanceOf[Foo$sp$Int]) // Bridge method maintains interface compatibility with A +``` +This cast is fine because of the invariant that `T <:< Foo[Int] <=> T <:< Foo$sp$Int`, since the method was typechecked under `Foo[Int]`. + +## Transportation of Specialized through generic code +The `Specialized` Type Class is erased at runtime. Instances of `Specialized[T]` are created automatically for types that do not contain type variables. + +It may surprise you to note that the following is valid. The `Numeric` constraint on `T` is only checked when +a concrete type is provided for `S` (and by extension `T`) when instantiating `T2`. +```scala +inline trait T1[T: Numeric] +inline trait T2[S] extends T1[S] +``` +In constrast, we do not allow this type of behaviour for `Specialized`. This is largely to avoid confusion. In particular: +```scala +inline trait T1[T: Specialized] +inline trait T2[S] extends T1[S] +val x = new T2[Int]() {} +``` + +Should `x` be a Specialized `$impl$` instance or a normal anonymous class? If we naively look at just `S`'s definition we would say an anonymous class, +but this is complicated by the fact that the generated anonymous class will also mixin `T1` directly as well as `T2`. Could we specialize +just for `T1`? This is hard to imagine. Furthermore if we don't specialize, this is also counter intuitive because T1 is then never specialized +to Int, because we have no specialized instance of trait `T2` to inline into. + +Therefore we require users to explicitly transport `Specialized` through their code. If a type is used in the right hand side of an `extends` clause then it must also be specialized at its definition. This also applies to type variables appearing deeper inside types. + +Furthermore, a specialized method taking a type parameter `T` and invoking specialized traits or calling other specialized methods using `T` must declare this `T` as specialized. This ensures that a specialized 'slot' can only be filled by a concrete type or a type defined in terms of specialized type parameters. + +```scala +inline trait T1[T: Specialized] + +inline trait T2[S] extends T1[S] // error: S should be specialized +inline trait T3 extends T1[List[Int]] // ok +inline trait T4[S] extends T1[List[S]] // error: S should be specialized +inline trait T5[S: Specialized] extends T1[List[S]] // ok +inline trait T6[T[_], S] extends T1[T[S]] // error: T should be specialized // error: S should be specialized + +inline def foo1[S](x: T1[S]): Int = 10 // error: S should be specialized +inline def foo2[S](x: T1[List[S]]): Int = 10 // error: S should be specialized +inline def foo3[S: Specialized](x: T1[List[S]]): Int = 10 // ok + +inline def bar1[S] = new T1[S]() {} // error: S should be specialized +inline def bar2[S] = new T1[List[S]]() {} // error: S should be specialized +inline def bar3[S: Specialized] = new T1[List[List[S]]]() {} // ok +``` + +Note that in the cases with `List` this restriction is imposed not because of the definition of `List`, but rather simply because we have a +type variable `S` which is not marked as Specialized which appears 'somewhere inside' a type in a Specialized position. Again this is done to +avoid confusion. A user extending `T1[List[S]]` would likely expect some degree of specialization to given the definition of `T1`, but this +is not possible if `S` is not marked as `Specialized`. In practice `specType(List[_])` is `Any` however. + +## Specialized Traits in the Compiler +We introduce a new phase `desugarSpecializedTraits` responsible for detecting specializations, generating the necessary `$sp$` and `$impl$` +classes for these specializations, and inlining into them. + +The replacement of references to e.g. `Vec[Int]` with `Vec$sp$Int` and `new Vec[Int] {}` with `Vec$impl$Int()` is done at erasure, because we cannot change signatures before then, and doing it afterwards would not prevent the boxing that we seek to avoid. + +Specialized traits rely on the semantics and implementation of inline traits, so it may seem logical that `desugarSpecializedTraits` would merely generate +the prototypes for the classes, and allow `specializeInlineTraits` to inline the bodies. We *do not* do this. Rather, the phase `desugarSpecializedTraits` directly performs inlining of the parent traits (`Vec[Int]` in the above example) into the +generated `$sp$` traits and `$impl$` classes, sharing the relevant code with the `specializeInlineTraits` phase through +the `Inlines.scala` file. + +This is done because while we want to keep the two phases separate and avoid coupling where possible (thus allowing e.g. specialized traits to be disabled +while maintaining inline traits), implementing specialized traits requires being able to alternate + between the inlining and specializing steps in some cases. Consider the following +example: + +```scala +inline trait D[R: Specialized] + +inline trait C[S: Specialized]: + def w(y: D[S]): Unit = println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = println("x") + +class B extends A[Char] +``` +If we run just the specialization part of the specialized trait processing (without the inlining yet), we get: + +```scala +inline trait A$sp$Char extends A[Char] +``` + +Inlining the body of `A` results in: +```scala +inline trait A$sp$Char extends A[Char]: + def x(y: C[Char]): Unit = println("x") +``` +Notice that this generates a reference to `C[Char]` which ought to be specialized, given that `S` is marked as `Specialized` in +the definition of `C`. Therefore we need to run the specialization process again. This time we will generate: +```scala +inline trait C$sp$Char extends C[Char] +``` +Inlining the body of `C` into this trait will create a reference to `D[Char]`, which also ought to be specialized. Thus it is clear that +it is possible to create arbitrarily long chains requiring alternating between specialized trait generation and inline trait inlining. We note that +this problem arises not only with the `$sp$` traits, but also the `$impl$` classes (see `specialized-trait-inlining-causes-implementation-required.scala`). + +To resolve this problem without alternating between and looping the `specializeInlineTraits` and `desugarSpecializedTraits` phases in an inconvenient way, we opt to make: +- `specializeInlineTraits` responsible for inlining inline traits written directly in user code: + - If a specialized trait creates an inline trait inlining opportunity which is not `specialized`, this is dealt with by `specializeInlineTraits`. + - Further if a user writes `class Bar extends Foo[Int]` where Foo is declared Specialized, `specializeInlineTraits` will do the inlining. +- `desugarSpecializedTraits` responsible for finding specializations and generating the required `$sp$` traits and `$impl$` classes, inlining the parent specialized traits into these traits/classes, and repeating until no more inlining can be performed and no more `$sp$` traits and `$impl$` classes are needed. + +This decouples the responsibilities of the two phases, but given that `desugarSpecializedTraits` could produce a reference that requires inline trait inlining +in the child class (e.g. `tests/run/specialized-trait-requires-inline-trait-inlining.scala`) we need to run `desugarSpecializedTraits` first. + +We also need to introduce a miniphase `pruneSpecializedMethods` which removes specialized methods. These methods are inline so they would be removed later anyway, but we need to remove them before `pruneInlineTraits` to avoid potentially creating dangling references. + +## [1] Why are the generated traits inline? +Consider the following: +```scala +inline trait A[T]: + def foo = "Hello World" +inline trait B extends A[Int] +class C extends B +// inlines to: +class C extends B: + def foo = "Hello World" + +// vs... + +inline trait A[T: Specialized]: + def foo = "Hello World" +inline trait B extends A[Int] +class C extends B +// would expand to: +trait A$sp$Int: + def foo = "Hello World" +inline trait B extends A$sp$Int +class C extends B +``` +We consider the fact that the location of the inlined `foo` method changes with only a simple +addition of `Specialized` to be inconsistent / confusing. Furthermore it would violate the rule +that ordinary traits may not extend specialized traits. + +Because we make the generated traits inline, we modify the behaviour of inline traits relative to the original semantics from Timothée's thesis, such that inline traits extended by other inline traits are still inlined (instead of inlining only at the first ordinary class extending the family of inline traits). This is necessary so that `A$sp$S$Int` can be made inline and still contain the specialized declarations which we need when we use it as an interface. The original argument for only inlining at the bottom of the hierarchy was to reduce code generation, and that this was sufficient when we only have inline traits, however the additional code generation is only linear in the number of traits in the sequence (*and limited to the interfaces since the implementations are pruned*) as we do not inline multiple copies. We consider this acceptable in order to implement specialized. + +## Future Work + +### Caching of Specialized Traits and Classes + +To avoid redundant repeated code generation of the same traits and classes, specialized instance traits and classes could be cached. +The compiler would put their tasty and classfile artifacts in a special directory on the class path. Each artifact would contain in an annotation a hash of the contents of the trait from which the instance was derived. Before creating a new specialized instance, the compiler would consult this directory to see whether an instance with the given name exists and whether its hash matches. In that case, the artifacts can be re-used. + +### Improve Existing Class Hierarchies + +We have shown that we can formulate an alternative version of a collection-like class hierarchy that is fully specialized. But can we retro-fit this idea even to existing collections? The direct approach would +clearly not work since an existing collection like `Vector[T]` can be created from anywhere whereas a specialized collection can be created only in a monomorphic context where we know the type instance of `T`. So specialized +collections come with a tax in expressiveness which pays for their superior performance. + +But it turns out we can gain a lot of flexibility with three additional tweaks to the language and compiler. + +#### 1. Adapt Overloading to Specialization + +More flexibility could be gained if we allowed method overloading between specialized inline methods and normal methods with matching type signatures. For instance, the `Vector` implementation above seriously restricts `map` by requiring that its `B` type parameter is also `Specialized`. Thus `map` cannot be used to map a specialized collection to another collection if the result element type is not statically known. But we could alleviate the problem by allowing a second, overloaded `map` operation like this: +```scala + def map[B](f: A => B): collection.immutable.Vector[B] = + new collection.immutable.Vector[B](elems.map(f)) +``` +The second implementation of `map` will return an unspecialized vector if +the new element type is not statically known. If overloads like this were allowed, they could be resolved by picking the specialized inline version if +a `Specialized` instance can be synthesized for the actual type argument, and picking the unspecialized version otherwise. + +We can do even better if we allow some additions of the existing collections. In that case, we can add definitions like the inline `map` above to the original collections. +That means, whenever we have a collection `xs` with a type such as `Vector[A]` and a function `f` with a statically known result type `B`, then `xs.map(f)` returns a specialized collection. So we can get specialized collections out of normal collections as long as the element type of the created collection is statically known. + +This can be generalized. In particular, all `apply` methods of `Vector` should be split into methods taking specialized types and unrestricted methods. For instance: +```scala +object Vector: + def apply[T](xs: T*): Vector[T] = ... + inline def apply[T: Specialized](xs: T*): faster.Vector[T] = ... +``` +The same holds for all collection methods such as `map` that return a new collection of a different element type. + +#### 2. Automate the Boilerplate with `specializedBy` + +The described scheme would entail some amount of code duplication. We could automate this with a new annotation that is put on a class and states that the class has a specialized variant. Example: +```scala +@specializedBy[faster.Vector] class Vector[+T] ... +``` +If a class carries such an annotation the specialized inline functions described above could be added automatically. + +#### 3. Optimize Use Sites by Path Splitting + +One remaining problem is that specialization is a compile-time operation. Without putting in additional work, we cannot immediately exploit the situation where a runtime type is a specialized collection but the static type is unspecialized. For instance, consider this use of `Vector`: + +```scala +def sumElems(xs: Vector[Int]): Int = + var i = 0 + var sum = 0 + while i < xs.length do + sum += xs(i) + i += 1 + sum +``` +Here, the problem is that, even though we know that `xs` is a `Vector` of `Int`, we cannot deduce that has been specialized to a `faster.Vector[Int]`. Therefore, `xs(i)` goes through the `apply` method of `Vector`. If the runtime class of `Vector` is indeed specialized this would box the `Int` element to `Object` in a bridge method and unbox it again to `Int` at the call site. This could lose a lot of performance, unless the JVM manages to optimize the box/unbox pair away (so far, experience shows that the JVM is not very good at this). The performance could be even worse than working with an unspecialized `Vector` where elements are held in boxed form so they don't have to be boxed each time they are accessed. + +Of course, we can narrow the type of `sumElems` to +```scala +def sumElems(xs: faster.Vector[Int]): Int +``` +but that would make it less generally usable. Another alternative is to optimize `sumElems` by path splitting. We could detect at runtime whether +`xs` is a `faster.Vector` and optimize the code if it is. For instance, like this: +```scala +def sumElems(xs: Vector[Int]): Int = + val faster: faster.Vector[Int] | Null = xs match + case xs: faster.Vector[_] => xs + case _ => null + var i = 0 + var sum = 0 + while i < xs.length do + sum += (if faster != null then faster(i) else xs(i)) + i += 1 + sum +``` +That would avoid the boxing at the cost of a type test in the computation of `faster` and a null test in the call of `apply`. The type test would be amortized over possibly many calls in the loop. We could do even better by generating a bit more code, splitting the whole loop: +```scala +def sumElems(xs: Vector[Int]): Int = + val faster: faster.Vector[Int] | Null = xs match + case xs: faster.Vector[_] => xs + case _ => null + var i = 0 + var sum = 0 + if faster != null then + while i < xs.length do + sum += faster(i) + i += 1 + else + while i < xs.length do + sum += xs(i) + i += 1 + sum +``` +The example has shown that one can write code over possibly specialized collections that is both general and highly performant. But it does require a lot of hand-written boiler-plate. + +The boilerplate could be generated automatically by an optimization phase in the compiler. Essentially, when compiling methods that take parameters whose type is a class annotated with `specializedBy`, we can do the path splitting automatically in an optimization step. The optimization would first analyze the body of the method to decide which path splitting strategy to apply. + +We believe the three tweaks we have outlined could overcome most of the performance penalties imposed by existing unspecialized class hierarchies like collections, making their performance comparable to languages that use global monomorphization. + +### Specializing Tuples + +The same optimizations can also avoid boxing for tuple elements, and with it extractor-based pattern matching. Scala 3 does not currently specialize tuples at all. Scala 2 specializes pairs but not tuples of higher arity. But it uses a scheme quite different from the one proposed here. + +Scala 2 pre-generates pair classes for all combinations of primitive types and Object. Each pair class inherits or implements access methods for all primitive types and Object. This allows to +arrange it so that access always goes through a specialized method that does not involve boxing. No path splitting is needed to achieve that. On the other hand, the exponentially growing amount of code that needs to be generated restricts the scheme to pairs only. Also, specialization is not done for reference types, access to fields of (say) `String` type still need a cast from `Object` to `String`. + +We could adopt the Scala 2 specialization scheme for pairs. This is not hard, since no new classes need to be generated, we simply re-use the Scala 2 classes. Then the new specialization scheme would apply to tuples of higher arities. Or we forego Scala 2 specialization altogether and specialize all tuples with the new scheme. + +The situation with functions is a bit different. Here, Scala 2 specializes functions with up to two parameters, and Scala 3 re-uses these specializations. +Going beyond that requires some adaptations since functions are not implemented as classes but as lambdas that are directly supported by the JVM. So Scala 3 specialization would have to be extended to the definition of these lambdas. + +### Hand-written Specializations + +Additional improvements could be gained if we allowed the programmer to pick their own implementations for specialized class instances. For example, +we could have a +```scala +inline trait HashMap[K: Specialized, +V: Specialized] ... +``` +and an optimized sub-trait +```scala +inline trait IntHashMap[+V: Specialized] extends HashMap[Int, V] ... +``` +The implementation in `IntHashMap` could exploit that fact that the key type `K` is known to be `Int` to pick a more performant algorithm, for instance. + +It would be great if we could use `IntHashMap` each time a specialized HashMap such as `HashMap$sp$Int$String` is referred to or created. In other words, `IntHashMap` should act as a drop-in replacement for `HashMap$sp$Int$String` that is selected automatically. A detailed proposal for this is left for future work. diff --git a/docs/_docs/reference/overview.md b/docs/_docs/reference/overview.md index 35a3a58733f1..f68af445176a 100644 --- a/docs/_docs/reference/overview.md +++ b/docs/_docs/reference/overview.md @@ -59,7 +59,7 @@ These constructs replace existing constructs with the aim of making the language With the exception of [early initializers](dropped-features/early-initializers.md) and old-style vararg patterns, all superseded constructs continue to be available in Scala 3.0. The plan is to deprecate and phase them out later. -Value classes (superseded by opaque type aliases) are a special case. There are currently no deprecation plans for value classes, since we might bring them back in a more general form if they are supported natively by the JVM as is planned by [project Valhalla](https://openjdk.java.net/projects/valhalla/). +Value classes (superseded by opaque type aliases) are a special case. There are currently no deprecation plans for value classes, since we might bring them back in a more general form if they are supported natively by the JVM as is planned by project Valhalla. ## Restrictions diff --git a/docs/_spec/TODOreference/overview.md b/docs/_spec/TODOreference/overview.md index bdb8aa74c1aa..29700f8d575a 100644 --- a/docs/_spec/TODOreference/overview.md +++ b/docs/_spec/TODOreference/overview.md @@ -59,7 +59,7 @@ These constructs replace existing constructs with the aim of making the language With the exception of [early initializers](dropped-features/early-initializers.md) and old-style vararg patterns, all superseded constructs continue to be available in Scala 3.0. The plan is to deprecate and phase them out later. -Value classes (superseded by opaque type aliases) are a special case. There are currently no deprecation plans for value classes, since we might bring them back in a more general form if they are supported natively by the JVM as is planned by [project Valhalla](https://openjdk.java.net/projects/valhalla/). +Value classes (superseded by opaque type aliases) are a special case. There are currently no deprecation plans for value classes, since we might bring them back in a more general form if they are supported natively by the JVM as is planned by project Valhalla. ## Restrictions diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 73a1703d04b8..bdfcd02b499c 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -197,6 +197,8 @@ subsection: - page: reference/experimental/sub-cases.md - page: reference/experimental/dedented-string-literals.md - page: reference/experimental/spec-strings.md + - page: reference/experimental/inline-traits.md + - page: reference/experimental/specialized-traits.md - page: reference/syntax.md - title: Language Versions index: reference/language-versions/language-versions.md @@ -440,4 +442,5 @@ subsection: - page: reference/error-codes/E230.md - page: reference/error-codes/E231.md - page: reference/error-codes/E232.md - + - page: reference/error-codes/E233.md + - page: reference/error-codes/E234.md diff --git a/library/src/scala/language.scala b/library/src/scala/language.scala index 76e160a08104..80b89adc3fd1 100644 --- a/library/src/scala/language.scala +++ b/library/src/scala/language.scala @@ -243,6 +243,13 @@ object language { @compileTimeOnly("`erasedDefinitions` can only be used at compile time in import statements") object erasedDefinitions + /** Experimental support for specialized traits + * + * @see [[docs/_docs/internals/specialized-traits.md]] + */ + @compileTimeOnly("`specializedTraits` can only be used at compile time in import statements") + object specializedTraits + /** Experimental support for relaxed CanEqual checks for ADT pattern matching * * @see [[https://github.com/scala/improvement-proposals/pull/97]] @@ -392,6 +399,11 @@ object language { @compileTimeOnly("`magic` can only be used at compile time in import statements") object magic + + /** Experimental support for inline traits + */ + @compileTimeOnly("`inlineTraits` can only be used at compile time in import statements") + object inlineTraits end experimental /** The deprecated object contains features that are no longer officially suypported in Scala. diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 638f8ed74a9a..c2eed0f43f93 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -52,6 +52,9 @@ private[scala] object language: @compileTimeOnly("`erasedDefinitions` can only be used at compile time in import statements") object erasedDefinitions + @compileTimeOnly("`specializedTraits` can only be used at compile time in import statements") + object specializedTraits + /** Experimental support for relaxed CanEqual checks for ADT pattern matching * * @see [[https://github.com/scala/improvement-proposals/pull/97]] diff --git a/library/src/scala/specialize/Specialized.scala b/library/src/scala/specialize/Specialized.scala new file mode 100644 index 000000000000..4e9420689f3a --- /dev/null +++ b/library/src/scala/specialize/Specialized.scala @@ -0,0 +1,11 @@ +package scala.specialize +import language.experimental.erasedDefinitions +import scala.annotation.nowarn + +@nowarn +sealed trait Specialized[T] extends compiletime.Erased + +object Specialized: + /* @nowarn: New anonymous class definition will be duplicated at each inline site, + however it's erased at runtime so we don't care. */ + @nowarn inline def apply[T] = new Specialized[T] {} diff --git a/tests/disabled/pos/inline-trait-4-inner-class.scala b/tests/disabled/pos/inline-trait-4-inner-class.scala new file mode 100644 index 000000000000..b3c25e75de4c --- /dev/null +++ b/tests/disabled/pos/inline-trait-4-inner-class.scala @@ -0,0 +1,20 @@ +inline trait Options[+T]: + sealed trait Option: + def get: T + def isEmpty: Boolean + + class Some(x: T) extends Option: + def get: T = x + def isEmpty: Boolean = false + + object None extends Option: + def get: T = throw new NoSuchElementException("None.get") + def isEmpty: Boolean = true +end Options + +object IntOptions extends Options[Int] +import IntOptions._ + +val o1: Option = Some(1) // specialized +val o2: Option = None +val x1: Int = o1.get // no unboxing diff --git a/tests/disabled/pos/inline-trait-body-class-abstract.scala b/tests/disabled/pos/inline-trait-body-class-abstract.scala new file mode 100644 index 000000000000..4704b324e26d --- /dev/null +++ b/tests/disabled/pos/inline-trait-body-class-abstract.scala @@ -0,0 +1,10 @@ +inline trait A: + class InnerA: + def foo(): Int + def bar = foo() + 1 + +class B extends A: + class InnerB extends InnerA: + def foo(): Int = -23 + + def f = InnerB().bar \ No newline at end of file diff --git a/tests/disabled/pos/inline-trait-body-class-enum.scala b/tests/disabled/pos/inline-trait-body-class-enum.scala new file mode 100644 index 000000000000..a114ff396067 --- /dev/null +++ b/tests/disabled/pos/inline-trait-body-class-enum.scala @@ -0,0 +1,6 @@ +inline trait A: + enum Inner: + case A, B, C + +class B extends A: + def f = Inner.B \ No newline at end of file diff --git a/tests/disabled/pos/inline-trait-body-class-object.scala b/tests/disabled/pos/inline-trait-body-class-object.scala new file mode 100644 index 000000000000..dc990c69573c --- /dev/null +++ b/tests/disabled/pos/inline-trait-body-class-object.scala @@ -0,0 +1,6 @@ +inline trait A[T]: + object Inner: + val x: T = ??? + +class B extends A[Int]: + def i: Int = Inner.x diff --git a/tests/disabled/pos/inline-trait-body-trait-generic.scala b/tests/disabled/pos/inline-trait-body-trait-generic.scala new file mode 100644 index 000000000000..999dd0c8c1ea --- /dev/null +++ b/tests/disabled/pos/inline-trait-body-trait-generic.scala @@ -0,0 +1,6 @@ +inline trait A[T]: + trait InnerA[U]: + def x: (T, U) = ??? + +class B extends A[Int]: + class InnerB extends InnerA[String] \ No newline at end of file diff --git a/tests/neg-macros/wrong-owner.check b/tests/neg-macros/wrong-owner.check index dd7c137458df..851285cd9989 100644 --- a/tests/neg-macros/wrong-owner.check +++ b/tests/neg-macros/wrong-owner.check @@ -16,6 +16,6 @@ | |Error: |assertion failed: bad owner; method toString has owner class String, expected was class Foo - |owner chain = method toString, class String, package java.lang, package java, package , ctxOwners = class Foo, class Foo, package , package , package , package , package , package , package , package , package , package , , , , , + |owner chain = method toString, class String, package java.lang, package java, package , ctxOwners = class Foo, class Foo, package , package , package , package , package , package , package , package , package , package , package , , , , , | |stacktrace available when compiling with `-Ydebug` diff --git a/tests/neg/i2421.scala b/tests/neg/i2421.scala index dc8e229f38f0..bcf3da6dbb36 100644 --- a/tests/neg/i2421.scala +++ b/tests/neg/i2421.scala @@ -1,7 +1,6 @@ inline object Foo // OK (error would be detected later, in PostTyper) inline class Bar // error: modifier(s) `inline' incompatible with type definition inline abstract class Baz // error: modifier(s) `inline' incompatible with type definition -inline trait Qux // error: modifier(s) `inline' incompatible with type definition object Quux { inline type T // error: modifier(s) `inline' incompatible with type definition diff --git a/tests/neg/i8337.scala b/tests/neg/i8337.scala index 6e42b96c2855..3f05d00d2d4f 100644 --- a/tests/neg/i8337.scala +++ b/tests/neg/i8337.scala @@ -3,4 +3,4 @@ class Bar extends Foo[Bar] object Q { // error: cyclic reference opaque type X <: Foo[X] = Bar // error: cyclic reference -} \ No newline at end of file +} diff --git a/tests/neg/inline-trait-body-class-case.scala b/tests/neg/inline-trait-body-class-case.scala new file mode 100644 index 000000000000..d8b26eae29b3 --- /dev/null +++ b/tests/neg/inline-trait-body-class-case.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + case class Inner(val x: Int) // error + +class B extends A: + def f = Inner(17).x \ No newline at end of file diff --git a/tests/neg/inline-trait-body-class-extends-inline-trait.scala b/tests/neg/inline-trait-body-class-extends-inline-trait.scala new file mode 100644 index 000000000000..9decc51de177 --- /dev/null +++ b/tests/neg/inline-trait-body-class-extends-inline-trait.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + class Inner extends Trait[Int]: // error: Inline traits may not define inner classes or traits. + val x = 1 + +inline trait Trait[T]: + def f(x: T): T = x + +class B extends A: + val inner = Inner() + def x = inner.x + def f = inner.f(x) \ No newline at end of file diff --git a/tests/neg/inline-trait-body-class-generic.scala b/tests/neg/inline-trait-body-class-generic.scala new file mode 100644 index 000000000000..db3c80c9abd6 --- /dev/null +++ b/tests/neg/inline-trait-body-class-generic.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T]: + class Inner[U](u: U): // error + val x: (T, U) = (???, u) + def f: (T, String) = Inner("U").x + +class B extends A[Int] \ No newline at end of file diff --git a/tests/neg/inline-trait-body-class-params.scala b/tests/neg/inline-trait-body-class-params.scala new file mode 100644 index 000000000000..72169ec4b327 --- /dev/null +++ b/tests/neg/inline-trait-body-class-params.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + class Inner(val x: Int) // error + +class B extends A: + def f = Inner(17).x \ No newline at end of file diff --git a/tests/neg/inline-trait-body-class-return.scala b/tests/neg/inline-trait-body-class-return.scala new file mode 100644 index 000000000000..3603cdfde338 --- /dev/null +++ b/tests/neg/inline-trait-body-class-return.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + sealed class InnerA: // error: Inline traits may not define inner classes or traits. + val x = 1 + def generate(x: Int) = new InnerA() {} + +class B extends A: + val y = generate(7) diff --git a/tests/neg/inline-trait-body-class-sealed.scala b/tests/neg/inline-trait-body-class-sealed.scala new file mode 100644 index 000000000000..7badb82141ab --- /dev/null +++ b/tests/neg/inline-trait-body-class-sealed.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + sealed class InnerA: // error: Inline traits may not define inner classes or traits. + val x = 1 + +class B extends A: + class InnerB extends InnerA + def f = InnerB().x \ No newline at end of file diff --git a/tests/neg/inline-trait-body-class-simple.scala b/tests/neg/inline-trait-body-class-simple.scala new file mode 100644 index 000000000000..854505207501 --- /dev/null +++ b/tests/neg/inline-trait-body-class-simple.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + class Inner: // error: Inline traits may not define inner classes or traits. + val x = 1 + +class B extends A: + def f = Inner().x \ No newline at end of file diff --git a/tests/neg/inline-trait-body-override-def-final.scala b/tests/neg/inline-trait-body-override-def-final.scala new file mode 100644 index 000000000000..f034e0b3564d --- /dev/null +++ b/tests/neg/inline-trait-body-override-def-final.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + final def f(x: Int) = x + +class B extends A: + override final def f(x: Int) = x + 1 // error \ No newline at end of file diff --git a/tests/neg/inline-trait-body-override-val-final.scala b/tests/neg/inline-trait-body-override-val-final.scala new file mode 100644 index 000000000000..4d3279968ebc --- /dev/null +++ b/tests/neg/inline-trait-body-override-val-final.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + final val x = 1 + +class B extends A: + override final val x = 2 // error \ No newline at end of file diff --git a/tests/neg/inline-trait-body-override-var.scala b/tests/neg/inline-trait-body-override-var.scala new file mode 100644 index 000000000000..6c76cac5e70b --- /dev/null +++ b/tests/neg/inline-trait-body-override-var.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + var x: Int = 1 + +class B extends A: + override var x = 2 // error diff --git a/tests/neg/inline-trait-body-private-name-collision.scala b/tests/neg/inline-trait-body-private-name-collision.scala new file mode 100644 index 000000000000..3bee309de8fa --- /dev/null +++ b/tests/neg/inline-trait-body-private-name-collision.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + private val x: Int = 1 // error: inline traits cannot have non-local private members + def eq(o: A) = o.x == x diff --git a/tests/neg/inline-trait-body-trait-inline.scala b/tests/neg/inline-trait-body-trait-inline.scala new file mode 100644 index 000000000000..1e27ec5ccf18 --- /dev/null +++ b/tests/neg/inline-trait-body-trait-inline.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T]: + inline trait InnerA[U]: // error + val x: (T, U) = ??? + +class B extends A[Int]: + class InnerB extends InnerA[String] + def f: (Int, String) = InnerB().x \ No newline at end of file diff --git a/tests/neg/inline-trait-body-trait-parameter.scala b/tests/neg/inline-trait-body-trait-parameter.scala new file mode 100644 index 000000000000..2989532ed54f --- /dev/null +++ b/tests/neg/inline-trait-body-trait-parameter.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T]: + trait InnerAType[T >: Int <: AnyVal] + trait InnerATypes[T <: AnyVal, U <: T] + trait InnerATerm(i: Int) // error + trait InnerATerms(i: Int, j: Double) // error + trait InnerATermsCurried(i: Int, j: Double)(k: String) // error + trait InnerAAllCurried[T, U](i: T, j: U)(k: (T, U)) // error \ No newline at end of file diff --git a/tests/neg/inline-trait-body-trait-simple.scala b/tests/neg/inline-trait-body-trait-simple.scala new file mode 100644 index 000000000000..77e0cf027cd4 --- /dev/null +++ b/tests/neg/inline-trait-body-trait-simple.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T]: + trait InnerA: // error: Inline traits may not contain inner classes. + def x: T = ??? + +class B extends A[Int]: + class InnerB extends InnerA \ No newline at end of file diff --git a/tests/neg/inline-trait-body-trait-term-parameters.scala b/tests/neg/inline-trait-body-trait-term-parameters.scala new file mode 100644 index 000000000000..c28e76c6e614 --- /dev/null +++ b/tests/neg/inline-trait-body-trait-term-parameters.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits + +inline trait A[T]: + trait InnerA(t: T): // error + def x: T = t + +class B extends A[Int]: + class InnerB extends InnerA(???) \ No newline at end of file diff --git a/tests/neg/inline-trait-child-overrides-parent-missing-override.scala b/tests/neg/inline-trait-child-overrides-parent-missing-override.scala new file mode 100644 index 000000000000..bce597cff45d --- /dev/null +++ b/tests/neg/inline-trait-child-overrides-parent-missing-override.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits + +inline trait A(val x: Int) + +class C extends A(10): + val x = 1000 // error: Needs override modifier diff --git a/tests/neg/inline-trait-clash-method-method-needs-override.scala b/tests/neg/inline-trait-clash-method-method-needs-override.scala new file mode 100644 index 000000000000..c83d20064a93 --- /dev/null +++ b/tests/neg/inline-trait-clash-method-method-needs-override.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x(y: String) = "Hello world" + +class C extends A: + def x(y: String) = "Hello world2" // error: Needs override + +@main def Test = + val v = C() + assert(v.x("Hello World") == "Hello world2") diff --git a/tests/neg/inline-trait-clash-method-param.scala b/tests/neg/inline-trait-clash-method-param.scala new file mode 100644 index 000000000000..720f783f9c79 --- /dev/null +++ b/tests/neg/inline-trait-clash-method-param.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x(b: Int) = "Hello world" + def z = "Hello world" + +class C(x: String, z: String) extends A: // error: Inlining of inline trait created name conflict on z. + val y = x + val w = z + +@main def Test = + val v = C("Overridden", "Overridden2") + assert(v.y == "Overridden") diff --git a/tests/neg/inline-trait-clash-method-val-param-needs-override.scala b/tests/neg/inline-trait-clash-method-val-param-needs-override.scala new file mode 100644 index 000000000000..502c726d4884 --- /dev/null +++ b/tests/neg/inline-trait-clash-method-val-param-needs-override.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x = "Hello world" + +class C extends A: + val x = "Overridden" // error: Needs override + +@main def Test = + val v = C() + assert(v.x == "Overridden") diff --git a/tests/neg/inline-trait-clash-method-var-param-missing-override.scala b/tests/neg/inline-trait-clash-method-var-param-missing-override.scala new file mode 100644 index 000000000000..efc856ab6fd8 --- /dev/null +++ b/tests/neg/inline-trait-clash-method-var-param-missing-override.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x = "Hello world" + +class C extends A: + var x = "Overridden" // error: Needs override + +@main def Test = + val v = C() + assert(v.x == "Overridden") diff --git a/tests/neg/inline-trait-clash-method-var-param.scala b/tests/neg/inline-trait-clash-method-var-param.scala new file mode 100644 index 000000000000..303cbf9c1daf --- /dev/null +++ b/tests/neg/inline-trait-clash-method-var-param.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x = "Hello world" + +class C extends A: + override var x = "Overridden" // error: Setter x_= overrides nothing + +@main def Test = + val v = C() + assert(v.x == "Overridden") diff --git a/tests/neg/inline-trait-clash-val-param-method-2.scala b/tests/neg/inline-trait-clash-val-param-method-2.scala new file mode 100644 index 000000000000..e74ff0506da0 --- /dev/null +++ b/tests/neg/inline-trait-clash-val-param-method-2.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A(val x: Int) + +class C extends A(10): + def x = 1000 // error: Needs override marker diff --git a/tests/neg/inline-trait-clash-val-param-method.scala b/tests/neg/inline-trait-clash-val-param-method.scala new file mode 100644 index 000000000000..4d87c916ad70 --- /dev/null +++ b/tests/neg/inline-trait-clash-val-param-method.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A(val x: Int) + +class D extends A(10): + override def x = 1000 // error: needs to be a stable, immutable value diff --git a/tests/neg/inline-trait-clash-val-param-param.scala b/tests/neg/inline-trait-clash-val-param-param.scala new file mode 100644 index 000000000000..aae609f3be2b --- /dev/null +++ b/tests/neg/inline-trait-clash-val-param-param.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.inlineTraits +// Not allowed due to name clash + +inline trait A(val x: Int): + val y = x + +class C(x: Int) extends A(10): // error: Inlining of inline trait created name conflict on x. Constructor parameters of inline receivers may not collide with members of inline traits. + val z = x + +@main def Test = + val v = C(5) + assert(v.y == 10) + assert(v.x == 10) + assert(v.z == 5) + println(v.y) + println(v.x) diff --git a/tests/neg/inline-trait-clash-val-param-val-param-missing-override.scala b/tests/neg/inline-trait-clash-val-param-val-param-missing-override.scala new file mode 100644 index 000000000000..e17cb9dcb26d --- /dev/null +++ b/tests/neg/inline-trait-clash-val-param-val-param-missing-override.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits + +inline trait A(val x: Int, val y: Int) +class C(val y: Int) extends A(10, 54): // error: Needs override + val x = 1000 // error: Needs override diff --git a/tests/neg/inline-trait-clash-var-param-method-2.scala b/tests/neg/inline-trait-clash-var-param-method-2.scala new file mode 100644 index 000000000000..284b6c3af643 --- /dev/null +++ b/tests/neg/inline-trait-clash-var-param-method-2.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A(var x: Int) + +class D extends A(10): + override def x = 1000 // error: cannot override a mutable value diff --git a/tests/neg/inline-trait-clash-var-param-method.scala b/tests/neg/inline-trait-clash-var-param-method.scala new file mode 100644 index 000000000000..ae8dd4c9352e --- /dev/null +++ b/tests/neg/inline-trait-clash-var-param-method.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A(var x: Int) + +class C extends A(10): + def x = 1000 // error: Needs override marker diff --git a/tests/neg/inline-trait-clash-var-param-param.scala b/tests/neg/inline-trait-clash-var-param-param.scala new file mode 100644 index 000000000000..0fc2b68cd7b4 --- /dev/null +++ b/tests/neg/inline-trait-clash-var-param-param.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.inlineTraits +// Not allowed due to name clash + +inline trait A(var x: Int): + val y = x + +class C(x: Int) extends A(10): // error: Inlining of inline trait created name conflict on x. Constructor parameters of inline receivers may not collide with members of inline traits. + val z = x + +@main def Test = + val v = C(5) + assert(v.y == 10) + assert(v.x == 10) + assert(v.z == 5) + println(v.y) + println(v.x) diff --git a/tests/neg/inline-trait-clash-var-param-var-param.scala b/tests/neg/inline-trait-clash-var-param-var-param.scala new file mode 100644 index 000000000000..83e50ea72598 --- /dev/null +++ b/tests/neg/inline-trait-clash-var-param-var-param.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits +inline trait A(val x: Int, var y: Int) +class C(override var y: Int) extends A(10, 54): // error: Cannot override a mutable variable + override val x = 1000 + +@main def Test = + val v = C(44) + assert(v.x == 1000) + assert(v.y == 44) diff --git a/tests/neg/inline-trait-clashing-parent-members.scala b/tests/neg/inline-trait-clashing-parent-members.scala new file mode 100644 index 000000000000..89bce4ccdd21 --- /dev/null +++ b/tests/neg/inline-trait-clashing-parent-members.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + val x = 10 + +inline trait B: + val x = 11 + +class C extends A, B // error: C inherits conflicting members x from A and B + +@main def Test = + val v = C() + assert(v.x == 11) diff --git a/tests/neg/inline-trait-clashing-parent-methods.scala b/tests/neg/inline-trait-clashing-parent-methods.scala new file mode 100644 index 000000000000..3fae501d0ed3 --- /dev/null +++ b/tests/neg/inline-trait-clashing-parent-methods.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def foo = "Hello World" + +inline trait B: + def foo = "Bonjour" + +class C extends A, B // error: C inherits conflicting members diff --git a/tests/neg/inline-trait-clashing-parent-val-params.scala b/tests/neg/inline-trait-clashing-parent-val-params.scala new file mode 100644 index 000000000000..35543f64def0 --- /dev/null +++ b/tests/neg/inline-trait-clashing-parent-val-params.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A(val x: Int) + +inline trait B(val x: Int) + +class C extends A(10), B(11) // error: C inherits conflicting members + +@main def Test = + val v = C() + assert(v.x == 11) diff --git a/tests/neg/inline-trait-co-nested.scala b/tests/neg/inline-trait-co-nested.scala new file mode 100644 index 000000000000..c627b6eee520 --- /dev/null +++ b/tests/neg/inline-trait-co-nested.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A: // At the moment this works with an ordinary trait but throws a TypeError with inline traits + sealed class InnerA: // error: Inline traits may not define inner classes or traits. + val x = new InnerB + + sealed class InnerB: // error: Inline traits may not define inner classes or traits. + val x = new InnerA + +class B extends A: + val y = 10 diff --git a/tests/neg/inline-trait-cross-reference-defined-after.scala b/tests/neg/inline-trait-cross-reference-defined-after.scala new file mode 100644 index 000000000000..f62eb0245ac6 --- /dev/null +++ b/tests/neg/inline-trait-cross-reference-defined-after.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A: // At the moment this works with an ordinary trait but throws a TypeError with inline traits + sealed class InnerA: // error: Inline traits may not define inner classes or traits. + val x = new InnerB + + sealed class InnerB: // error: Inline traits may not define inner classes or traits. + val x = 10 + +class B extends A: + val y = 10 diff --git a/tests/neg/inline-trait-double-nested-class.scala b/tests/neg/inline-trait-double-nested-class.scala new file mode 100644 index 000000000000..3e4d60123cdc --- /dev/null +++ b/tests/neg/inline-trait-double-nested-class.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + sealed class InnerA: // error: Inline traits may not define inner classes or traits. + sealed class InnerInnerA: + val x = 1 + +class B extends A: + class InnerB extends InnerA { + class InnerInnerB extends InnerInnerA + } + def f = + val a = new InnerB() + val b = a.InnerInnerB() + b.x diff --git a/tests/neg/inline-trait-infinite-inline-triangle-cycle.scala b/tests/neg/inline-trait-infinite-inline-triangle-cycle.scala new file mode 100644 index 000000000000..a4eb8fabc539 --- /dev/null +++ b/tests/neg/inline-trait-infinite-inline-triangle-cycle.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new D[S] {} + println("w") + +inline trait D[S] extends C[S] + +class Cl[T] extends D[T] // error: Inlining of inline traits looped, which will create an infinitely long program. This is not allowed. diff --git a/tests/neg/inline-trait-inline-overrides-non-inline-retained-inline.scala b/tests/neg/inline-trait-inline-overrides-non-inline-retained-inline.scala new file mode 100644 index 000000000000..80bdbd2303c1 --- /dev/null +++ b/tests/neg/inline-trait-inline-overrides-non-inline-retained-inline.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x: Int + +inline trait B extends A: + override inline def x = 10 // error: implementation restriction: inline traits cannot have non-local private members. This also means no retained inline methods. + +@main def Test = + val x: A = new B() {} + assert(x.x == 10) diff --git a/tests/neg/inline-trait-inline-val-constant-value.scala b/tests/neg/inline-trait-inline-val-constant-value.scala new file mode 100644 index 000000000000..8817a2e3f27d --- /dev/null +++ b/tests/neg/inline-trait-inline-val-constant-value.scala @@ -0,0 +1,3 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](x: T): + inline val property = x // error: inline value must have a literal constant type diff --git a/tests/neg/inline-trait-nested-class-outside-ref.scala b/tests/neg/inline-trait-nested-class-outside-ref.scala new file mode 100644 index 000000000000..3271bfd3fb1f --- /dev/null +++ b/tests/neg/inline-trait-nested-class-outside-ref.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + class InnerA: // error: Inline traits may not define inner classes or traits. + val x = 10 + +class B extends A: + def foo = 10 + +def x = + val b = B() + val c = b.InnerA() + c diff --git a/tests/neg/inline-trait-nested-class-parameter-passing.scala b/tests/neg/inline-trait-nested-class-parameter-passing.scala new file mode 100644 index 000000000000..6d8e3436ba98 --- /dev/null +++ b/tests/neg/inline-trait-nested-class-parameter-passing.scala @@ -0,0 +1,19 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + sealed class InnerA: // error: Inline traits may not define inner classes or traits. + val x = 1 + +class B extends A: + class InnerB extends InnerA: + override val x = 2 + +def foo(x: A#InnerA) = println(x.x) + +@main def main = + val a = new A() {} + val inner_a = a.InnerA() + foo(inner_a) + + val b = B() + val inner_b = b.InnerB() + foo(inner_b) diff --git a/tests/neg/inline-trait-opaque-type-fail.scala b/tests/neg/inline-trait-opaque-type-fail.scala new file mode 100644 index 000000000000..b416077c6036 --- /dev/null +++ b/tests/neg/inline-trait-opaque-type-fail.scala @@ -0,0 +1,15 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](val x: T): + opaque type Special = T + type Ordinary = T + opaque type Special2 = Int + + def foo1: Special = 10 // error: 10 does not conform to T + def bar1: Ordinary = 10 // error: 10 does not conform to T + def baz1: Special2 = 10 // This one is fine + +class B extends A[Int](100): + def foo2: Special = 10 // error: 10 does not conform to Special (same behaviour as ordinary traits) + def bar2: Ordinary = 10 // This one is fine + def baz2: Special2 = 10 // error: 10 does not conform to Special (same behaviour as ordinary traits) + \ No newline at end of file diff --git a/tests/neg/inline-trait-override-private-member-deleted.scala b/tests/neg/inline-trait-override-private-member-deleted.scala new file mode 100644 index 000000000000..65c28ab4278f --- /dev/null +++ b/tests/neg/inline-trait-override-private-member-deleted.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait Foo: + private def foo = 10 + +class A extends Foo: + override def foo = 11 // error: method foo overrides nothing diff --git a/tests/neg/inline-trait-parent-trait-param-access.scala b/tests/neg/inline-trait-parent-trait-param-access.scala new file mode 100644 index 000000000000..22895387de5c --- /dev/null +++ b/tests/neg/inline-trait-parent-trait-param-access.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A(x: Int) + +class C extends A(10): + val y = x // error: Not Found Error diff --git a/tests/neg/inline-trait-self-inline-anonymous-class.scala b/tests/neg/inline-trait-self-inline-anonymous-class.scala new file mode 100644 index 000000000000..233a16221b0c --- /dev/null +++ b/tests/neg/inline-trait-self-inline-anonymous-class.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} + println("w") + +class T extends C[Int] // error: Inlining of inline traits looped. diff --git a/tests/neg/inline-trait-self-inline-three-cycle.scala b/tests/neg/inline-trait-self-inline-three-cycle.scala new file mode 100644 index 000000000000..49e2a8fac4d9 --- /dev/null +++ b/tests/neg/inline-trait-self-inline-three-cycle.scala @@ -0,0 +1,21 @@ +//> using options -language:experimental.inlineTraits +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new D[S] {} + println("w") + +inline trait D[S]: + def v(x: S): S = x + def w: Unit = + val x = new E[S] {} + println("w") + +inline trait E[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} + println("w") + +def main = + val x = new C[Int] {} // error: Inlining of inline traits looped. diff --git a/tests/neg/inline-trait-self-inline-two-cycle.scala b/tests/neg/inline-trait-self-inline-two-cycle.scala new file mode 100644 index 000000000000..bd7a1e7c06c9 --- /dev/null +++ b/tests/neg/inline-trait-self-inline-two-cycle.scala @@ -0,0 +1,15 @@ +//> using options -language:experimental.inlineTraits +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new D[S] {} + println("w") + +inline trait D[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} + println("w") + +def main = + val x = new C[Int] {} // error: Inlining of inline traits looped. diff --git a/tests/neg/inline-trait-self-specialization-loop.scala b/tests/neg/inline-trait-self-specialization-loop.scala new file mode 100644 index 000000000000..6c986eb241dc --- /dev/null +++ b/tests/neg/inline-trait-self-specialization-loop.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits + +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[Int] {} + println("w") + +def main = + val y = new C[Int] {} // error: Inlining of inline traits loops which would create an infinitely long program. This is not allowed. diff --git a/tests/neg/inline-trait-self-type-missing-parent.scala b/tests/neg/inline-trait-self-type-missing-parent.scala new file mode 100644 index 000000000000..10b271e9962d --- /dev/null +++ b/tests/neg/inline-trait-self-type-missing-parent.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +trait T1 +inline trait A[T]: + this: T1 => + +class B extends A[Int] // error: illegal inheritance: class B does not conform to self type +class C extends A, T1 diff --git a/tests/neg/inline-trait-self-type-problems.scala b/tests/neg/inline-trait-self-type-problems.scala new file mode 100644 index 000000000000..4af8426a8484 --- /dev/null +++ b/tests/neg/inline-trait-self-type-problems.scala @@ -0,0 +1,24 @@ +//> using options -language:experimental.inlineTraits +trait T1 +trait T2 +trait T3 +class Test + +inline trait A[T]: + this: T1 => + +inline trait D extends A[Int] // error: self type of D does not conform to that of A +inline trait E extends D + +inline trait B[T]: + this: T2 & T1 => + +inline trait F extends A[Int], B[Int] // error: self type of F does not conform to that of A + +inline trait C[T]: + this: T => + +inline trait H extends C[Test]: // error self type of H does not conform to that of C + this: T3 => + +class Cl2 extends Test with H with T3 diff --git a/tests/neg/inline-trait-supercall-into-trait.scala b/tests/neg/inline-trait-supercall-into-trait.scala new file mode 100644 index 000000000000..be19a74ce48e --- /dev/null +++ b/tests/neg/inline-trait-supercall-into-trait.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +trait A: + def foo = "A" + +inline trait B extends A: + override def foo = super.foo // error: Inline traits may not contain superclass references to classes or non-inline traits + +class C1 extends B diff --git a/tests/neg/inline-trait-uninitialized-value-should-warn.scala b/tests/neg/inline-trait-uninitialized-value-should-warn.scala new file mode 100644 index 000000000000..5f1a1bf0632a --- /dev/null +++ b/tests/neg/inline-trait-uninitialized-value-should-warn.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +//> using options -Werror -Wsafe-init + +inline trait A(val x: Int): + val f = z + val z: Int // nopos-error: This should warn with -Wsafe-init +class C extends A(10): + val z = 10 diff --git a/tests/neg/specialized-trait-anonymous-class-breaks-rules.scala b/tests/neg/specialized-trait-anonymous-class-breaks-rules.scala new file mode 100644 index 000000000000..60900def115b --- /dev/null +++ b/tests/neg/specialized-trait-anonymous-class-breaks-rules.scala @@ -0,0 +1,28 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits + +inline trait Baz[T: Specialized]: + def foo = "hello world" + +inline trait Foo[T: Specialized] extends Baz[T]: + val bar = 10 + +inline trait Bar[T: Specialized]: + val baz = 100 + +trait MyTrait[T](val x: T) + +@main def main = + val w = new Foo[Int] {} // ok + + val x = new Foo[Int] { // error: Anonymous classes acting as instances of Specialized traits may not have additional members; you can make a named object instead if you like. + val y = 10 + } + + val y = new Foo[Int] with MyTrait(10) with Bar {} // error: Anonymous classes acting as instances of Specialized traits may not mix in other traits; you can make a named object instead if you like. + + val z = new Foo[Int] with Bar {} // error: Anonymous classes acting as instances of Specialized traits may not mix in other traits; you can make a named object instead if you like. + + val a = new Foo[Int] with Baz[Int] {} // ok: We have to allow this because it desugars to the same thing as the new Foo[Int] {} case; it will have the same behaviour when compiled; + + val b = new Baz[Int] with Foo[Int] {} // ok: We have to allow this because it desugars to the same thing as the new Foo[Int] {} case; it will have the same behaviour when compiled; diff --git a/tests/neg/specialized-trait-bad-type-param.scala b/tests/neg/specialized-trait-bad-type-param.scala new file mode 100644 index 000000000000..7a4319ab863b --- /dev/null +++ b/tests/neg/specialized-trait-bad-type-param.scala @@ -0,0 +1,13 @@ +//> using options -experimental -language:experimental.specializedTraits + +object Snippet { + inline trait RecyclingBin[-T: Specialized]: + def recycle(x: T) = println(s"Recycling ${x}") + + def recycleAnInteger(bin: RecyclingBin[Int]) = bin.recycle(100) + def recycleAnInteger(bin: Int) = () + + recycleAnInteger(new RecyclingBin[Anyval]() {}) // error + // ^^^^^^ + // Not found: type Anyval - did you mean AnyVal? +} diff --git a/tests/neg/specialized-trait-banned-covariance.scala b/tests/neg/specialized-trait-banned-covariance.scala new file mode 100644 index 000000000000..d0db47513c18 --- /dev/null +++ b/tests/neg/specialized-trait-banned-covariance.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits + +inline trait List[+T: Specialized] +case object Nil extends List[Nothing] + +def foo(x: List[Int]) = println("Foo") + +def main = + foo(Nil) // error: This use of variance is incompatible with specialized traits. diff --git a/tests/neg/specialized-trait-drops-specialized-qualifier.scala b/tests/neg/specialized-trait-drops-specialized-qualifier.scala new file mode 100644 index 000000000000..2bfa0f029553 --- /dev/null +++ b/tests/neg/specialized-trait-drops-specialized-qualifier.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits +inline trait T1[T: Specialized] +inline trait T2[S] extends T1[S] // error: type S used in a Specialized position, so it must be marked as Specialized at its definition. + +inline def foo[E](x: T1[E]) = 100 // error: type E used in a Specialized position, so it must be marked as Specialized at its definition. + +inline def foo[F] = new T1[F]() {} // error: type F used in a Specialized position, so it must be marked as Specialized at its definition. + +class Bar[T] extends T1[T] // error: type T used in a Specialized position, so it must be marked Specialized at its definition diff --git a/tests/neg/specialized-trait-exhaustivity-check-2.scala b/tests/neg/specialized-trait-exhaustivity-check-2.scala new file mode 100644 index 000000000000..03135e990023 --- /dev/null +++ b/tests/neg/specialized-trait-exhaustivity-check-2.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits -Werror + +// nopos-error: (warning) non-exhaustive pattern match + +sealed inline trait Foo[S: Specialized] +sealed inline trait Baz[S: Specialized] extends Foo[S] +inline trait Bar extends Foo[Int] + +def doMatch(x: Foo[Int]) = x match { + case x: Baz[_] => println("Hello World") +} + + diff --git a/tests/neg/specialized-trait-exhaustivity-check.scala b/tests/neg/specialized-trait-exhaustivity-check.scala new file mode 100644 index 000000000000..df219421ed27 --- /dev/null +++ b/tests/neg/specialized-trait-exhaustivity-check.scala @@ -0,0 +1,17 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits -Werror + +// nopos-error: (warning) non-exhaustive pattern match + +sealed inline trait Foo[S: Specialized] +inline trait Bar extends Foo[Int] + +def doMatch(x: Foo[Int]) = x match { + case x: Bar => println("Hello World") +} + +def main = + val x = new Foo[Int]() {} + doMatch(x) + + diff --git a/tests/neg/specialized-trait-infinite-inline.scala b/tests/neg/specialized-trait-infinite-inline.scala new file mode 100644 index 000000000000..035d96d4c04b --- /dev/null +++ b/tests/neg/specialized-trait-infinite-inline.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized]: + inline def a[S: Specialized](b: S): Boolean = a[S](b) + +@main def main = + val x = new Foo[Int]() {} + x.a(100) // error: inline limit exceeded diff --git a/tests/neg/specialized-trait-inline-method-missing-specialized.scala b/tests/neg/specialized-trait-inline-method-missing-specialized.scala new file mode 100644 index 000000000000..897fad85a802 --- /dev/null +++ b/tests/neg/specialized-trait-inline-method-missing-specialized.scala @@ -0,0 +1,3 @@ +//> using options -language:experimental.specializedTraits +inline trait Vec[T: Specialized](val xs: List[T]): + def method[S: Specialized](other: Vec[S]): Vec[(T, S)] // error: Only inline traits and inline functions may take specialized type parameters diff --git a/tests/neg/specialized-trait-inlining-causes-implementation-required-loop-bad-manual.scala b/tests/neg/specialized-trait-inlining-causes-implementation-required-loop-bad-manual.scala new file mode 100644 index 000000000000..ccbfce55a0bc --- /dev/null +++ b/tests/neg/specialized-trait-inlining-causes-implementation-required-loop-bad-manual.scala @@ -0,0 +1,24 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits + +// Contrast with specialized-trait-inlining-causes-implementation-required-loop-bad.scala. +// and specialized-trait-inlining-causes-implementation-required-loop-without-implementation-fine.scala +// This one is not allowed because it will loop forever when specializing. The $impl$ class will +// get a loop when the body of w is inlined. + +// A[Char] => A$sp$Char, but only once we inline the body of A$sp$Char do we realise that we need +// C$impl$Char as well. + +inline trait C[S: Specialized]: + def v(x: S): S = x + def w: Unit = + class D extends C[S] + println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = + val x = new C[T]() {} // error: Inlining of inline traits looped + println("x") + +def main = + val y = new A[Char] {} diff --git a/tests/neg/specialized-trait-nested-specialized.scala b/tests/neg/specialized-trait-nested-specialized.scala new file mode 100644 index 000000000000..6223e95fcb91 --- /dev/null +++ b/tests/neg/specialized-trait-nested-specialized.scala @@ -0,0 +1,25 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits +inline trait T1[T: Specialized] + +inline trait T2 extends T1[List[Int]] // ok +inline trait T3[S] extends T1[List[S]] // error: S should be specialized +inline trait T4[S] extends T1[List[List[S]]] // error: S should be specialized +inline trait T5[S: Specialized] extends T1[List[S]] // ok; should only specialize later +inline trait T6[T[_], S] extends T1[T[S]] // error: T should be specialized // error: S should be specialized +inline trait T7[T[_]] extends T1[T[Int]] // error: T should be specialized + +inline def foo1[S](x: T1[List[S]]): Int = 10 // error: S should be specialized +inline def foo2(x: T1[List[Int]]): Int = 10 // ok +inline def foo3[S](x: T1[List[List[S]]]): Int = 10 // error: S should be specialized +inline def foo4[S: Specialized](x: T1[List[List[S]]]): Int = 10 // ok + +inline def bar1[S] = new T1[List[S]]() {} // error: S should be specialized +inline def bar2 = new T1[List[Int]]() {} // ok +inline def bar3[S] = new T1[List[List[S]]]() {} // error: S should be specialized +inline def bar4[S: Specialized] = new T1[List[List[S]]]() {} // ok + +inline def baz1[T] = new T1[T1[T]]() {} // error: T should be Specialized +inline def baz2[T](x: T1[T1[T]]) = 1 // error: T should be Specialized +inline def baz3[T: Specialized] = new T1[T1[T]]() {} // ok +inline def baz4[T: Specialized](x: T1[T1[T]]) = 1 // ok diff --git a/tests/neg/specialized-trait-no-path-dependent.scala b/tests/neg/specialized-trait-no-path-dependent.scala new file mode 100644 index 000000000000..8d7027f607c7 --- /dev/null +++ b/tests/neg/specialized-trait-no-path-dependent.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits +trait T1: + inline trait Foo[T: Specialized]: // error: May not define specialized traits inside traits or classes so as to make them path-dependent + def bar = "Bar" diff --git a/tests/neg/specialized-trait-no-regression-overrides-nothing.scala b/tests/neg/specialized-trait-no-regression-overrides-nothing.scala new file mode 100644 index 000000000000..9bef96125971 --- /dev/null +++ b/tests/neg/specialized-trait-no-regression-overrides-nothing.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits +trait A1: + def foo = "A" + +trait B1: + override def foo = "B" // error: foo overrides nothing + +inline trait A2: + def foo = "A" + +inline trait B2: + override def foo = "B" // error: foo overrides nothing diff --git a/tests/neg/specialized-trait-not-inline.scala b/tests/neg/specialized-trait-not-inline.scala new file mode 100644 index 000000000000..c18a317baeae --- /dev/null +++ b/tests/neg/specialized-trait-not-inline.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits +trait Trait[T: Specialized] // error: Only inline traits and inline functions may take Specialized type parameters +def t[T: Specialized](a: T) = "Output" // error: Only inline traits and inline functions may take Specialized type parameters diff --git a/tests/neg/specialized-trait-opaque-type-fail.scala b/tests/neg/specialized-trait-opaque-type-fail.scala new file mode 100644 index 000000000000..3079aa803d55 --- /dev/null +++ b/tests/neg/specialized-trait-opaque-type-fail.scala @@ -0,0 +1,17 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized](val x: T): + opaque type Special = T + type Ordinary = T + opaque type Special2 = Int + + def foo1: Special = 10 // error: 10 does not conform to T + def bar1: Ordinary = 10 // error: 10 does not conform to T + def baz1: Special2 = 10 // This one is fine + +class B extends A[Int](100): + def foo2: Special = 10 // error: 10 does not conform to Special (same behaviour as ordinary traits) + def bar2: Ordinary = 10 // This one is fine + def baz2: Special2 = 10 // error: 10 does not conform to Special (same behaviour as ordinary traits) + \ No newline at end of file diff --git a/tests/neg/specialized-trait-overload.scala b/tests/neg/specialized-trait-overload.scala new file mode 100644 index 000000000000..696f764c5029 --- /dev/null +++ b/tests/neg/specialized-trait-overload.scala @@ -0,0 +1,19 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits + +// In principle we might be able to allow this because after erasure we will have different +// classes. The problem is we don't know the classes at the beginning of compilation +// so until erasure these overloads will be the same. It depends on to what extent the +// pre-erasure phases rely on "pre-computing" post-erasure signatures. For now we block +// it as it's not a requirement for specialized traits to work. + +inline trait Foo[T: Specialized]: + def foo(x: Foo[T]): Foo[T] + +class Bar: + def method1(x: Foo[Int]) = 10 + def method1(x: Foo[String]) = 10 // error: Conflicting definitions + +@main def Test = + val x = Bar() + diff --git a/tests/neg/specialized-trait-question-mark-in-inheritance.scala b/tests/neg/specialized-trait-question-mark-in-inheritance.scala new file mode 100644 index 000000000000..22039935f547 --- /dev/null +++ b/tests/neg/specialized-trait-question-mark-in-inheritance.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits +inline trait T1[T: Specialized] +inline trait T2[T: Specialized] extends T1[?] // error: Wildcard types may not be substituted for Specialized type parameters. + +inline trait T3[T: Specialized, E, F: Numeric] +inline trait T4 extends T3[?, ?, ?] // error: Wildcard types may not be substituted for Specialized type parameters. diff --git a/tests/neg/specialized-trait-question-mark-interface.scala b/tests/neg/specialized-trait-question-mark-interface.scala new file mode 100644 index 000000000000..641ba43c258b --- /dev/null +++ b/tests/neg/specialized-trait-question-mark-interface.scala @@ -0,0 +1,17 @@ +//> using options -language:experimental.specializedTraits -language:experimental.inlineTraits +inline trait Trait[S: Specialized] + +def foo(x: Trait[?], y: Trait[? <: Long]) = // error: Wildcard types may not be substituted for Specialized type parameters. // error: Wildcard types may not be substituted for Specialized type parameters. + println("HELLO WORLD") + +def bar(x: Trait[_]) = // error: Wildcard types may not be substituted for Specialized type parameters. + println("HELLO WORLD") + +def baz(x: Trait[? >: Long]) = // error: Wildcard types may not be substituted for Specialized type parameters. + println("HELLO WORLD") + +def foo2(x: Trait[? >: Long <: Long]) = // error: Wildcard types may not be substituted for Specialized type parameters. + println("HELLO WORLD") + +def main = + foo(new Trait[Int]() {}, new Trait[Long]() {}) diff --git a/tests/neg/specialized-trait-question-mark.scala b/tests/neg/specialized-trait-question-mark.scala new file mode 100644 index 000000000000..ea652a18c5e0 --- /dev/null +++ b/tests/neg/specialized-trait-question-mark.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits +inline trait Trait[S: Specialized] + +def main = + val x = new Trait[?] {} // error: Type argument must be fully defined diff --git a/tests/neg/specialized-trait-scoped-inside-object-deep-nesting.scala b/tests/neg/specialized-trait-scoped-inside-object-deep-nesting.scala new file mode 100644 index 000000000000..b46d2b1adc0e --- /dev/null +++ b/tests/neg/specialized-trait-scoped-inside-object-deep-nesting.scala @@ -0,0 +1,19 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits + +package p1: + package p2: + object O1: + class C1: + trait T1: + object O2: + inline trait Foo[T: Specialized]: // error: May not define specialized traits inside traits or classes so as to make them path-dependent + def bar = "Bar" + + class F extends Foo + + val v = O1.C1() + val w = new v.T1 {} + def foo = new w.O2.Foo[Int] {} + +@main def Test = p1.p2.foo.bar diff --git a/tests/neg/specialized-trait-sealed-cross-file/A_1.scala b/tests/neg/specialized-trait-sealed-cross-file/A_1.scala new file mode 100644 index 000000000000..567107fb9c42 --- /dev/null +++ b/tests/neg/specialized-trait-sealed-cross-file/A_1.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +sealed inline trait Foo[T: Specialized] +inline trait Bar[T: Specialized] extends Foo[T] + +def foo(x: Foo[Int]) = x match { + case y: Bar[Int] => println("ok boomer") +} diff --git a/tests/neg/specialized-trait-sealed-cross-file/B_2.scala b/tests/neg/specialized-trait-sealed-cross-file/B_2.scala new file mode 100644 index 000000000000..bfde7e71d826 --- /dev/null +++ b/tests/neg/specialized-trait-sealed-cross-file/B_2.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +object x extends Bar[Int] // Fine because it extends Bar which is not sealed + +def foo(x: Foo[Int]) = println("hello world") // Generates $sp$ trait but actually fine + +@main def main = ??? + val z = new Foo[Int]() {} // error: Extending sealed trait diff --git a/tests/neg/specialized-trait-self-type-problems.scala b/tests/neg/specialized-trait-self-type-problems.scala new file mode 100644 index 000000000000..c09c1a24dade --- /dev/null +++ b/tests/neg/specialized-trait-self-type-problems.scala @@ -0,0 +1,26 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits + +trait T1 +trait T2 +trait T3 +class Test + +inline trait A[T: Specialized]: + this: T1 => + +inline trait D extends A[Int] // error: self type of D does not conform to that of A +inline trait E extends D + +inline trait B[T: Specialized]: + this: T2 & T1 => + +inline trait F extends A[Int], B[Int] // error: self type of F does not conform to that of A + +inline trait C[T: Specialized]: + this: T => + +inline trait H extends C[Test]: // error self type of H does not conform to that of C + this: T3 => + +class Cl2 extends Test with H with T3 diff --git a/tests/neg/specialized-trait-specialized-incorrect-usage.scala b/tests/neg/specialized-trait-specialized-incorrect-usage.scala new file mode 100644 index 000000000000..dc2c91320b86 --- /dev/null +++ b/tests/neg/specialized-trait-specialized-incorrect-usage.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits +trait Illegal: + def x: Int = + new Specialized[Int] {} // error: Cannot extend sealed trait Specialized in a different source file + 10 + + class Baz extends Specialized[Int] // error: Cannot extend sealed trait Specialized in a different source file + + trait A[T] extends Specialized[T] // error: Cannot extend sealed trait Specialized in a different source file diff --git a/tests/neg/specialized-trait-specialized-incorrect-usage2.scala b/tests/neg/specialized-trait-specialized-incorrect-usage2.scala new file mode 100644 index 000000000000..68dca15834fd --- /dev/null +++ b/tests/neg/specialized-trait-specialized-incorrect-usage2.scala @@ -0,0 +1,25 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits +trait Illegal: + def foo: Specialized[Int] // error: Specialized may only be used as a context bound + + def bar(x: Specialized[Int]): Int // error: Specialized may only be used as a context bound + + class Foo(x: Specialized[Int]) // error: Specialized may only be used as a context bound + + class Bar(val x: Specialized[Int]) // error: Specialized may only be used as a context bound + + val y: Specialized[Char] // error: Specialized may only be used as a context bound + + val v = Specialized.apply[Int] // error: Specialized may only be used as a context bound + + def z = + println(Specialized.apply[Float]) // error: Specialized may only be used as a context bound + + def a = Specialized.apply // error: Specialized may only be used as a context bound + + type V = Specialized // error: Specialized may only be used as a context bound + + type W = Specialized[Int] // error: Specialized may only be used as a context bound + + val b = Specialized // error: Specialized may only be used as a context bound diff --git a/tests/neg/specialized-trait-specialized-incorrect-usage3.scala b/tests/neg/specialized-trait-specialized-incorrect-usage3.scala new file mode 100644 index 000000000000..430b594c2d64 --- /dev/null +++ b/tests/neg/specialized-trait-specialized-incorrect-usage3.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits +trait Illegal: + def x(v: Specialized) = 10 // error: Missing type parameter for Specialized diff --git a/tests/neg/specialized-trait-trait-extends-specialized-trait-no-param.scala b/tests/neg/specialized-trait-trait-extends-specialized-trait-no-param.scala new file mode 100644 index 000000000000..fa268be768a3 --- /dev/null +++ b/tests/neg/specialized-trait-trait-extends-specialized-trait-no-param.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits +inline trait Foo[T: Specialized] + +trait Bar extends Foo[Int]: // error: Specialized traits may not be extended by ordinary traits. They may only be extended by classes, objects or inline/specialized traits. + def myMethod = "Hello I am a method" diff --git a/tests/neg/specialized-trait-trait-extends-specialized-trait.scala b/tests/neg/specialized-trait-trait-extends-specialized-trait.scala new file mode 100644 index 000000000000..f2e955c58998 --- /dev/null +++ b/tests/neg/specialized-trait-trait-extends-specialized-trait.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits +inline trait Foo[T: Specialized](x: T): + def foo = x + +trait Bar extends Foo[Int]: // error: Specialized traits may not be extended by ordinary traits. They may only be extended by classes, objects or inline/specialized traits. + def myMethod = "Hello I am a method" + +def f(b: Foo[Int]) = println(s"We found the following value of foo ${b.foo}") + +@main def main = + val x = new Bar with Foo(19) {} + f(x) diff --git a/tests/neg/specialized-trait-variance-not-allowed.scala b/tests/neg/specialized-trait-variance-not-allowed.scala new file mode 100644 index 000000000000..29212f7a65ac --- /dev/null +++ b/tests/neg/specialized-trait-variance-not-allowed.scala @@ -0,0 +1,50 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits + +trait Animal: + def makeNoise: String +class Lion extends Animal: + override def makeNoise = "ROAR!" +class Dog extends Animal: + override def makeNoise: String = "BARK!" + +trait Material +class Paper extends Material +class Newspaper extends Paper + +inline trait MyList[+T: Specialized](val xs: List[T]): + def map[S](f: T => S) = xs.map(f) +inline trait Bin[-T: Specialized]: + def throwAway(x: T) = println(s"Throwing away ${x}") + +def sound(dogs: MyList[Dog]) = + dogs.map(_.makeNoise) + +def throwAwayGeneralPaper(bin: Bin[Paper]) = + val a4 = Paper() + bin.throwAway(a4) + +def main = + /* These ones are not allowed normally */ + val myAnimals: MyList[Animal] = new MyList(List(Dog(), Dog(), Lion())) {} + sound(myAnimals) // error: MyList[Animal] can be interpreted as MyList[Dog] due to covariance + + val myNewspaperWasteBin = new Bin[Newspaper]() {} + throwAwayGeneralPaper(myNewspaperWasteBin) // error: Bin[Newspaper] cannot be interpreted as Bin[Paper] due to contravariance + + /* These ones are specifically banned for specialized traits: */ + val myObjectBin = new Bin[Object]() {} + throwAwayTheNewspaper(myObjectBin) // error: This use of contravariance is not compatible with specialized traits. + + val myAnyBin = new Bin[Any]() {} + throwAwayTheNewspaper(myAnyBin) // error: This use of contravariance is not compatible with specialized traits. + + throwAwayAnInteger(myAnyBin) // error: This use of contravariance is not compatible with specialized traits. + + val myAnyRefBin = new Bin[AnyRef]() {} + val myAnyValBin = new Bin[AnyVal]() {} + + throwAwayTheNewspaper(myAnyRefBin) // error: This use of contravariance is not compatible with specialized traits + throwAwayAnInteger(myAnyValBin) // error: This use of contravariance is not compatible with specialized traits + + diff --git a/tests/neg/specialized-trait-variance-nothing.scala b/tests/neg/specialized-trait-variance-nothing.scala new file mode 100644 index 000000000000..49ab1cfb76de --- /dev/null +++ b/tests/neg/specialized-trait-variance-nothing.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits + +inline trait Box[+T: Specialized] + +def checkNothingInt(x: Box[Int]) = println("Good morning") +def checkNothingString(x: Box[String]) = println("Good afternoon") + +@main def Test = + val x = new Box[Nothing] {} + + checkNothingInt(x) // error: Illegal variance in specialized traits + checkNothingString(x) // error: Illegal variance in specialized traits diff --git a/tests/neg/specialized-trait-variant-warns.scala b/tests/neg/specialized-trait-variant-warns.scala new file mode 100644 index 000000000000..210c62e23a65 --- /dev/null +++ b/tests/neg/specialized-trait-variant-warns.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.specializedTraits +//> using options -language:experimental.specializedTraits -Werror + +// nopos-error: no warnings under Werror + +inline trait Bin[-T: Specialized] // warn +inline trait List[+T: Specialized] // warn diff --git a/tests/neg/trait-extends-inline-trait-indirectly.scala b/tests/neg/trait-extends-inline-trait-indirectly.scala new file mode 100644 index 000000000000..60610d3a3f8c --- /dev/null +++ b/tests/neg/trait-extends-inline-trait-indirectly.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](x: T): + val y = x +inline trait B extends A[Int] +class C extends B // error: parameterized trait A is indirectly implemented, needs to be implemented directly so that arguments can be passed + +object Test: + def main(args: Array[String]): Unit = + val z = new C diff --git a/tests/neg/trait-extends-inline-trait-with-params-uninitialized.scala b/tests/neg/trait-extends-inline-trait-with-params-uninitialized.scala new file mode 100644 index 000000000000..971ec344917b --- /dev/null +++ b/tests/neg/trait-extends-inline-trait-with-params-uninitialized.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](x: T): + val y = x +trait B extends A[Int] // error: only parameterless inline traits may be extended by ordinary traits. +class C extends B, A[Int](4) diff --git a/tests/neg/trait-inline-trait-clashing.scala b/tests/neg/trait-inline-trait-clashing.scala new file mode 100644 index 000000000000..daafbc457806 --- /dev/null +++ b/tests/neg/trait-inline-trait-clashing.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits +trait Foo: + def foo = 10 + +inline trait Bar: + def foo = 10 + +class C extends Bar, Foo // error: C inherits conflicting members +class D extends Foo, Bar // error: D inherits conflicting members diff --git a/tests/pos/inline-trait-1-simple-trait.scala b/tests/pos/inline-trait-1-simple-trait.scala new file mode 100644 index 000000000000..51dc3fd97b6e --- /dev/null +++ b/tests/pos/inline-trait-1-simple-trait.scala @@ -0,0 +1,37 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + type X = String + + val x: Int = 3 + val y: Int = x + z + private val z: Int = 1 + + def f: Int = g + def f(x: Int): Int = x + def f[T](x: T): Int = 2 + + private def g = 1 + protected[this] def p = 123 + private[this] def pp = 123456 + + def xx: X = "foo".asInstanceOf[X] +end A + +class B extends A: + /* + override type X = String + + override val x: Int = 3 + override val y: Int = this.x.+(this.z) + + private[this] val z: Int = 1 + + override def f: Int = this.g + override def f(x: Int): Int = x + override def f[T](x: T): Int = 2 + + private[this] def g: Int = 1 + + override def xx: X = "foo".asInstanceOf[X] + */ +end B diff --git a/tests/pos/inline-trait-2-generic-trait.scala b/tests/pos/inline-trait-2-generic-trait.scala new file mode 100644 index 000000000000..a6e7de410b9d --- /dev/null +++ b/tests/pos/inline-trait-2-generic-trait.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T]: + def f: T = f + def f(x: T): T = x + def f[U <: T](x: U, y: T): T = x +end A + +class B extends A[Int]: + /* + override def f: Int = this.f + override def f(x: Int): Int = x + override def f[U <: Int](x: U, y: Int): Int = x + */ +end B diff --git a/tests/pos/inline-trait-3-trait-params.scala b/tests/pos/inline-trait-3-trait-params.scala new file mode 100644 index 000000000000..9d89837dcf2f --- /dev/null +++ b/tests/pos/inline-trait-3-trait-params.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.inlineTraits +inline trait A(a: Int): + def f: Int = a + def g(b: Int): Int = a + b +end A + +class B extends A(4): + /* + private val a: Int = 4 + override def f: Int = this.a + override def g(x: Int): Int = this.a.+(b) + */ +end B diff --git a/tests/pos/inline-trait-3-trait-with-params.scala b/tests/pos/inline-trait-3-trait-with-params.scala new file mode 100644 index 000000000000..70983423cf01 --- /dev/null +++ b/tests/pos/inline-trait-3-trait-with-params.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](a: T): + def f: T = a + def f(x: T): T = x + def f[U <: T](x: U, y: T): T = x +end A + +class B extends A[Int](3): + /* + override def f: Int = ??? + override def f(x: Int): Int = ??? + override def f[U <: Int](x: U, y: Int): Int = ??? + */ +end B diff --git a/tests/pos/inline-trait-4-no-inner-class.scala b/tests/pos/inline-trait-4-no-inner-class.scala new file mode 100644 index 000000000000..90415769d852 --- /dev/null +++ b/tests/pos/inline-trait-4-no-inner-class.scala @@ -0,0 +1,25 @@ +//> using options -language:experimental.inlineTraits +inline trait Option[+T]: + def get: T + def isEmpty: Boolean +end Option + +inline trait Some[+T](x: T) extends Option[T]: + def get: T = x + def isEmpty: Boolean = false +end Some + +inline trait None extends Option[Nothing]: + def get: Nothing = throw new NoSuchElementException("None.get") + def isEmpty: Boolean = true +end None + +sealed trait IntOption extends Option[Int] +class IntSome(i: Int) extends IntOption, Some[Int](i) +object IntNone extends IntOption, None + +val o1: IntOption = IntSome(1) // specialized +val o2: IntOption = IntNone +val o3: Some[Int] = IntSome(1) // non-specialized +val x1: Int = o1.get // no unboxing +val x3: Int = o3.get // unboxing diff --git a/tests/pos/inline-trait-accesses-parent-val-param.scala b/tests/pos/inline-trait-accesses-parent-val-param.scala new file mode 100644 index 000000000000..2bd783a7bc2d --- /dev/null +++ b/tests/pos/inline-trait-accesses-parent-val-param.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +//> using options -Werror -Wsafe-init + +inline trait A[T](val x: T): + val v = x + +inline trait B extends A[Int]: + val z = x + +class C extends B, A[Int](10) diff --git a/tests/pos/inline-trait-body-abstract-def.scala b/tests/pos/inline-trait-body-abstract-def.scala new file mode 100644 index 000000000000..c1259ac9efbb --- /dev/null +++ b/tests/pos/inline-trait-body-abstract-def.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T]: + def x: T + +class B extends A[Int]: + def x = 1 + def f: Int = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-context-bound.scala b/tests/pos/inline-trait-body-def-context-bound.scala new file mode 100644 index 000000000000..20992020fe11 --- /dev/null +++ b/tests/pos/inline-trait-body-def-context-bound.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + given List[String] = "AAA" :: Nil + def foo[T: List](x: T): T = summon[List[T]].headOption.getOrElse(x) + +class B extends A: + def f = foo("BBB") \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-curried-params.scala b/tests/pos/inline-trait-body-def-curried-params.scala new file mode 100644 index 000000000000..f34363d0690a --- /dev/null +++ b/tests/pos/inline-trait-body-def-curried-params.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x(foo: Int)(bar: Int) = + foo + bar + + def y(foo: Int) = x(foo)(foo) + +class B extends A: + def f = x(1)(2) diff --git a/tests/pos/inline-trait-body-def-extension-method.scala b/tests/pos/inline-trait-body-def-extension-method.scala new file mode 100644 index 000000000000..a2cd5cbbe844 --- /dev/null +++ b/tests/pos/inline-trait-body-def-extension-method.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + extension [T](x: T) + def foo[U](y: U)(z: T): (T, U, T) = (x, y, z) + +class B extends A: + def f = 1.foo("2")(3) \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-final.scala b/tests/pos/inline-trait-body-def-final.scala new file mode 100644 index 000000000000..122810276a89 --- /dev/null +++ b/tests/pos/inline-trait-body-def-final.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + final def f(x: Int) = x + +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-generic-singleton.scala b/tests/pos/inline-trait-body-def-generic-singleton.scala new file mode 100644 index 000000000000..bc978c651425 --- /dev/null +++ b/tests/pos/inline-trait-body-def-generic-singleton.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def foo: 3 = 3 + def bar[T](x: T): T = x + +class B extends A: + def f: "A" = bar("A") + def g: 3 = foo \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-generic.scala b/tests/pos/inline-trait-body-def-generic.scala new file mode 100644 index 000000000000..de024f0caea1 --- /dev/null +++ b/tests/pos/inline-trait-body-def-generic.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def foo[T] = 1 + def bar: [U <: A] => U => Int = [U <: A] => (x: U) => x.foo + +class B extends A: + def f = foo[String] + bar(this) \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-implicit.scala b/tests/pos/inline-trait-body-def-implicit.scala new file mode 100644 index 000000000000..d3389e7f8271 --- /dev/null +++ b/tests/pos/inline-trait-body-def-implicit.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + implicit val x: String = "AAA" + def foo(implicit s: String): String = s + s + +class B extends A: + def f = foo \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-inline-abstract.scala b/tests/pos/inline-trait-body-def-inline-abstract.scala new file mode 100644 index 000000000000..26171c6fb41c --- /dev/null +++ b/tests/pos/inline-trait-body-def-inline-abstract.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + inline def x: Int + +class B extends A: + inline def x = 1 + def f = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-inline-compiletime.scala b/tests/pos/inline-trait-body-def-inline-compiletime.scala new file mode 100644 index 000000000000..40d1e42d3c28 --- /dev/null +++ b/tests/pos/inline-trait-body-def-inline-compiletime.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +import scala.compiletime.* + +inline trait A: + inline def f[T <: String] = + inline if constValue[T] == "I consent" then "All is OK!" + else error("You must consent!") + +class B extends A: + val x = f["I consent"] \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-inline-transparent.scala b/tests/pos/inline-trait-body-def-inline-transparent.scala new file mode 100644 index 000000000000..e36113a9505d --- /dev/null +++ b/tests/pos/inline-trait-body-def-inline-transparent.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + transparent inline def x = 1 + +class B extends A: + def f: 1 = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-inline.scala b/tests/pos/inline-trait-body-def-inline.scala new file mode 100644 index 000000000000..5a87f4a7a0c2 --- /dev/null +++ b/tests/pos/inline-trait-body-def-inline.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + inline def x = 1 + +class B extends A: + def f = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-lambda.scala b/tests/pos/inline-trait-body-def-lambda.scala new file mode 100644 index 000000000000..85e829f4f4f0 --- /dev/null +++ b/tests/pos/inline-trait-body-def-lambda.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def f = (i: Int) => i + +class B extends A: + def g = f \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-local-val.scala b/tests/pos/inline-trait-body-def-local-val.scala new file mode 100644 index 000000000000..2314d8ae6b63 --- /dev/null +++ b/tests/pos/inline-trait-body-def-local-val.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def f = + val foo = 1 + foo + +class B extends A: + def g = f \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-params.scala b/tests/pos/inline-trait-body-def-params.scala new file mode 100644 index 000000000000..a4da752fece6 --- /dev/null +++ b/tests/pos/inline-trait-body-def-params.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x(foo: Int, bar: Unit) = + bar + foo + +class B extends A: + def f = x(1, ()) \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-parens.scala b/tests/pos/inline-trait-body-def-parens.scala new file mode 100644 index 000000000000..2412d75c6ac9 --- /dev/null +++ b/tests/pos/inline-trait-body-def-parens.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x() = 1 + +class B extends A: + def f = x() \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-simple.scala b/tests/pos/inline-trait-body-def-simple.scala new file mode 100644 index 000000000000..25422f9437af --- /dev/null +++ b/tests/pos/inline-trait-body-def-simple.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x = 1 + +class B extends A: + def f = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-def-using.scala b/tests/pos/inline-trait-body-def-using.scala new file mode 100644 index 000000000000..b0b230864c9c --- /dev/null +++ b/tests/pos/inline-trait-body-def-using.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + given String = "AAA" + def foo(using s: String): String = s + s + +class B extends A: + def f = foo \ No newline at end of file diff --git a/tests/pos/inline-trait-body-lazy-val.scala b/tests/pos/inline-trait-body-lazy-val.scala new file mode 100644 index 000000000000..1ccd491087eb --- /dev/null +++ b/tests/pos/inline-trait-body-lazy-val.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + lazy val x = 1 + +class B extends A: + def f = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-macro-suspend/Macro.scala b/tests/pos/inline-trait-body-macro-suspend/Macro.scala new file mode 100644 index 000000000000..9c1ec234e92b --- /dev/null +++ b/tests/pos/inline-trait-body-macro-suspend/Macro.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.inlineTraits + +import scala.quoted.* + +inline def foo(): Int = + ${fooImpl} + +def fooImpl(using Quotes): Expr[Int] = + '{3} + +inline trait A: + val i: Int = foo() diff --git a/tests/pos/inline-trait-body-macro-suspend/Test.scala b/tests/pos/inline-trait-body-macro-suspend/Test.scala new file mode 100644 index 000000000000..c3acfb647387 --- /dev/null +++ b/tests/pos/inline-trait-body-macro-suspend/Test.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits + +class B extends A: + def test = foo() diff --git a/tests/pos/inline-trait-body-macro/Macro_1.scala b/tests/pos/inline-trait-body-macro/Macro_1.scala new file mode 100644 index 000000000000..9c1ec234e92b --- /dev/null +++ b/tests/pos/inline-trait-body-macro/Macro_1.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.inlineTraits + +import scala.quoted.* + +inline def foo(): Int = + ${fooImpl} + +def fooImpl(using Quotes): Expr[Int] = + '{3} + +inline trait A: + val i: Int = foo() diff --git a/tests/pos/inline-trait-body-macro/Test_2.scala b/tests/pos/inline-trait-body-macro/Test_2.scala new file mode 100644 index 000000000000..06ecafcd4583 --- /dev/null +++ b/tests/pos/inline-trait-body-macro/Test_2.scala @@ -0,0 +1,3 @@ +//> using options -language:experimental.inlineTraits + +class B extends A diff --git a/tests/pos/inline-trait-body-rhs-type.scala b/tests/pos/inline-trait-body-rhs-type.scala new file mode 100644 index 000000000000..7af262f66f96 --- /dev/null +++ b/tests/pos/inline-trait-body-rhs-type.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](x: T): + def f: T = x: T + def g: T = identity[T](x) + def h: this.type = this: this.type + +class B extends A("Hello") diff --git a/tests/pos/inline-trait-body-setter.scala b/tests/pos/inline-trait-body-setter.scala new file mode 100644 index 000000000000..d8ae4e1a1bc1 --- /dev/null +++ b/tests/pos/inline-trait-body-setter.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x = 1 + def x_= (x: Int) = ??? + var y = 1 + +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-body-type.scala b/tests/pos/inline-trait-body-type.scala new file mode 100644 index 000000000000..3dfdc9e36d77 --- /dev/null +++ b/tests/pos/inline-trait-body-type.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T]: + type U = String + +class B extends A[Int]: + def f: U = "ABD" \ No newline at end of file diff --git a/tests/pos/inline-trait-body-val-inline.scala b/tests/pos/inline-trait-body-val-inline.scala new file mode 100644 index 000000000000..d1ba10d1a5b0 --- /dev/null +++ b/tests/pos/inline-trait-body-val-inline.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + inline val x = 1 + +class B extends A: + def f = x \ No newline at end of file diff --git a/tests/pos/inline-trait-body-val.scala b/tests/pos/inline-trait-body-val.scala new file mode 100644 index 000000000000..d0603fda64dd --- /dev/null +++ b/tests/pos/inline-trait-body-val.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + val x = 1 + +class B extends A: + def f = x diff --git a/tests/pos/inline-trait-clash-type-type.scala b/tests/pos/inline-trait-clash-type-type.scala new file mode 100644 index 000000000000..6bba1ae4e4ca --- /dev/null +++ b/tests/pos/inline-trait-clash-type-type.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits +trait One +trait Two extends One + +inline trait A: + type T <: One + +class C extends A: + type T = Two diff --git a/tests/pos/inline-trait-class-type-generic.scala b/tests/pos/inline-trait-class-type-generic.scala new file mode 100644 index 000000000000..28f7ba37e5f2 --- /dev/null +++ b/tests/pos/inline-trait-class-type-generic.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +object MyObject + +inline trait A[T](x: T): + def foo: T = x + +class B extends A[MyObject.type](MyObject): + val y = 1 + +def h(x: B) = x.foo diff --git a/tests/pos/inline-trait-extends-abstract-class.scala b/tests/pos/inline-trait-extends-abstract-class.scala new file mode 100644 index 000000000000..3d2690c715a7 --- /dev/null +++ b/tests/pos/inline-trait-extends-abstract-class.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits +abstract class A: + def foo: Int + +inline trait B(x: Int) extends A: + val y = x + override def foo = 10 + +class C extends B(15) diff --git a/tests/pos/inline-trait-extends-class.scala b/tests/pos/inline-trait-extends-class.scala new file mode 100644 index 000000000000..9b7b94afda46 --- /dev/null +++ b/tests/pos/inline-trait-extends-class.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits +class A: + def foo: Int = 12 + +inline trait B(x: Int) extends A: + val y = x + override def foo = 10 + +class C extends B(15) diff --git a/tests/pos/inline-trait-extends-non-inline.scala b/tests/pos/inline-trait-extends-non-inline.scala new file mode 100644 index 000000000000..0837800291ee --- /dev/null +++ b/tests/pos/inline-trait-extends-non-inline.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +trait A: + def foo = "Hello World" + +inline trait B extends A + +trait C extends B diff --git a/tests/pos/inline-trait-extends-trait-with-parameters.scala b/tests/pos/inline-trait-extends-trait-with-parameters.scala new file mode 100644 index 000000000000..4651e1252a54 --- /dev/null +++ b/tests/pos/inline-trait-extends-trait-with-parameters.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits +trait A(val z: Int): + def foo: Int + +inline trait B(x: Int) extends A: + val y = x + z + override def foo = 10 + +class C extends B(15), A(10) diff --git a/tests/pos/inline-trait-extends-trait.scala b/tests/pos/inline-trait-extends-trait.scala new file mode 100644 index 000000000000..70e9ea39a958 --- /dev/null +++ b/tests/pos/inline-trait-extends-trait.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits +trait A: + def foo: Int + +inline trait B(x: Int) extends A: + val y = x + override def foo = 10 + +class C extends B(15) diff --git a/tests/pos/inline-trait-generic-array.scala b/tests/pos/inline-trait-generic-array.scala new file mode 100644 index 000000000000..b4a175909be1 --- /dev/null +++ b/tests/pos/inline-trait-generic-array.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +import scala.reflect.ClassTag + +inline trait Foo[K: ClassTag]: + var keys: Array[Array[K]] = Array.fill(100){new Array[K](50)} + +val m = new Foo[Int]() {} diff --git a/tests/pos/inline-trait-inheritance-multiple-override.scala b/tests/pos/inline-trait-inheritance-multiple-override.scala new file mode 100644 index 000000000000..35c5d6e2d593 --- /dev/null +++ b/tests/pos/inline-trait-inheritance-multiple-override.scala @@ -0,0 +1,21 @@ +//> using options -language:experimental.inlineTraits +inline trait IT1: + def i: Int = 1 + def f[T](x: T): T = x + +inline trait IT2: + def j: String = "inline" + def g(x: Int): String = x.toString() + +trait T: + def k: List[Nothing] + def h(x: Int, y: Double): Double = x + y + +class C1 extends IT1, T: + override def i: Int = 123456 + def k = Nil + override def h(x: Int, y: Double): Double = 1.0 + +class C2 extends IT1, IT2: + override def i: Int = 567890 + override def f[T](x: T): T = ??? \ No newline at end of file diff --git a/tests/pos/inline-trait-inheritance-multiple.scala b/tests/pos/inline-trait-inheritance-multiple.scala new file mode 100644 index 000000000000..197ece4f827e --- /dev/null +++ b/tests/pos/inline-trait-inheritance-multiple.scala @@ -0,0 +1,18 @@ +//> using options -language:experimental.inlineTraits +object InlineTraits: + inline trait IT1: + def i: Int = 1 + def f[T](x: T): T = x + + inline trait IT2: + def j: String = "inline" + def g(x: Int): String = x.toString() + +trait T: + def k: List[Nothing] + def h(x: Int, y: Double): Double = x + y + +class C1 extends InlineTraits.IT1, T: + def k = Nil + +class C2 extends InlineTraits.IT1, InlineTraits.IT2 \ No newline at end of file diff --git a/tests/pos/inline-trait-inheritance-single-abstract-class-override.scala b/tests/pos/inline-trait-inheritance-single-abstract-class-override.scala new file mode 100644 index 000000000000..5a4ba8a96286 --- /dev/null +++ b/tests/pos/inline-trait-inheritance-single-abstract-class-override.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +inline trait IT: + def i: Int = 1 + def f[T](x: T): T = x + +abstract class AC extends IT: + override def i: Int = 123456 + def j: Int diff --git a/tests/pos/inline-trait-inheritance-single-abstract-class.scala b/tests/pos/inline-trait-inheritance-single-abstract-class.scala new file mode 100644 index 000000000000..508b49614483 --- /dev/null +++ b/tests/pos/inline-trait-inheritance-single-abstract-class.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait IT: + def i: Int = 1 + def f[T](x: T): T = x + +abstract class AC extends IT: + def j: Int diff --git a/tests/pos/inline-trait-inheritance-single-object-override.scala b/tests/pos/inline-trait-inheritance-single-object-override.scala new file mode 100644 index 000000000000..70a78d455678 --- /dev/null +++ b/tests/pos/inline-trait-inheritance-single-object-override.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.inlineTraits +inline trait IT: + def i: Int = 1 + def f[T](x: T): T = x + +object O extends IT: + override def i: Int = 123456 diff --git a/tests/pos/inline-trait-inheritance-single-object.scala b/tests/pos/inline-trait-inheritance-single-object.scala new file mode 100644 index 000000000000..9f9c7e9d348b --- /dev/null +++ b/tests/pos/inline-trait-inheritance-single-object.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait IT: + def i: Int = 1 + def f[T](x: T): T = x + +object O extends IT diff --git a/tests/pos/inline-trait-inter-trait-inlining-no-cycle.scala b/tests/pos/inline-trait-inter-trait-inlining-no-cycle.scala new file mode 100644 index 000000000000..ef9fe500cccd --- /dev/null +++ b/tests/pos/inline-trait-inter-trait-inlining-no-cycle.scala @@ -0,0 +1,17 @@ +//> using options -language:experimental.inlineTraits +inline trait F: + def i = println("HELLO WORLD") + +inline trait E: + def h = + new F() {} + +inline trait D: + def g = + new E() {} + +inline trait C: + def f = + new D() {} + +class MyClass extends C, D, E, F diff --git a/tests/pos/inline-trait-multiple-files/A.scala b/tests/pos/inline-trait-multiple-files/A.scala new file mode 100644 index 000000000000..cc0079fca388 --- /dev/null +++ b/tests/pos/inline-trait-multiple-files/A.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits + +inline trait A: + val i: Int = 1 diff --git a/tests/pos/inline-trait-multiple-files/B.scala b/tests/pos/inline-trait-multiple-files/B.scala new file mode 100644 index 000000000000..06ecafcd4583 --- /dev/null +++ b/tests/pos/inline-trait-multiple-files/B.scala @@ -0,0 +1,3 @@ +//> using options -language:experimental.inlineTraits + +class B extends A diff --git a/tests/pos/inline-trait-multiple-stages-defs/A_1.scala b/tests/pos/inline-trait-multiple-stages-defs/A_1.scala new file mode 100644 index 000000000000..e3b817303e99 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-defs/A_1.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.inlineTraits + +inline trait A(x: Int): + def f: Int = 1 + def g(a: Int): Int = 2 + def h: Int + val i: Int = 3 + val j: Int + var k: Int = 4 + + inline val a = 5 + inline val b = a + inline def b(a: Int): Int = 6 diff --git a/tests/pos/inline-trait-multiple-stages-defs/B_2.scala b/tests/pos/inline-trait-multiple-stages-defs/B_2.scala new file mode 100644 index 000000000000..af8b27bce089 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-defs/B_2.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits + +class B extends A(10): + def h: Int = 11 + val j: Int = 12 diff --git a/tests/pos/inline-trait-multiple-stages-generic-defs/A_1.scala b/tests/pos/inline-trait-multiple-stages-generic-defs/A_1.scala new file mode 100644 index 000000000000..cf99c49c40a2 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-generic-defs/A_1.scala @@ -0,0 +1,11 @@ +//> using options -language:experimental.inlineTraits + +inline trait A[T](x: T): + def f: T = x + def g(a: T): T = a + def h: T + val i: T = x + val j: T + var k: T = x + + inline def method(a: T): T = x diff --git a/tests/pos/inline-trait-multiple-stages-generic-defs/B_2.scala b/tests/pos/inline-trait-multiple-stages-generic-defs/B_2.scala new file mode 100644 index 000000000000..b684951f8898 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-generic-defs/B_2.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits + +class B extends A[Int](10): + def h: Int = 11 + val j: Int = 12 diff --git a/tests/pos/inline-trait-multiple-stages-inline-traits/A_1.scala b/tests/pos/inline-trait-multiple-stages-inline-traits/A_1.scala new file mode 100644 index 000000000000..a94e0a869035 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-inline-traits/A_1.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits + +inline trait A: + def foo = "hello world" + \ No newline at end of file diff --git a/tests/pos/inline-trait-multiple-stages-inline-traits/B_2.scala b/tests/pos/inline-trait-multiple-stages-inline-traits/B_2.scala new file mode 100644 index 000000000000..30d4c01b7636 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-inline-traits/B_2.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits + +inline trait B extends A: + def bar = "Good morning world" diff --git a/tests/pos/inline-trait-multiple-stages-inline-traits/C_3.scala b/tests/pos/inline-trait-multiple-stages-inline-traits/C_3.scala new file mode 100644 index 000000000000..30ad04c88260 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages-inline-traits/C_3.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits + +class C extends B + +@main def main = + val x = C() + println(x.foo) + println(x.bar) diff --git a/tests/pos/inline-trait-multiple-stages/A_1.scala b/tests/pos/inline-trait-multiple-stages/A_1.scala new file mode 100644 index 000000000000..cc0079fca388 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages/A_1.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits + +inline trait A: + val i: Int = 1 diff --git a/tests/pos/inline-trait-multiple-stages/B_2.scala b/tests/pos/inline-trait-multiple-stages/B_2.scala new file mode 100644 index 000000000000..06ecafcd4583 --- /dev/null +++ b/tests/pos/inline-trait-multiple-stages/B_2.scala @@ -0,0 +1,3 @@ +//> using options -language:experimental.inlineTraits + +class B extends A diff --git a/tests/pos/inline-trait-object-not-primitive.scala b/tests/pos/inline-trait-object-not-primitive.scala new file mode 100644 index 000000000000..a5fe92f3aafb --- /dev/null +++ b/tests/pos/inline-trait-object-not-primitive.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +object Hop + +inline trait A[T](x: T): + def foo: T = x + +class B extends A[Hop.type](Hop): + val y = 1 + +def h(x: B) = x.foo diff --git a/tests/pos/inline-trait-opaque-type.scala b/tests/pos/inline-trait-opaque-type.scala new file mode 100644 index 000000000000..701bc06e7f00 --- /dev/null +++ b/tests/pos/inline-trait-opaque-type.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](val x: T): + opaque type Special = T + + def getSpecial: Special = x + def eatSpecial(y: Special) = "Mmm, that was tasty!" + +class B extends A[Int](100) + +def foo = + val b = B() + println(b.eatSpecial(b.getSpecial)) diff --git a/tests/pos/inline-trait-override-overload.scala b/tests/pos/inline-trait-override-overload.scala new file mode 100644 index 000000000000..d93908435829 --- /dev/null +++ b/tests/pos/inline-trait-override-overload.scala @@ -0,0 +1,11 @@ +//> using options -language:experimental.inlineTraits +inline trait Foo[T]: + def foo(x: T) = x + def foo(x: String) = "Test" + +class Bar extends Foo[Int] + +@main def main = + val x = Bar() + println(x.foo(10)) + println(x.foo("Hello world")) \ No newline at end of file diff --git a/tests/pos/inline-trait-pair-example.scala b/tests/pos/inline-trait-pair-example.scala new file mode 100644 index 000000000000..d8da116c87a0 --- /dev/null +++ b/tests/pos/inline-trait-pair-example.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +package inlinetrait + +inline trait Pair[+T1, +T2](val _1: T1, val _2: T2) +class IntDoublePair(override val _1: Int, override val _2: Double) extends Pair[Int, Double](_1, _2) +class CharShortPair(override val _1: Char, override val _2: Short) extends Pair[Char, Short](_1, _2) + +def foo(i: IntDoublePair) = i._1 + i._2 diff --git a/tests/pos/inline-trait-parameter-passing.scala b/tests/pos/inline-trait-parameter-passing.scala new file mode 100644 index 000000000000..edfe74b52cd4 --- /dev/null +++ b/tests/pos/inline-trait-parameter-passing.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def foo() = 10 + +class B extends A: + def bar() = 1000 + +def some_function(x: A) = + x.foo() + +@main def main = + val b = B() + some_function(b) diff --git a/tests/pos/inline-trait-parent-method-call.scala b/tests/pos/inline-trait-parent-method-call.scala new file mode 100644 index 000000000000..3eab6f0979b0 --- /dev/null +++ b/tests/pos/inline-trait-parent-method-call.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def generate(x: Int) = x + 1 + +class B extends A: + val y = generate(7) diff --git a/tests/pos/inline-trait-parent-ref.scala b/tests/pos/inline-trait-parent-ref.scala new file mode 100644 index 000000000000..aeb2f2e898b8 --- /dev/null +++ b/tests/pos/inline-trait-parent-ref.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](x: T): + def foo: T = x + +class B extends A[Int](15): + val y = 1 + +def h(x: B) = x.foo diff --git a/tests/pos/inline-trait-private-evidence.scala b/tests/pos/inline-trait-private-evidence.scala new file mode 100644 index 000000000000..7fac112d4102 --- /dev/null +++ b/tests/pos/inline-trait-private-evidence.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.inlineTraits +/* + This is a special case for inline traits because the summon method is defined inline. We don't evaluate it until after + we've inlined it into child traits, but this means it can hang around in the original inline trait, which is a problem because + it throws a compiler error "method should have been inlined but was not". We need to make sure we prune it out, which initially + was missing for private fields (since it wasn't clear that this was necessary, but it is). +*/ + +inline trait A[T: Numeric]: + private val v = summon[Numeric[T]] + +inline trait B[T: Numeric] extends A[T] + +class C extends B[Int], A[Int] diff --git a/tests/pos/inline-trait-private-nested-inline-must-delete.scala b/tests/pos/inline-trait-private-nested-inline-must-delete.scala new file mode 100644 index 000000000000..9ad8950e9014 --- /dev/null +++ b/tests/pos/inline-trait-private-nested-inline-must-delete.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T: Numeric]: + private val num = summon[Numeric[T]] + private val x = 1 + +class B extends A[Float] diff --git a/tests/pos/inline-trait-private-this-nowarn.scala b/tests/pos/inline-trait-private-this-nowarn.scala new file mode 100644 index 000000000000..161b15277c88 --- /dev/null +++ b/tests/pos/inline-trait-private-this-nowarn.scala @@ -0,0 +1,2 @@ +//> using options -language:experimental.inlineTraits +inline trait CellSpecialized[T](private[this] var x: T) diff --git a/tests/pos/inline-trait-return-ref.scala b/tests/pos/inline-trait-return-ref.scala new file mode 100644 index 000000000000..19bd3afcfa9b --- /dev/null +++ b/tests/pos/inline-trait-return-ref.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](val x: T): + def foo: T = x + +class B extends A[Int](1): + def bar: Int = foo diff --git a/tests/pos/inline-trait-self-inline-two-cycle-no-use.scala b/tests/pos/inline-trait-self-inline-two-cycle-no-use.scala new file mode 100644 index 000000000000..5cea507c2df9 --- /dev/null +++ b/tests/pos/inline-trait-self-inline-two-cycle-no-use.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.inlineTraits +// We throw the error only when someone tries to use the traits, because +// the theoretical infinite inlining is only an intermediate state before pruning anyway. +// At the end these traits can be pruned. + +inline trait C[S]: + def v(x: S): S = x + def w: Unit = + val x = new D[S] {} + println("w") + +inline trait D[S]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} + println("w") diff --git a/tests/pos/inline-trait-self-type.scala b/tests/pos/inline-trait-self-type.scala new file mode 100644 index 000000000000..afc5ed63186e --- /dev/null +++ b/tests/pos/inline-trait-self-type.scala @@ -0,0 +1,27 @@ +//> using options -language:experimental.inlineTraits +trait T1 +trait T2 +trait T3 +class Test + +inline trait A[T]: + this: T1 => + +inline trait D extends A[Int]: + this: T1 => +inline trait E extends D: + this: T1 => + +inline trait B[T]: + this: T2 & T1 => + +inline trait F extends A[Int], B[Int]: + this: T2 & T1 => + +inline trait C[T]: + this: T => + +inline trait H extends C[Test]: + this: T3 & Test => + +class Cl2 extends Test with H with T3 diff --git a/tests/pos/inline-trait-signature-generic-context-bound.scala b/tests/pos/inline-trait-signature-generic-context-bound.scala new file mode 100644 index 000000000000..84d20fd8d7e6 --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-context-bound.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T: List] + +given List[Int] = Nil +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-generic-inferred-type.scala b/tests/pos/inline-trait-signature-generic-inferred-type.scala new file mode 100644 index 000000000000..2a225438c1dc --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-inferred-type.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](val x: T): + def f: T = x + +class B extends A(1): + val y: Int = f diff --git a/tests/pos/inline-trait-signature-generic-invariant.scala b/tests/pos/inline-trait-signature-generic-invariant.scala new file mode 100644 index 000000000000..52b0e0479fc1 --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-invariant.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T]: + def f(x: T): T = x + +class B extends A[Int] diff --git a/tests/pos/inline-trait-signature-generic-parameter.scala b/tests/pos/inline-trait-signature-generic-parameter.scala new file mode 100644 index 000000000000..71759205a138 --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-parameter.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](val x: T): + def f: T = x + +class B extends A[Int](1): + val y: Int = f diff --git a/tests/pos/inline-trait-signature-generic-refinement-type.scala b/tests/pos/inline-trait-signature-generic-refinement-type.scala new file mode 100644 index 000000000000..77520d7b447d --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-refinement-type.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +import reflect.Selectable.reflectiveSelectable + +class C[T](x: T): + def foo(): T = x + +inline trait A[T, U[T]](u: U[T]{ def foo(): T }): + def f: T = u.foo() + +class B extends A(C(1)) diff --git a/tests/pos/inline-trait-signature-generic-singleton.scala b/tests/pos/inline-trait-signature-generic-singleton.scala new file mode 100644 index 000000000000..fb271bd0b507 --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-singleton.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](x: T): + def f: T = x + +class B extends A[1](1) diff --git a/tests/pos/inline-trait-signature-generic-type-bounds.scala b/tests/pos/inline-trait-signature-generic-type-bounds.scala new file mode 100644 index 000000000000..60c07f7df0ce --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-type-bounds.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T >: Int <: AnyVal]: + def f(x: T): T = x + +class B extends A[Int] diff --git a/tests/pos/inline-trait-signature-generic-variant.scala b/tests/pos/inline-trait-signature-generic-variant.scala new file mode 100644 index 000000000000..7185dba62581 --- /dev/null +++ b/tests/pos/inline-trait-signature-generic-variant.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits +inline trait Cov[+T]: + def f: T = ??? + +inline trait Contr[-T]: + def f(x: T) = ??? + +class A extends Cov[AnyVal] +class B extends Contr[Int] \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-currying.scala b/tests/pos/inline-trait-signature-parameters-currying.scala new file mode 100644 index 000000000000..c4d6243818c6 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-currying.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A(i: Int)(j: Double): + def f: Double = i + j + +class B extends A(1)(1.0) diff --git a/tests/pos/inline-trait-signature-parameters-default-value.scala b/tests/pos/inline-trait-signature-parameters-default-value.scala new file mode 100644 index 000000000000..a35e645ebb7b --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-default-value.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A(val x: Int = 4) + +class B extends A(1) +class C extends A() \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-implicit.scala b/tests/pos/inline-trait-signature-parameters-implicit.scala new file mode 100644 index 000000000000..9acab2c517ef --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-implicit.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A(implicit val imp: Int) + +implicit val x: Int = 1 +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-using-nameless.scala b/tests/pos/inline-trait-signature-parameters-using-nameless.scala new file mode 100644 index 000000000000..83972624e6d0 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-using-nameless.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A(using Int) // error + +given x: Int = 1 +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-using.scala b/tests/pos/inline-trait-signature-parameters-using.scala new file mode 100644 index 000000000000..ccf438774353 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-using.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A(using usng: Int) + +given x: Int = 1 +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-val-private.scala b/tests/pos/inline-trait-signature-parameters-val-private.scala new file mode 100644 index 000000000000..639a06619464 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-val-private.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits +inline trait A(x: Int) + +class B extends A(1) \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-val-protected.scala b/tests/pos/inline-trait-signature-parameters-val-protected.scala new file mode 100644 index 000000000000..eef1c6aa5d15 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-val-protected.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits +inline trait A(protected val x: Int) + +class B extends A(1) \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-val.scala b/tests/pos/inline-trait-signature-parameters-val.scala new file mode 100644 index 000000000000..66ffd3d86c43 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-val.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits +inline trait A(val x: Int) + +class B extends A(1) \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parameters-var.scala b/tests/pos/inline-trait-signature-parameters-var.scala new file mode 100644 index 000000000000..cd643cabb151 --- /dev/null +++ b/tests/pos/inline-trait-signature-parameters-var.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits +inline trait A(var x: Int) + +class B extends A(1) \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-parentheses.scala b/tests/pos/inline-trait-signature-parentheses.scala new file mode 100644 index 000000000000..6d61285b893f --- /dev/null +++ b/tests/pos/inline-trait-signature-parentheses.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A(): + def i: Int = 1 + +class B extends A diff --git a/tests/pos/inline-trait-signature-sealed.scala b/tests/pos/inline-trait-signature-sealed.scala new file mode 100644 index 000000000000..76b6302533d5 --- /dev/null +++ b/tests/pos/inline-trait-signature-sealed.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline sealed trait A: + final def f(x: Int) = x + +class B extends A \ No newline at end of file diff --git a/tests/pos/inline-trait-signature-simple.scala b/tests/pos/inline-trait-signature-simple.scala new file mode 100644 index 000000000000..53ecf4759f0b --- /dev/null +++ b/tests/pos/inline-trait-signature-simple.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def i: Int = 1 + +class B extends A diff --git a/tests/pos/inline-trait-super-call-non-overridden.scala b/tests/pos/inline-trait-super-call-non-overridden.scala new file mode 100644 index 000000000000..e2a1caea4d1c --- /dev/null +++ b/tests/pos/inline-trait-super-call-non-overridden.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def foo = 10 + +inline trait B extends A: + def bar = super.foo diff --git a/tests/pos/inline-trait-usage-anonymous-class.scala b/tests/pos/inline-trait-usage-anonymous-class.scala new file mode 100644 index 000000000000..929d0aa99a51 --- /dev/null +++ b/tests/pos/inline-trait-usage-anonymous-class.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T]: + val x: T = ??? + +val a = new A[Int] {} \ No newline at end of file diff --git a/tests/pos/inline-trait-usage-extension.scala b/tests/pos/inline-trait-usage-extension.scala new file mode 100644 index 000000000000..8e4e1601a428 --- /dev/null +++ b/tests/pos/inline-trait-usage-extension.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits +inline trait A + +class B extends A diff --git a/tests/pos/inline-trait-usage-inner.scala b/tests/pos/inline-trait-usage-inner.scala new file mode 100644 index 000000000000..a534f821d13e --- /dev/null +++ b/tests/pos/inline-trait-usage-inner.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.inlineTraits +object O: + inline trait A[T]: + def t: T = ??? + +class B extends O.A[Int] diff --git a/tests/pos/inline-trait-usage-param-type.scala b/tests/pos/inline-trait-usage-param-type.scala new file mode 100644 index 000000000000..de5f6e792f5e --- /dev/null +++ b/tests/pos/inline-trait-usage-param-type.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits +inline trait A + +def f(a: A) = ??? diff --git a/tests/pos/inline-trait-usage-return-type.scala b/tests/pos/inline-trait-usage-return-type.scala new file mode 100644 index 000000000000..74abb6ce4940 --- /dev/null +++ b/tests/pos/inline-trait-usage-return-type.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits +inline trait A + +def f: A = ??? diff --git a/tests/pos/inline-trait-usage-type-bound.scala b/tests/pos/inline-trait-usage-type-bound.scala new file mode 100644 index 000000000000..12391a4d8e32 --- /dev/null +++ b/tests/pos/inline-trait-usage-type-bound.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits +inline trait A + +type T >: A <: A \ No newline at end of file diff --git a/tests/pos/inline-trait-usage-type.scala b/tests/pos/inline-trait-usage-type.scala new file mode 100644 index 000000000000..37a8aed116f0 --- /dev/null +++ b/tests/pos/inline-trait-usage-type.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits +inline trait A + +type T = List[A] \ No newline at end of file diff --git a/tests/pos/inline-trait-val-def-inner-class-inner-class-owners-init.scala b/tests/pos/inline-trait-val-def-inner-class-inner-class-owners-init.scala new file mode 100644 index 000000000000..6c287cd1805e --- /dev/null +++ b/tests/pos/inline-trait-val-def-inner-class-inner-class-owners-init.scala @@ -0,0 +1,17 @@ +//> using options -language:experimental.inlineTraits +inline trait Trait[T](x: Any): + def do_something() = println("Good morning") + +trait Trait2: + def bar() = println("bar") + +inline trait A[T]: + def foo: Trait[T] + +inline trait B extends A[Int]: + override def foo = new Trait[Int](new Trait2() {}) {} + +@main def Test = + val b = new B() {} + val f = b.foo + f.do_something() diff --git a/tests/pos/inline-trait-val-def-inner-class-inner-class-owners.scala b/tests/pos/inline-trait-val-def-inner-class-inner-class-owners.scala new file mode 100644 index 000000000000..eb85c9da236d --- /dev/null +++ b/tests/pos/inline-trait-val-def-inner-class-inner-class-owners.scala @@ -0,0 +1,19 @@ +//> using options -language:experimental.inlineTraits +inline trait Trait[T]: + def do_something() = println("Good morning") + +trait Trait2: + def bar() = println("bar") + +inline trait A[T]: + def foo: Trait[T] + +inline trait B extends A[Int]: + override def foo = new Trait[Int] { + val x = new Trait2() {} + } + +@main def Test = + val b = new B() {} + val f = b.foo + f.do_something() diff --git a/tests/pos/inline-trait-y-equals-x-inlined-nowarn.scala b/tests/pos/inline-trait-y-equals-x-inlined-nowarn.scala new file mode 100644 index 000000000000..cea2423ce65a --- /dev/null +++ b/tests/pos/inline-trait-y-equals-x-inlined-nowarn.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.inlineTraits +//> using options -Werror -Wsafe-init + +inline trait A(val x: Int): + val y = x // We need to be careful not to warn on this when we inline it but leave the body in the parent, because in the child the access is fine as we inline the parameter value, + // while the rhs of val y in the parent inline trait is going to be pruned out later so the illegal access won't be possible. +class C extends A(10) + +@main def Test = + val v = C() + println(v.y) + println(v.x) diff --git a/tests/pos/inlined-value-class-source/Context.scala b/tests/pos/inlined-value-class-source/Context.scala new file mode 100644 index 000000000000..e67660235343 --- /dev/null +++ b/tests/pos/inlined-value-class-source/Context.scala @@ -0,0 +1,2 @@ +final class InlinedContext(var seen: Any): + inline def handle(details: Any): Unit = seen = details diff --git a/tests/pos/inlined-value-class-source/TypeApplications.scala b/tests/pos/inlined-value-class-source/TypeApplications.scala new file mode 100644 index 000000000000..8205484f3a0a --- /dev/null +++ b/tests/pos/inlined-value-class-source/TypeApplications.scala @@ -0,0 +1,2 @@ +final class InlinedTypeApplications(val self: Any) extends AnyVal: + def typeParams(using ctx: InlinedContext): Any = ctx.handle(self) diff --git a/tests/pos/specialized-trait-anonymous-class-as-param.scala b/tests/pos/specialized-trait-anonymous-class-as-param.scala new file mode 100644 index 000000000000..c0f7ce1ea8bc --- /dev/null +++ b/tests/pos/specialized-trait-anonymous-class-as-param.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized](x: Any): + def do_something() = println("Good morning") + +trait Trait2 + +@main def Test = + val b = new Trait(new Trait2() {}) {} diff --git a/tests/pos/specialized-trait-body-macro/Macro_1.scala b/tests/pos/specialized-trait-body-macro/Macro_1.scala new file mode 100644 index 000000000000..9bc4f7301e70 --- /dev/null +++ b/tests/pos/specialized-trait-body-macro/Macro_1.scala @@ -0,0 +1,11 @@ +//> using options -language:experimental.specializedTraits +import scala.quoted.* + +inline def foo(): Int = + ${fooImpl} + +def fooImpl(using Quotes): Expr[Int] = + '{3} + +inline trait A[T: Specialized]: + val i: Int = foo() diff --git a/tests/pos/specialized-trait-body-macro/Test_2.scala b/tests/pos/specialized-trait-body-macro/Test_2.scala new file mode 100644 index 000000000000..ba4e4b643cf8 --- /dev/null +++ b/tests/pos/specialized-trait-body-macro/Test_2.scala @@ -0,0 +1,2 @@ +//> using options -language:experimental.specializedTraits +class B extends A[Int] \ No newline at end of file diff --git a/tests/pos/specialized-trait-class-extends-specialized-trait.scala b/tests/pos/specialized-trait-class-extends-specialized-trait.scala new file mode 100644 index 000000000000..f2ba081e5ecc --- /dev/null +++ b/tests/pos/specialized-trait-class-extends-specialized-trait.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits +inline trait Foo[T: Specialized](x: T): + def foo = x + +class Bar extends Foo(10): + def myMethod = "Hello I am a method" + +def f(b: Foo[Int]) = println(s"We found the following value of foo ${b.foo}") + +@main def main = + val x = Bar() + f(x) diff --git a/tests/pos/specialized-trait-exhaustivity-check-3.scala b/tests/pos/specialized-trait-exhaustivity-check-3.scala new file mode 100644 index 000000000000..320ecd99401f --- /dev/null +++ b/tests/pos/specialized-trait-exhaustivity-check-3.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.specializedTraits -Werror +import scala.annotation.nowarn + +@nowarn("id=E234") +sealed inline trait List[+T: Specialized] +sealed inline trait Nil[T: Specialized] extends List[T] +sealed inline trait :+:[T: Specialized](h: T, t: List[T]) extends List[T] + +val xs: List[Double] = new Nil[Double]() {} + +def foo(x: List[Double]): Unit = x match { + case xs: :+:[_] => println("C") + case _: Nil[_] => +} diff --git a/tests/pos/specialized-trait-exhaustivity-no-problem.scala b/tests/pos/specialized-trait-exhaustivity-no-problem.scala new file mode 100644 index 000000000000..6f244a31617a --- /dev/null +++ b/tests/pos/specialized-trait-exhaustivity-no-problem.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits -Werror + +sealed inline trait Foo[S: Specialized] +inline trait Bar extends Foo[Int] + +def doMatch(x: Foo[Int]) = x match { + case x: Bar => println("Hello World") +} diff --git a/tests/pos/specialized-trait-function-takes-specialized-trait.scala b/tests/pos/specialized-trait-function-takes-specialized-trait.scala new file mode 100644 index 000000000000..6cf930cf0134 --- /dev/null +++ b/tests/pos/specialized-trait-function-takes-specialized-trait.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.specializedTraits + +inline trait Container[T: Specialized](val elem: T) + +def apply(f: Container[Int] => Unit, v: Container[Int]) = + f(v) + +def f(e: Container[Int]) = + println(e.elem) + +@main def Test = + val v = new Container(10) {} + apply(f, v) + apply((e: Container[Int]) => println(e), v) diff --git a/tests/pos/specialized-trait-inline-anonymous-class-defn-nowarn.scala b/tests/pos/specialized-trait-inline-anonymous-class-defn-nowarn.scala new file mode 100644 index 000000000000..01ecce142333 --- /dev/null +++ b/tests/pos/specialized-trait-inline-anonymous-class-defn-nowarn.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.specializedTraits -Werror +inline trait T1[T: Specialized] +inline trait T2[T: Specialized, E] + +inline def foo[F: Specialized] = new T1[F]() {} // By default this warns Anonymous class will be defined at each use site, which may lead to a larger number of classfiles. This is not true for Specialized traits as we share the $impl$ class instances. + +inline def foo[F: Specialized, E] = new T2[F, E]() {} // This should also not warn even though E is not specialized diff --git a/tests/pos/specialized-trait-inline-def-specialized.scala b/tests/pos/specialized-trait-inline-def-specialized.scala new file mode 100644 index 000000000000..98332c74994e --- /dev/null +++ b/tests/pos/specialized-trait-inline-def-specialized.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits + +inline trait Vec[T: Specialized](val x: T) + +inline def foo[T: Specialized](v: Vec[T]) = v.x + +@main def Test = + val v = new Vec[Int](10) {} + println(foo(v)) diff --git a/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad-manual-no-usage.scala b/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad-manual-no-usage.scala new file mode 100644 index 000000000000..87948cffb8c9 --- /dev/null +++ b/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad-manual-no-usage.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits + +// No usage of the spec traits so this is fine. + +inline trait C[S: Specialized]: + def v(x: S): S = x + def w: Unit = + class D extends C[S] + println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = println("x") diff --git a/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad.scala b/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad.scala new file mode 100644 index 000000000000..281c1beee93a --- /dev/null +++ b/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits + +// A[Char] => A$sp$Char => C$sp$Char, but only once we inline the body of C$sp$Char do we realise that we need +// C$impl$Char as well. + +inline trait C[S: Specialized]: + def v(x: S): S = x + def w: Unit = + val x = new C[S] {} // Actually ok because $impl$ classes are generated outside of the inline trait; class C$impl$Char will just create an instance of itself (this is allowed) + println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = println("x") + +def main = + val y = new A[Char] {} diff --git a/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-without-implementation-fine.scala b/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-without-implementation-fine.scala new file mode 100644 index 000000000000..61c4e83cba56 --- /dev/null +++ b/tests/pos/specialized-trait-inlining-causes-implementation-required-loop-without-implementation-fine.scala @@ -0,0 +1,15 @@ +//> using options -language:experimental.specializedTraits + +// This one is fine because we only ask for an $sp$ trait interface in C and not an $impl$ class + +inline trait C[S: Specialized]: + def v(x: S): S = x + def w: Unit = + class D extends C[S] + println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = println("x") + +def main = + val y = new A[Char] {} diff --git a/tests/pos/specialized-trait-inlining-causes-implementation-required.scala b/tests/pos/specialized-trait-inlining-causes-implementation-required.scala new file mode 100644 index 000000000000..7860a420eba3 --- /dev/null +++ b/tests/pos/specialized-trait-inlining-causes-implementation-required.scala @@ -0,0 +1,51 @@ +//> using options -language:experimental.specializedTraits + +// A[Char] => A$sp$Char, A$impl$Char => C$sp$Char, +// but only once we inline the body of C$sp$Char do we realise that we need B$impl$Char as well. + +inline trait B[E: Specialized] + +inline trait C[S: Specialized]: + def v(x: S): S = x + def w: Unit = + val x = new B[S] {} + println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = println("x") + +def foo(x: B[Char]) = "A" + +def main = + val y = new A[Char] {} + +/* +inline trait B[E: Specialized] + +inline trait C[S: Specialized]: + def v(x: S): S + def w: Unit + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit + +inline trait A$sp$Char: + def x(y: C$sp$Char): Unit + +class A$impl$Char: + def x(y: C$sp$Char): Unit = println("x") + +inline trait B$sp$Char + +class B$impl$Char + +inline trait C$sp$Char: + def v(x: Char): Char + def w: Unit + +def foo(x: B$sp$Char) = "A" + +def main = + val y = new A$impl$Char() {} + +*/ diff --git a/tests/pos/specialized-trait-inlining-causes-specialization.scala b/tests/pos/specialized-trait-inlining-causes-specialization.scala new file mode 100644 index 000000000000..d99267f5b9fa --- /dev/null +++ b/tests/pos/specialized-trait-inlining-causes-specialization.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits + +inline trait D[R: Specialized] + +inline trait C[S: Specialized]: + def w(y: D[S]): Unit = println("w") + +inline trait A[T: Specialized]: + def x(y: C[T]): Unit = println("x") + +def main = + val b = new A[Char] {} diff --git a/tests/pos/specialized-trait-multiple-stages-specialized-numeric.scala b/tests/pos/specialized-trait-multiple-stages-specialized-numeric.scala new file mode 100644 index 000000000000..40211f2b8729 --- /dev/null +++ b/tests/pos/specialized-trait-multiple-stages-specialized-numeric.scala @@ -0,0 +1,22 @@ +//> using options -language:experimental.specializedTraits +// This shouldn't generate any boxing thanks to the specialized version of Numeric + +inline def foo[T: {Specialized, Numeric2}](x: T): T = + val num = summon[Numeric2[T]] + num.plus(x, num.fromInt(1)) + +inline trait A[T: {Numeric2, Specialized}]: + def bar(x: T): T = foo(x) + +class B extends A[Int]: + def baz(x: Int): Int = foo(x) + +inline trait Numeric2[T: Specialized]: + def fromInt(x: Int): T + def plus(x: T, y: T): T + def times(x: T, y: T): T + +implicit object IntIsIntegral extends Numeric2[Int]: + override def fromInt(x: Int): Int = x + override def plus(x: Int, y: Int): Int = x + y + override def times(x: Int, y: Int): Int = x * y diff --git a/tests/pos/specialized-trait-object-extends-specialized-trait.scala b/tests/pos/specialized-trait-object-extends-specialized-trait.scala new file mode 100644 index 000000000000..0ba1fa4a42e8 --- /dev/null +++ b/tests/pos/specialized-trait-object-extends-specialized-trait.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits + +inline trait MyInterface[T: Specialized]: + def fromInt(x: Int): T + def plus(x: T, y: T): T + +implicit object Implementation extends MyInterface[Int]: + override def fromInt(x: Int): Int = x + override def plus(x: Int, y: Int): Int = x + y diff --git a/tests/pos/specialized-trait-opaque-type.scala b/tests/pos/specialized-trait-opaque-type.scala new file mode 100644 index 000000000000..d82ec5e1aebd --- /dev/null +++ b/tests/pos/specialized-trait-opaque-type.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T](val x: T): + opaque type Special = T + + def getSpecial: Special = x + def eatSpecial(y: Special) = "Mmm, that was tasty!" + +class B extends A[Int](100) + +def foo = + val b = B() + println(b.eatSpecial(b.getSpecial)) diff --git a/tests/pos/specialized-trait-original-diamond.scala b/tests/pos/specialized-trait-original-diamond.scala new file mode 100644 index 000000000000..525f338ffe2f --- /dev/null +++ b/tests/pos/specialized-trait-original-diamond.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo = 10 + +inline trait B extends A[Int] +inline trait C extends A[Int] + +class Bar extends B with C diff --git a/tests/pos/specialized-trait-parameter-passing-as-named.scala b/tests/pos/specialized-trait-parameter-passing-as-named.scala new file mode 100644 index 000000000000..ac9cd596069b --- /dev/null +++ b/tests/pos/specialized-trait-parameter-passing-as-named.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits +inline trait A[T: Specialized](parameter: Int): + val x = parameter + +def main = + val y = new A[Int](parameter = 1000) {} diff --git a/tests/pos/specialized-trait-partial-complete-specialization-with-return-type.scala b/tests/pos/specialized-trait-partial-complete-specialization-with-return-type.scala new file mode 100644 index 000000000000..f2d1d71c051b --- /dev/null +++ b/tests/pos/specialized-trait-partial-complete-specialization-with-return-type.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized]: + def do_something() = println("Good morning") + +inline trait A[T: Specialized]: + def foo: Trait[T] + +inline trait B extends A[Int]: + override def foo = new Trait[Int] {} + +@main def Test = + val b = new B() {} + val f = b.foo + f.do_something() diff --git a/tests/pos/specialized-trait-partial-specialization-self-type.scala b/tests/pos/specialized-trait-partial-specialization-self-type.scala new file mode 100644 index 000000000000..2c7ddeb66579 --- /dev/null +++ b/tests/pos/specialized-trait-partial-specialization-self-type.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits + +trait T1 +trait T2 +trait T3 +trait Test1 +trait Test2 + +inline trait A[T: Specialized, S: Specialized, Z]: + this: T1 & T & S => + +inline trait B[W: Specialized] extends A[W, Test2, Int]: + this: T1 & W & Test2 => + +class Cl2 extends B[Test1] with Test1 with Test2 with T1 + diff --git a/tests/pos/specialized-trait-partial-specialization-with-argument-passing.scala b/tests/pos/specialized-trait-partial-specialization-with-argument-passing.scala new file mode 100644 index 000000000000..037943202c35 --- /dev/null +++ b/tests/pos/specialized-trait-partial-specialization-with-argument-passing.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized, S: Specialized] +inline trait Bar[T: Specialized] extends Foo[T, Int] +class Baz extends Bar[Boolean] + +inline def receiver[S: Specialized](x: Foo[S, Int]) = + println("Good Morning") + +val x = receiver(Baz()) diff --git a/tests/pos/specialized-trait-partial-specialization-with-body.scala b/tests/pos/specialized-trait-partial-specialization-with-body.scala new file mode 100644 index 000000000000..d6198764f358 --- /dev/null +++ b/tests/pos/specialized-trait-partial-specialization-with-body.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized, D: Specialized]: + def foo(x: T): T = x + def bar(x: D): D = x +inline trait B[S: Specialized] extends A[S, Int] +class C extends B[Char] diff --git a/tests/pos/specialized-trait-partial-specialization.scala b/tests/pos/specialized-trait-partial-specialization.scala new file mode 100644 index 000000000000..4b05eafe9db5 --- /dev/null +++ b/tests/pos/specialized-trait-partial-specialization.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +inline trait Map[K: Specialized, V: Specialized] +inline trait MapFromInt[V: Specialized] extends Map[Int, V] +inline trait MapToInt[K: Specialized] extends Map[K, Int] +inline trait MapIntInt1 extends MapFromInt[Int] +inline trait MapIntInt2 extends MapToInt[Int] +inline trait MapIntInt3 extends MapToInt[Int], MapFromInt[Int] diff --git a/tests/pos/specialized-trait-processing-order-matters.scala b/tests/pos/specialized-trait-processing-order-matters.scala new file mode 100644 index 000000000000..132652152c7e --- /dev/null +++ b/tests/pos/specialized-trait-processing-order-matters.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits + +// It's important to update the symbol infos for methods whose type interface changes due to specialization +// before we update any code that uses these symbols so that we type their uses' Apply nodes correctly. +// This only becomes clear when the user is in another object (and not a top-level main method) +// because otherwise the processing follows source code ordering. + +inline trait Trait[T: Specialized]: + def bar = "bar" + +def foo(v: Trait[Int]) = v + +object Test: + def main(args: Array[String]): Unit = + val a = new Trait[Int] {} + foo(a).bar diff --git a/tests/pos/specialized-trait-question-mark-non-specialized-allowed.scala b/tests/pos/specialized-trait-question-mark-non-specialized-allowed.scala new file mode 100644 index 000000000000..74aadecea4e4 --- /dev/null +++ b/tests/pos/specialized-trait-question-mark-non-specialized-allowed.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits +inline trait T3[T: Specialized, E, F: Numeric] +def foo(v: T3[Int, ?, ?]) = + println("HELLO") + +def main = + val x = new T3[Int, String, Float] {} + foo(x) diff --git a/tests/pos/specialized-trait-self-as-param.scala b/tests/pos/specialized-trait-self-as-param.scala new file mode 100644 index 000000000000..2dce27ad5c53 --- /dev/null +++ b/tests/pos/specialized-trait-self-as-param.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized](x: Trait[T] | Int): + def do_something(): Unit = + assert(Thread.currentThread.getStackTrace()(1).getClassName().contains("$impl$")) + if x.isInstanceOf[Trait[T]] then + x.asInstanceOf[Trait[T]].do_something() + +@main def Test = + val b = new Trait(new Trait(10) {}) {} + b.do_something() diff --git a/tests/pos/specialized-trait-self-type.scala b/tests/pos/specialized-trait-self-type.scala new file mode 100644 index 000000000000..4fc2105626ac --- /dev/null +++ b/tests/pos/specialized-trait-self-type.scala @@ -0,0 +1,28 @@ +//> using options -language:experimental.specializedTraits + +trait T1 +trait T2 +trait T3 +class Test + +inline trait A[T: Specialized]: + this: T1 => + +inline trait D extends A[Int]: + this: T1 => +inline trait E extends D: + this: T1 => + +inline trait B[T: Specialized]: + this: T2 & T1 => + +inline trait F extends A[Int], B[Int]: + this: T2 & T1 => + +inline trait C[T: Specialized]: + this: T => + +inline trait H extends C[Test]: + this: T3 & Test => + +class Cl2 extends Test with H with T3 diff --git a/tests/pos/specialized-trait-simplest-implicit-type-name.scala b/tests/pos/specialized-trait-simplest-implicit-type-name.scala new file mode 100644 index 000000000000..39ca98b6456f --- /dev/null +++ b/tests/pos/specialized-trait-simplest-implicit-type-name.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized](val x: T) +inline trait Bar[T: Specialized] + +@main def main = + val x = new Foo(10) {} // Type name not provided explicitly; this should still be specialized to Int. + val y = new Bar() {} // This will be specialized to Nothing. diff --git a/tests/pos/specialized-trait-simplest.scala b/tests/pos/specialized-trait-simplest.scala new file mode 100644 index 000000000000..7c230c58c2bd --- /dev/null +++ b/tests/pos/specialized-trait-simplest.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized] + +@main def main = + val x = new Foo[Int] {} diff --git a/tests/pos/specialized-trait-specialized-context-bound.scala b/tests/pos/specialized-trait-specialized-context-bound.scala new file mode 100644 index 000000000000..fb2022eaee63 --- /dev/null +++ b/tests/pos/specialized-trait-specialized-context-bound.scala @@ -0,0 +1,11 @@ +//> using options -language:experimental.specializedTraits + +inline trait Numeric2[T: Specialized] + +inline trait A[T: {Numeric2, Specialized}]: + def bar(x: T): T = x + +given Numeric2[Int] = new Numeric2[Int] {} + +class B extends A[Int]: + def baz(x: Int): Int = x diff --git a/tests/pos/specialized-trait-supercall-non-overridden.scala b/tests/pos/specialized-trait-supercall-non-overridden.scala new file mode 100644 index 000000000000..d4f8a9c46c5c --- /dev/null +++ b/tests/pos/specialized-trait-supercall-non-overridden.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits +inline trait A[T: Specialized]: + def foo = 10 + +inline trait B extends A[Int]: + def bar = super.foo diff --git a/tests/pos/specialized-trait-val-parameter.scala b/tests/pos/specialized-trait-val-parameter.scala new file mode 100644 index 000000000000..403561ec0794 --- /dev/null +++ b/tests/pos/specialized-trait-val-parameter.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits + +inline trait Vec[T: Specialized](val x: T) + +def main = + val v = new Vec[Int](10) {} diff --git a/tests/pos/specialized-trait-var-parameter.scala b/tests/pos/specialized-trait-var-parameter.scala new file mode 100644 index 000000000000..31207dd8d58e --- /dev/null +++ b/tests/pos/specialized-trait-var-parameter.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits + +inline trait Vec[T: Specialized](var x: T) + +def main = + val v = new Vec[Int](10) {} diff --git a/tests/pos/specialized-trait-very-specialized-list.scala b/tests/pos/specialized-trait-very-specialized-list.scala new file mode 100644 index 000000000000..7bef9c3786a0 --- /dev/null +++ b/tests/pos/specialized-trait-very-specialized-list.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits +inline trait Foo[T: Specialized] + +abstract class VerySpecializedList extends Seq[Foo[Int]] + +def main = + val x: VerySpecializedList = null + val y: Foo[Int] = x(1) diff --git a/tests/pos/specialized-trait-with-param.scala b/tests/pos/specialized-trait-with-param.scala new file mode 100644 index 000000000000..aa9cba4bf7c3 --- /dev/null +++ b/tests/pos/specialized-trait-with-param.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized](x: T): + def foo = x + +def f(b: Foo[Int]) = 37 + b.foo + +@main def main = + val x = new Foo[Int](42) {} + f(x) diff --git a/tests/pos/trait-extends-inline-trait.scala b/tests/pos/trait-extends-inline-trait.scala new file mode 100644 index 000000000000..cc418fe85464 --- /dev/null +++ b/tests/pos/trait-extends-inline-trait.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x = 1 +trait B extends A // This is fine as long as A has no parameters. diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index d88e96e9dd36..a22d88f7848b 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -96,6 +96,10 @@ val experimentalDefinitionInLibrary = Set( // New feature: Erased trait "scala.compiletime.Erased", + + // New feature: Specialized traits + "scala.specialize.Specialized", + "scala.specialize.Specialized$" ) diff --git a/tests/run/i3000b.check b/tests/run/i3000b.check index 605021c9b2c0..3684e1d2dc81 100644 --- a/tests/run/i3000b.check +++ b/tests/run/i3000b.check @@ -1,2 +1,2 @@ -Foo$$anon$1 -bar.Bar$$anon$2 +Foo$$anon$2 +bar.Bar$$anon$1 diff --git a/tests/run/inline-trait-body-lazy-val.scala b/tests/run/inline-trait-body-lazy-val.scala new file mode 100644 index 000000000000..d5b016ab6ed1 --- /dev/null +++ b/tests/run/inline-trait-body-lazy-val.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + lazy val x = + throw new Exception + 1 + +class B extends A + +@main def Test: Unit = + val b = B() \ No newline at end of file diff --git a/tests/run/inline-trait-body-override-def.scala b/tests/run/inline-trait-body-override-def.scala new file mode 100644 index 000000000000..d5a1de7b3124 --- /dev/null +++ b/tests/run/inline-trait-body-override-def.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def foo: Unit = throw Exception("I should not be run!") + +class B extends A: + override def foo: Unit = () + +@main def Test = + val b = B() + b.foo diff --git a/tests/run/inline-trait-body-override-val.check b/tests/run/inline-trait-body-override-val.check new file mode 100644 index 000000000000..0cfbf08886fc --- /dev/null +++ b/tests/run/inline-trait-body-override-val.check @@ -0,0 +1 @@ +2 diff --git a/tests/run/inline-trait-body-override-val.scala b/tests/run/inline-trait-body-override-val.scala new file mode 100644 index 000000000000..4205e0f28f4e --- /dev/null +++ b/tests/run/inline-trait-body-override-val.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + val x: Int = 1 + +class B extends A: + override val x = 2 + +@main def Test = + val b = B() + println(b.x) diff --git a/tests/run/inline-trait-body-statements.check b/tests/run/inline-trait-body-statements.check new file mode 100644 index 000000000000..73b256c35fd0 --- /dev/null +++ b/tests/run/inline-trait-body-statements.check @@ -0,0 +1,8 @@ +1 +foo +foo +1 +foo +bar +bar +bar diff --git a/tests/run/inline-trait-body-statements.scala b/tests/run/inline-trait-body-statements.scala new file mode 100644 index 000000000000..1a3a6ef593a1 --- /dev/null +++ b/tests/run/inline-trait-body-statements.scala @@ -0,0 +1,23 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T]: + var x = 1 + def foo = + if x == 1 then println("1") + x = 1 - x + println("foo") + + foo + foo + foo + +class B extends A[Int]: + def bar = + if x == 1 then println("1") + println("bar") + + bar + bar + bar + +@main def Test = + B() \ No newline at end of file diff --git a/tests/run/inline-trait-body-var.check b/tests/run/inline-trait-body-var.check new file mode 100644 index 000000000000..94ebaf900161 --- /dev/null +++ b/tests/run/inline-trait-body-var.check @@ -0,0 +1,4 @@ +1 +2 +3 +4 diff --git a/tests/run/inline-trait-body-var.scala b/tests/run/inline-trait-body-var.scala new file mode 100644 index 000000000000..ac1d1eb483c0 --- /dev/null +++ b/tests/run/inline-trait-body-var.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + var x = 1 + +class B extends A: + def f = + val old = x + x += 1 + old + +@main def Test = + val b = B() + println(b.f) + println(b.f) + println(b.f) + println(b.f) diff --git a/tests/run/inline-trait-child-and-grandchild-override.scala b/tests/run/inline-trait-child-and-grandchild-override.scala new file mode 100644 index 000000000000..8b976af0019c --- /dev/null +++ b/tests/run/inline-trait-child-and-grandchild-override.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.inlineTraits +inline trait GreatGrandParent: + val x = 10 +inline trait GrandParent extends GreatGrandParent: + override val x = 11 +inline trait Parent extends GrandParent: + override val x = 12 +class C extends Parent + +@main def Test = + val x = C() + assert(x.x == 12) diff --git a/tests/run/inline-trait-clash-method-method.scala b/tests/run/inline-trait-clash-method-method.scala new file mode 100644 index 000000000000..642f7c1ad292 --- /dev/null +++ b/tests/run/inline-trait-clash-method-method.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x(y: String) = "Hello world" + +class C extends A: + override def x(y: String) = "Hello world2" + +@main def Test = + val v = C() + assert(v.x("Hello World") == "Hello world2") diff --git a/tests/run/inline-trait-clash-method-val-param.scala b/tests/run/inline-trait-clash-method-val-param.scala new file mode 100644 index 000000000000..dbf87ff4ba9f --- /dev/null +++ b/tests/run/inline-trait-clash-method-val-param.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def x = "Hello world" + +class C extends A: + override val x = "Overridden" + +@main def Test = + val v = C() + assert(v.x == "Overridden") diff --git a/tests/run/inline-trait-clash-param-method.scala b/tests/run/inline-trait-clash-param-method.scala new file mode 100644 index 000000000000..048b0b3f92ae --- /dev/null +++ b/tests/run/inline-trait-clash-param-method.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits +inline trait A(x: Int) + +class C extends A(10): + def x = 1000 + +@main def Test = + val v = C() + assert(v.x == 1000) diff --git a/tests/run/inline-trait-clash-param-param.scala b/tests/run/inline-trait-clash-param-param.scala new file mode 100644 index 000000000000..fe24d9e0994f --- /dev/null +++ b/tests/run/inline-trait-clash-param-param.scala @@ -0,0 +1,11 @@ +//> using options -language:experimental.inlineTraits +// Param x in A will be renamed so it doesn't clash. + +inline trait A(x: Int) + +class C(x: String) extends A(10): + val y = x + +@main def Test = + val v = C("Hello World") + assert(v.y == "Hello World") diff --git a/tests/run/inline-trait-clash-param-val-param.scala b/tests/run/inline-trait-clash-param-val-param.scala new file mode 100644 index 000000000000..fd5968671cba --- /dev/null +++ b/tests/run/inline-trait-clash-param-val-param.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.inlineTraits +// Allowed. We rename A.x, A.y to avoid a clash. + +inline trait A(x: Int, y: Int) + +class C(val x: Int) extends A(10, 100): + val y: Int = 10 + +@main def Test = + val v = C(5) + assert(v.x == 5) + assert(v.y == 10) diff --git a/tests/run/inline-trait-clash-param-var-param.scala b/tests/run/inline-trait-clash-param-var-param.scala new file mode 100644 index 000000000000..cd5c1f70f3d3 --- /dev/null +++ b/tests/run/inline-trait-clash-param-var-param.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.inlineTraits +// Allowed. We rename A.x, A.y to avoid a clash. + +inline trait A(x: Int, y: Int) + +class C(var x: Int) extends A(10, 100): + val y: Int = 10 + +@main def Test = + val v = C(5) + assert(v.x == 5) + assert(v.y == 10) diff --git a/tests/run/inline-trait-clash-val-param-val-param.scala b/tests/run/inline-trait-clash-val-param-val-param.scala new file mode 100644 index 000000000000..b4157d97050f --- /dev/null +++ b/tests/run/inline-trait-clash-val-param-val-param.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits +inline trait A(val x: Int, val y: Int) +class C(override val y: Int) extends A(10, 54): + override val x = 1000 + +@main def Test = + val v = C(44) + assert(v.x == 1000) + assert(v.y == 44) diff --git a/tests/run/inline-trait-clashing-parent-params.scala b/tests/run/inline-trait-clashing-parent-params.scala new file mode 100644 index 000000000000..91d901574cd8 --- /dev/null +++ b/tests/run/inline-trait-clashing-parent-params.scala @@ -0,0 +1,15 @@ +//> using options -language:experimental.inlineTraits +// These params should be renamed (as they are private) so no clash + +inline trait A(x: Int): + val y = x + +inline trait B(x: Int): + val z = x + +class C extends A(10), B(11) + +@main def Test = + val v = C() + assert(v.y == 10) + assert(v.z == 11) diff --git a/tests/run/inline-trait-enclosing-super-call.scala b/tests/run/inline-trait-enclosing-super-call.scala new file mode 100644 index 000000000000..b3f3ae347313 --- /dev/null +++ b/tests/run/inline-trait-enclosing-super-call.scala @@ -0,0 +1,15 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def bar = "A" + +class E: + def bar = "E" + +class D extends E: + class C extends A: + def foo = D.super.bar // We shouldn't touch this super call when inlining A + +@main def Test = + val d = D() + val c = d.C() + assert(c.foo == "E") diff --git a/tests/run/inline-trait-enclosing-supercall-in-inline-trait.scala b/tests/run/inline-trait-enclosing-supercall-in-inline-trait.scala new file mode 100644 index 000000000000..2da21512c4ed --- /dev/null +++ b/tests/run/inline-trait-enclosing-supercall-in-inline-trait.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.inlineTraits +trait A: + def foo = 10 + +class B extends A: + inline trait T: + def foo = B.super.foo + + class C2 extends T + +@main def Test = + val x = new B() + val y = new x.C2() + assert(y.foo == 10) diff --git a/tests/run/inline-trait-indirect-with-params.scala b/tests/run/inline-trait-indirect-with-params.scala new file mode 100644 index 000000000000..76de257afd29 --- /dev/null +++ b/tests/run/inline-trait-indirect-with-params.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](x: T): + val y = x + def foo() = x +inline trait B extends A[Int] +class C extends A[Int](10), B + +object Test: + def main(args: Array[String]): Unit = + val z: B = new C + assert(z.foo() == 10) + assert(z.y == 10) \ No newline at end of file diff --git a/tests/run/inline-trait-inheritance-diamond-simple-trait.check b/tests/run/inline-trait-inheritance-diamond-simple-trait.check new file mode 100644 index 000000000000..adbdca39e3a4 --- /dev/null +++ b/tests/run/inline-trait-inheritance-diamond-simple-trait.check @@ -0,0 +1,4 @@ +1 +2 +999 +4 diff --git a/tests/run/inline-trait-inheritance-diamond-simple-trait.scala b/tests/run/inline-trait-inheritance-diamond-simple-trait.scala new file mode 100644 index 000000000000..1746050824e4 --- /dev/null +++ b/tests/run/inline-trait-inheritance-diamond-simple-trait.scala @@ -0,0 +1,23 @@ +//> using options -language:experimental.inlineTraits +trait TGP[T]: + def i: T + def f(x: T): T = x + +inline trait IT1[T](x: T) extends TGP[T]: + override def i: T = x + +inline trait IT2 extends TGP[Int]: + override def i: Int = 999 + def j: String = "inline" + def g(x: Int): String = x.toString() + +trait T extends TGP[Int] + +class C1 extends T, IT1[Int](1) +class C2 extends IT1[Int](2), T +class C3 extends IT1[Int](3), IT2 +class C4 extends IT2, IT1[Int](4) + +@main def Test: Unit = + for c <- List(C1(), C2(), C3(), C4()) + do println(c.i) \ No newline at end of file diff --git a/tests/run/inline-trait-inheritance-inline-ancestors/inlinetraits.scala b/tests/run/inline-trait-inheritance-inline-ancestors/inlinetraits.scala new file mode 100644 index 000000000000..f7677f520a10 --- /dev/null +++ b/tests/run/inline-trait-inheritance-inline-ancestors/inlinetraits.scala @@ -0,0 +1,48 @@ +//> using options -language:experimental.inlineTraits +package inlinetraits + +val inlineValues: List[Int] = + val c = C() + List(c.zero, c.eleven, c.twelve, c.thirteen, c.twentyOne, c.twentyTwo, c.thirty) + +inline trait T0: + def zero: Int = 0 + def eleven: Int = 0 + def twelve: Int = 0 + def twentyOne: Int = 0 + def thirteen: Int = 0 + def twentyTwo: Int = 0 + def thirty: Int = 0 + +inline trait T11 extends T0: + override def eleven: Int = 11 + override def twelve: Int = 11 + override def twentyOne: Int = 11 + override def thirteen: Int = 11 + override def twentyTwo: Int = 11 + override def thirty: Int = 11 + +inline trait T12 extends T0: + override def twelve: Int = 12 + override def twentyOne: Int = 12 + override def thirteen: Int = 12 + override def twentyTwo: Int = 12 + override def thirty: Int = 12 + +inline trait T21 extends T11, T12: + override def twentyOne: Int = 21 + override def thirteen: Int = 21 + override def twentyTwo: Int = 21 + override def thirty: Int = 21 + +inline trait T13 extends T0: + override def thirteen: Int = 13 + override def twentyTwo: Int = 13 + override def thirty: Int = 13 + +inline trait T22 extends T12, T13: + override def twentyTwo: Int = 22 + override def thirty: Int = 22 + +class C extends T21, T22: + override def thirty: Int = 30 diff --git a/tests/run/inline-trait-inheritance-inline-ancestors/normaltraits.scala b/tests/run/inline-trait-inheritance-inline-ancestors/normaltraits.scala new file mode 100644 index 000000000000..ba128145c793 --- /dev/null +++ b/tests/run/inline-trait-inheritance-inline-ancestors/normaltraits.scala @@ -0,0 +1,47 @@ +package normaltraits + +val normalValues: List[Int] = + val c = C() + List(c.zero, c.eleven, c.twelve, c.thirteen, c.twentyOne, c.twentyTwo, c.thirty) + +trait T0: + def zero: Int = 0 + def eleven: Int = 0 + def twelve: Int = 0 + def twentyOne: Int = 0 + def thirteen: Int = 0 + def twentyTwo: Int = 0 + def thirty: Int = 0 + +trait T11 extends T0: + override def eleven: Int = 11 + override def twelve: Int = 11 + override def twentyOne: Int = 11 + override def thirteen: Int = 11 + override def twentyTwo: Int = 11 + override def thirty: Int = 11 + +trait T12 extends T0: + override def twelve: Int = 12 + override def twentyOne: Int = 12 + override def thirteen: Int = 12 + override def twentyTwo: Int = 12 + override def thirty: Int = 12 + +trait T21 extends T11, T12: + override def twentyOne: Int = 21 + override def thirteen: Int = 21 + override def twentyTwo: Int = 21 + override def thirty: Int = 21 + +trait T13 extends T0: + override def thirteen: Int = 13 + override def twentyTwo: Int = 13 + override def thirty: Int = 13 + +trait T22 extends T12, T13: + override def twentyTwo: Int = 22 + override def thirty: Int = 22 + +class C extends T21, T22: + override def thirty: Int = 30 \ No newline at end of file diff --git a/tests/run/inline-trait-inheritance-inline-ancestors/test.scala b/tests/run/inline-trait-inheritance-inline-ancestors/test.scala new file mode 100644 index 000000000000..5f7a9be7a41b --- /dev/null +++ b/tests/run/inline-trait-inheritance-inline-ancestors/test.scala @@ -0,0 +1,5 @@ +import normaltraits.normalValues +import inlinetraits.inlineValues + +@main def Test = + assert(normalValues == inlineValues) \ No newline at end of file diff --git a/tests/run/inline-trait-inheritance-inline-grandparent.check b/tests/run/inline-trait-inheritance-inline-grandparent.check new file mode 100644 index 000000000000..02a3fcae5b4e --- /dev/null +++ b/tests/run/inline-trait-inheritance-inline-grandparent.check @@ -0,0 +1,6 @@ +0 +(Test SimpleC,Hello) + +5678 +(Test C,Hello,9) +5678 diff --git a/tests/run/inline-trait-inheritance-inline-grandparent.scala b/tests/run/inline-trait-inheritance-inline-grandparent.scala new file mode 100644 index 000000000000..8bd8fb33fb0e --- /dev/null +++ b/tests/run/inline-trait-inheritance-inline-grandparent.scala @@ -0,0 +1,34 @@ +//> using options -language:experimental.inlineTraits +package simpleGrandParent: + inline trait SimpleGrandParent[T]: + def foo(): Int = 0 + def foooo(): T = ??? + + inline trait SimpleParent[T, U](x: T, z: U) extends SimpleGrandParent[U]: + def bar(a: T) = (a, x) + + class SimpleC extends SimpleParent("Hello", 1234) + +package grandParentWithArgs: + inline trait GrandParent[T](val x: T, val y: T): + def foo(): T = x + def foooo(): T = y + + inline trait Parent[T, U](w: T, z: U) extends GrandParent[U]: + def bar(a: T) = (a, w, y) + + class C extends Parent("Hello", 1234), GrandParent(5678, 9) + +@main def Test = + import simpleGrandParent.SimpleC + import grandParentWithArgs.C + + val simpleC = SimpleC() + println(simpleC.foo()) + println(simpleC.bar("Test SimpleC")) + println() + + val c = C() + println(c.foo()) + println(c.bar("Test C")) + println(c.x) \ No newline at end of file diff --git a/tests/run/inline-trait-maximum-name-clash.scala b/tests/run/inline-trait-maximum-name-clash.scala new file mode 100644 index 000000000000..cc6788360529 --- /dev/null +++ b/tests/run/inline-trait-maximum-name-clash.scala @@ -0,0 +1,18 @@ +//> using options -language:experimental.inlineTraits +inline trait A(x: Int): + val z = x +inline trait B(x: Int): + val y = x +inline trait C(val x: Int) + +class D extends A(314), B(1200), C(12): + override val x = 1 + override val y = x + val w = y + +@main def Test = + val v = D() + assert(v.y == 1) + assert(v.w == 1) + assert(v.z == 314) + assert(v.x == 1) diff --git a/tests/run/inline-trait-nested.scala b/tests/run/inline-trait-nested.scala new file mode 100644 index 000000000000..9d17dfe41672 --- /dev/null +++ b/tests/run/inline-trait-nested.scala @@ -0,0 +1,17 @@ +//> using options -language:experimental.inlineTraits +// While we don't allow inner nested classes inside inline traits, we do allow creation of anonymous classes inside methods +// inside inline traits - after all these are just ordinary methods. + +inline trait Trait[T]: + def do_something() = println("Good morning") + +inline trait A[T]: + def foo: Trait[T] + +inline trait B extends A[Int]: + override def foo = new Trait[Int] {} + +@main def Test = + val b = new B() {} + val f = b.foo + f.do_something() diff --git a/tests/run/inline-trait-non-inline-blocks-inlining.scala b/tests/run/inline-trait-non-inline-blocks-inlining.scala new file mode 100644 index 000000000000..0a4bbc0ac342 --- /dev/null +++ b/tests/run/inline-trait-non-inline-blocks-inlining.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.inlineTraits +inline trait Foo: + def problem = + Thread.currentThread.getStackTrace()(1).getClassName() + +trait Bar extends Foo + +inline trait Confounder + +class Baz extends Bar, Confounder + +@main def Test = + val baz = Baz() + assert(baz.problem == "Baz") diff --git a/tests/run/inline-trait-override-in-parent-triangle.scala b/tests/run/inline-trait-override-in-parent-triangle.scala new file mode 100644 index 000000000000..f7aa57dea92b --- /dev/null +++ b/tests/run/inline-trait-override-in-parent-triangle.scala @@ -0,0 +1,15 @@ +//> using options -language:experimental.inlineTraits +inline trait Foo: + def foo = "Foo" + +inline trait Bar extends Foo: + override def foo = "Bar" + +class C extends Bar, Foo +class D extends Foo, Bar + +@main def Test = + val c = C() + val d = D() + assert(c.foo == "Bar") + assert(d.foo == "Bar") diff --git a/tests/run/inline-trait-param-shadows-parent-indirect.scala b/tests/run/inline-trait-param-shadows-parent-indirect.scala new file mode 100644 index 000000000000..3a3d83164ad8 --- /dev/null +++ b/tests/run/inline-trait-param-shadows-parent-indirect.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.inlineTraits +inline trait A[T](x: T): + def y = x + +inline trait B extends A[Int] +inline trait D extends A[Int] + +inline trait E extends B +inline trait F extends D + +class C extends E, F, A[Int](100) + +object Test: + def main(args: Array[String]): Unit = + val z = new C + assert(z.y == 100) diff --git a/tests/run/inline-trait-signature-parameters-val-block.check b/tests/run/inline-trait-signature-parameters-val-block.check new file mode 100644 index 000000000000..063724ea6c43 --- /dev/null +++ b/tests/run/inline-trait-signature-parameters-val-block.check @@ -0,0 +1,4 @@ +I am a B! +1 +1 +1 diff --git a/tests/run/inline-trait-signature-parameters-val-block.scala b/tests/run/inline-trait-signature-parameters-val-block.scala new file mode 100644 index 000000000000..e0fb9a73458c --- /dev/null +++ b/tests/run/inline-trait-signature-parameters-val-block.scala @@ -0,0 +1,11 @@ +//> using options -language:experimental.inlineTraits +inline trait A(val x: Int) + +class B extends A({ println("I am a B!"); 1 }) + +@main() def Test: Unit = { + val b = B() + println(b.x) + println(b.x) + println(b.x) +} \ No newline at end of file diff --git a/tests/run/inline-trait-signature-side-effects-1.check b/tests/run/inline-trait-signature-side-effects-1.check new file mode 100644 index 000000000000..4df3c9a85d7f --- /dev/null +++ b/tests/run/inline-trait-signature-side-effects-1.check @@ -0,0 +1,6 @@ +0 +0 +1 +0 +1 +2 diff --git a/tests/run/inline-trait-signature-side-effects-1.scala b/tests/run/inline-trait-signature-side-effects-1.scala new file mode 100644 index 000000000000..198b89472794 --- /dev/null +++ b/tests/run/inline-trait-signature-side-effects-1.scala @@ -0,0 +1,28 @@ +//> using options -language:experimental.inlineTraits +inline trait A(i: Int): + val x = i + val y = i + +class B(i: Int) extends A(i) + +@main def Test = + var c = 0 + + val b1 = new B({ + for i <- 0 to c + do println(i) + c += 1 + c + }) + val b2 = new B({ + for i <- 0 to c + do println(i) + c += 1 + c + }) + val b3 = new B({ + for i <- 0 to c + do println(i) + c += 1 + c + }) \ No newline at end of file diff --git a/tests/run/inline-trait-signature-side-effects-2.check b/tests/run/inline-trait-signature-side-effects-2.check new file mode 100644 index 000000000000..4df3c9a85d7f --- /dev/null +++ b/tests/run/inline-trait-signature-side-effects-2.check @@ -0,0 +1,6 @@ +0 +0 +1 +0 +1 +2 diff --git a/tests/run/inline-trait-signature-side-effects-2.scala b/tests/run/inline-trait-signature-side-effects-2.scala new file mode 100644 index 000000000000..e9d3299a3776 --- /dev/null +++ b/tests/run/inline-trait-signature-side-effects-2.scala @@ -0,0 +1,17 @@ +//> using options -language:experimental.inlineTraits +inline trait A(i: Int): + val x = i + val y = i + +class B(i: Int) extends A(i) + +@main def Test = + var c = 0 + + for _ <- 0 until 3 + do new B({ + for i <- 0 to c + do println(i) + c += 1 + c + }) \ No newline at end of file diff --git a/tests/run/inline-trait-specialized-desugar.scala b/tests/run/inline-trait-specialized-desugar.scala new file mode 100644 index 000000000000..3aca391598f6 --- /dev/null +++ b/tests/run/inline-trait-specialized-desugar.scala @@ -0,0 +1,45 @@ +//> using options -language:experimental.specializedTraits + +// User code does this: (with Specialized type class) +inline trait Iterator[T]: + def hasNext: Boolean + def next(): T + +// User code does this: (with Specialized type class) +inline trait ArrayIterator[T](elems: Array[T]) extends Iterator[T]: + private var current: Int = 0 + def hasNext: Boolean = current < elems.length + def next(): T = try elems(current) finally current += 1 + + +// Specialized traits generates these signatures: +inline trait Iterator_sp_Int extends Iterator[Int] +inline trait ArrayIterator_sp_Int extends ArrayIterator[Int], Iterator_sp_Int +class ArrayIterator_impl_Int(elems: Array[Int]) extends ArrayIterator_sp_Int, ArrayIterator[Int](elems) + +// User code does this: +def foo(x: ArrayIterator[Int]): Int = x.next() + +// Specialized traits converts this to +def foo(x: ArrayIterator_sp_Int): Int = x.next() + +// User code does this: +/* class MyClassA + class MyClassB extends MyClassA, ArrayIterator[Int](Array.from(Seq(1, 5))) */ + +// We convert this to: +class MyClassA +class MyClassB extends MyClassA, ArrayIterator_sp_Int, ArrayIterator[Int](Array.from(Seq(1, 5))) + +@main def Test = + val xs: Array[Int] = Array(1, 2, 3) + + // User code does this: + /* val ai = new ArrayIterator[Int](xs) {} */ + + // We convert this to: + val ai = ArrayIterator_impl_Int(xs) + + val mcb = MyClassB() + assert(mcb.hasNext) + assert(ai.next() == 1) diff --git a/tests/run/inline-trait-super-call.scala b/tests/run/inline-trait-super-call.scala new file mode 100644 index 000000000000..4db690f2fdaf --- /dev/null +++ b/tests/run/inline-trait-super-call.scala @@ -0,0 +1,25 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def foo = "A" + def bar = "bar" + +inline trait B: + def foo = "B" + +class C extends A, B: + override def foo = super.foo + +class D extends A, B: + override def foo = super[A].foo + +class E extends A, B: + override def foo = super[B].foo + def baz = super[A].bar // No override; this also needs to work + +@main def Test: Unit = + val c = C() + assert(c.foo == "B") + val d = D() + assert(d.foo == "A") + val e = E() + assert(e.foo == "B") diff --git a/tests/run/inline-trait-super-chain.scala b/tests/run/inline-trait-super-chain.scala new file mode 100644 index 000000000000..243d6e251a83 --- /dev/null +++ b/tests/run/inline-trait-super-chain.scala @@ -0,0 +1,24 @@ +//> using options -language:experimental.inlineTraits +inline trait A: + def foo = "A" + +inline trait B extends A: + override def foo = super.foo + +inline trait C extends B: + override def foo = "B" + +inline trait D extends C: + override def foo = super.foo + +inline trait E extends D: + override def foo = super.foo + +inline trait F extends E: + override def foo = super.foo + +class C1 extends F + +@main def Test = + val cl = C1() + assert(cl.foo == "B") diff --git a/tests/run/inline-trait-varargs.scala b/tests/run/inline-trait-varargs.scala new file mode 100644 index 000000000000..bab883ddc17b --- /dev/null +++ b/tests/run/inline-trait-varargs.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.inlineTraits + +inline trait ListA[T](vals: T*): + def printVals() = + vals.foreach(println(_)) + +@main def Test = + val x = new ListA[Short](4, 6, 7, 8, 10, 5, 11, 100) {} + x.printVals() diff --git a/tests/run/specialized-trait-all-general-raw.scala b/tests/run/specialized-trait-all-general-raw.scala new file mode 100644 index 000000000000..f63bdc6d2565 --- /dev/null +++ b/tests/run/specialized-trait-all-general-raw.scala @@ -0,0 +1,21 @@ +//> using options -language:experimental.specializedTraits +inline trait Foo[A: Specialized, B: Specialized, C: Specialized, D: Specialized, E: Specialized]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +inline trait Bar[A: Specialized, B, E]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +inline trait Baz[A: Specialized]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +@main def Test = + val foo = new Foo[Any, AnyVal, Object, AnyRef, Nothing]() {} + println(foo.foo) + assert(foo.foo == "Foo$impl") + + val bar = new Bar[AnyVal, Int, String]() {} + assert(bar.foo == "Bar$impl") + + val baz = new Baz[(Int, String)]() {} + + assert(baz.foo == "Baz$impl") diff --git a/tests/run/specialized-trait-argument-specializes-current-trait.scala b/tests/run/specialized-trait-argument-specializes-current-trait.scala new file mode 100644 index 000000000000..b3e067793f2c --- /dev/null +++ b/tests/run/specialized-trait-argument-specializes-current-trait.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits + +inline trait Vec[T: Specialized](elems: Array[T]): + def length = elems.length + + def apply(i: Int): T = elems(i) + + def lengthOfOtherVector(other: Vec[Int]): Int = + other.length + +object Test: + def main(args: Array[String]) = + val x = new Vec[Int](Array(1, 2, 3, 4, 5)) {} + val y = new Vec[Int](Array(3, 4, 5, 6, 7, 9)) {} + assert(x.lengthOfOtherVector(y) == 6) + assert(y.lengthOfOtherVector(x) == 5) diff --git a/tests/run/specialized-trait-array-of-specialized-trait-classtag.scala b/tests/run/specialized-trait-array-of-specialized-trait-classtag.scala new file mode 100644 index 000000000000..9a8a4a271488 --- /dev/null +++ b/tests/run/specialized-trait-array-of-specialized-trait-classtag.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized]: + val x = 10 + +@main def Test = + val xs: Array[Foo[Int]] = Array.tabulate(10)(_ => new Foo[Int] {}) + println(xs.head.x) diff --git a/tests/run/specialized-trait-as-parameter.scala b/tests/run/specialized-trait-as-parameter.scala new file mode 100644 index 000000000000..ac5eb130a89b --- /dev/null +++ b/tests/run/specialized-trait-as-parameter.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized]: + def do_something() = println("Good morning") + +inline trait A[T: Specialized]: + def foo(x: Trait[T]) = x + +@main def Test = + val b = new A[Long]() {} + b.foo(new Trait[Long] {}).do_something() diff --git a/tests/run/specialized-trait-as-return-type.scala b/tests/run/specialized-trait-as-return-type.scala new file mode 100644 index 000000000000..2307b766eb0b --- /dev/null +++ b/tests/run/specialized-trait-as-return-type.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized]: + def do_something() = println("Good morning") + +inline trait A[T: Specialized]: + def foo: Trait[T] + +inline trait B[S: Specialized] extends A[S]: + override def foo = new Trait[S] {} + +@main def Test = + val b = new B[Int]() {} + val f = b.foo + f.do_something() diff --git a/tests/run/specialized-trait-as-val.scala b/tests/run/specialized-trait-as-val.scala new file mode 100644 index 000000000000..dcbc06aa2585 --- /dev/null +++ b/tests/run/specialized-trait-as-val.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized]: + def do_something() = println("Good morning") + +inline trait A[T: Specialized]: + val t = new Trait[T] {} + +@main def Test = + val b = new A[Int]() {} + b.t.do_something() diff --git a/tests/run/specialized-trait-body-macro-uses-type-param/Macro_1.scala b/tests/run/specialized-trait-body-macro-uses-type-param/Macro_1.scala new file mode 100644 index 000000000000..593623d38800 --- /dev/null +++ b/tests/run/specialized-trait-body-macro-uses-type-param/Macro_1.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits +import scala.quoted.* + +inline def describe[T]: String = ${ describeImpl[T] } + +def describeImpl[T: Type](using Quotes): Expr[String] = + Expr(Type.show[T]) + +inline trait A[T: Specialized]: + val name: String = describe[T] diff --git a/tests/run/specialized-trait-body-macro-uses-type-param/Test_2.scala b/tests/run/specialized-trait-body-macro-uses-type-param/Test_2.scala new file mode 100644 index 000000000000..6d122f4d491e --- /dev/null +++ b/tests/run/specialized-trait-body-macro-uses-type-param/Test_2.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +class B extends A[Int] +class C extends A[String] + +@main def Test = + assert(B().name == "scala.Int", B().name) + assert(C().name == "scala.Predef.String", C().name) diff --git a/tests/run/specialized-trait-call-method-in-other-file/A_1.scala b/tests/run/specialized-trait-call-method-in-other-file/A_1.scala new file mode 100644 index 000000000000..762bd6017223 --- /dev/null +++ b/tests/run/specialized-trait-call-method-in-other-file/A_1.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized](val x: T): + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +object Methods: + def foo(y: Foo[Int]) = + assert(y.foo == "Foo$impl$scala$Int") + y.x + + def bar(y: Foo[Boolean]) = + assert(y.foo == "Bar") + y.x diff --git a/tests/run/specialized-trait-call-method-in-other-file/B_2.scala b/tests/run/specialized-trait-call-method-in-other-file/B_2.scala new file mode 100644 index 000000000000..acdd2d2305f1 --- /dev/null +++ b/tests/run/specialized-trait-call-method-in-other-file/B_2.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits + +class Bar extends Foo[Boolean](true) + +@main def Test = + val x = new Foo[Int](100) {} + val y = Bar() + + assert(Methods.foo(x) == 100) + assert(Methods.bar(y) == true) diff --git a/tests/run/specialized-trait-check-bridge-exists-hidden-by-inline.scala b/tests/run/specialized-trait-check-bridge-exists-hidden-by-inline.scala new file mode 100644 index 000000000000..76f7241fa3a1 --- /dev/null +++ b/tests/run/specialized-trait-check-bridge-exists-hidden-by-inline.scala @@ -0,0 +1,19 @@ +// scalajs: --skip +// (getName() reflection is not supported in ScalaJS) + +//> using options -language:experimental.specializedTraits +inline trait Trait[T: Specialized]: + def do_something() = println("Good morning") + +inline trait A[T: Specialized]: + def foo(x: Trait[T]): Unit + +inline trait B extends A[Int]: + override def foo(x: Trait[Int]): Unit = x.do_something() + +class C extends B // Should have a bridge for the specialized foo + +@main def Test = + val fooMethods = classOf[C].getDeclaredMethods.filter(_.getName == "foo") + assert(fooMethods.length == 2, s"expected 2 `foo` methods on Bar (real + bridge), found ${fooMethods.length}: ${fooMethods.mkString(", ")}") + diff --git a/tests/run/specialized-trait-check-bridge-exists.scala b/tests/run/specialized-trait-check-bridge-exists.scala new file mode 100644 index 000000000000..eb8a2d27ebd1 --- /dev/null +++ b/tests/run/specialized-trait-check-bridge-exists.scala @@ -0,0 +1,16 @@ +// scalajs: --skip +// (getName() reflection is not supported in ScalaJS) + +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized]: + def foo(x: Foo[T]): Foo[T] + +class Bar extends Foo[Int]: + override def foo(x: Foo[Int]): Foo[Int] = x + +@main def Test = + val x = Bar() + val fooMethods = classOf[Bar].getDeclaredMethods.filter(_.getName == "foo") + assert(fooMethods.length == 2, s"expected 2 `foo` methods on Bar (real + bridge), found ${fooMethods.length}: ${fooMethods.mkString(", ")}") + diff --git a/tests/run/specialized-trait-check-specialized-method-called.scala b/tests/run/specialized-trait-check-specialized-method-called.scala new file mode 100644 index 000000000000..16f2b0714016 --- /dev/null +++ b/tests/run/specialized-trait-check-specialized-method-called.scala @@ -0,0 +1,26 @@ +//> using options -language:experimental.specializedTraits + +// Check we actually call the specialized method that we should be calling +// We can't easily check the return type, but we can check that we are calling a method +// on Foo$impl$Int$ directly, with no bridge methods in between (this means it's the correct +// method without boxing / unboxing). + +inline trait Foo[T: Specialized](x: T): + def foo = + val stackTrace = Thread.currentThread.getStackTrace() + + // No bridge methods; we call foo directly + assert(stackTrace.toList.tail.takeWhile(call => call.getMethodName().startsWith("foo")).length == 1) + + // We call this method on the correct impl class + assert(Thread.currentThread.getStackTrace()(1).getClassName() == "Foo$impl$scala$Int") + x + +def f(b: Foo[Int]) = + 37 + b.foo + +object Test: + def main(args: Array[String]): Unit = { + val x = new Foo[Int](42) {} + f(x) + } diff --git a/tests/run/specialized-trait-class-extends-specialized-trait-check-specialized.scala b/tests/run/specialized-trait-class-extends-specialized-trait-check-specialized.scala new file mode 100644 index 000000000000..02c470bdd27a --- /dev/null +++ b/tests/run/specialized-trait-class-extends-specialized-trait-check-specialized.scala @@ -0,0 +1,22 @@ +// scalajs: --skip +// (getInterfaces() reflection is not supported in ScalaJS) + +//> using options -language:experimental.specializedTraits +inline trait Foo[T: Specialized](x: T): + def foo = x + +class Bar extends Foo[Int](10): + def myMethod = "Hello I am a method" + +class Bar2 extends Foo(10): + def myMethod = "Hello I am a method" + +@main def Test = + val y = new Foo(10) {} + val x = Bar() + val traits = classOf[Bar].getInterfaces() + + assert(traits.exists(cl => cl.getName() == "Foo$$sp$scala$Int")) + + val traits2 = classOf[Bar2].getInterfaces() + assert(traits2.exists(cl => cl.getName() == "Foo$$sp$scala$Int")) diff --git a/tests/run/specialized-trait-collections-example.scala b/tests/run/specialized-trait-collections-example.scala new file mode 100644 index 000000000000..3f7e5d7bb099 --- /dev/null +++ b/tests/run/specialized-trait-collections-example.scala @@ -0,0 +1,28 @@ +//> using options -language:experimental.specializedTraits + +inline trait Iterator[T: Specialized]: + def hasNext: Boolean + def next(): T + +inline trait ArrayIterator[T: Specialized](elems: Array[T]) extends Iterator[T]: + private var current = 0 + def hasNext: Boolean = current < elems.length + def next(): T = try elems(current) finally current += 1 + +inline trait Iterable[T: Specialized]: + def iterator: Iterator[T] + def forall(f: T => Unit): Unit = + val it = iterator + while it.hasNext do f(it.next()) + +inline trait Seq[T: Specialized](elems: Array[T]) extends Iterable[T]: + def length: Int = elems.length + def apply(i: Int): T = elems(i) + def iterator: Iterator[T] = new ArrayIterator[T](elems) {} + +@main def Test = + val elems = Array.from(scala.collection.immutable.Seq(1, 2, 3, 4, 5)) + val seq = new Seq[Int](elems) {} + var x = 0 + seq.forall(v => x += v) + assert(x == 15) diff --git a/tests/run/specialized-trait-diamond/A_1.scala b/tests/run/specialized-trait-diamond/A_1.scala new file mode 100644 index 000000000000..f7d8db67febf --- /dev/null +++ b/tests/run/specialized-trait-diamond/A_1.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.specializedTraits + +// Lib1 and Lib2 both specialize A, can we share their specializations? +// At the moment we don't. Do we still avoid symbol collisions? + +inline trait A[T: Specialized]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() diff --git a/tests/run/specialized-trait-diamond/B_2.scala b/tests/run/specialized-trait-diamond/B_2.scala new file mode 100644 index 000000000000..7e8ebb7f03e0 --- /dev/null +++ b/tests/run/specialized-trait-diamond/B_2.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +object B: + val a = new A[Int]() {} \ No newline at end of file diff --git a/tests/run/specialized-trait-diamond/C_3.scala b/tests/run/specialized-trait-diamond/C_3.scala new file mode 100644 index 000000000000..49d99c8c73da --- /dev/null +++ b/tests/run/specialized-trait-diamond/C_3.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +object C: + val a = new A[Int]() {} \ No newline at end of file diff --git a/tests/run/specialized-trait-diamond/D_4.scala b/tests/run/specialized-trait-diamond/D_4.scala new file mode 100644 index 000000000000..7561c8d2f064 --- /dev/null +++ b/tests/run/specialized-trait-diamond/D_4.scala @@ -0,0 +1,5 @@ +//> using options -language:experimental.specializedTraits + +@main def Test = + assert(B.a.foo == "A$impl$scala$Int") + assert(C.a.foo == "A$impl$scala$Int") diff --git a/tests/run/specialized-trait-erasure-impl-classes.scala b/tests/run/specialized-trait-erasure-impl-classes.scala new file mode 100644 index 000000000000..1b850f0aad3f --- /dev/null +++ b/tests/run/specialized-trait-erasure-impl-classes.scala @@ -0,0 +1,50 @@ +//> using options -language:experimental.specializedTraits + +class Animal +class BigCat extends Animal +class Lion extends BigCat + +inline trait A[T: Specialized]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +abstract class methods: + // Specialization takes place + def bar1(a: A[Int]): Unit = assert(a.foo == "A$impl$scala$Int", a.foo) + def bar2(a: A[String]): Unit = assert(a.foo == "A$impl$java$lang$String", a.foo) + def bar3(a: A[Lion]): Unit = assert(a.foo == "A$impl$Animal", a.foo) + def bar4(a: A[BigCat]): Unit = assert(a.foo == "A$impl$Animal", a.foo) + def bar5(a: A[Animal]): Unit = assert(a.foo == "A$impl$Animal", a.foo) + def bar11(a: A[Boolean]): Unit = assert(a.foo == "A$impl$scala$Boolean", a.foo) + def bar12(a: A[Byte]): Unit = assert(a.foo == "A$impl$scala$Byte", a.foo) + def bar13(a: A[Short]): Unit = assert(a.foo == "A$impl$scala$Short", a.foo) + def bar14(a: A[Long]): Unit = assert(a.foo == "A$impl$scala$Long", a.foo) + def bar15(a: A[Float]): Unit = assert(a.foo == "A$impl$scala$Float", a.foo) + def bar16(a: A[Double]): Unit = assert(a.foo == "A$impl$scala$Double", a.foo) + def bar17(a: A[Char]): Unit = assert(a.foo == "A$impl$scala$Char", a.foo) + + // SpecType is a top class -> no $impl$ class generated, just an anonymous class. + def bar6(a: A[List[Int]]): Unit = assert(!a.foo.contains("$impl$"), a.foo) + def bar7(a: A[IArray[Boolean]]): Unit = assert(!a.foo.contains("$impl$"), a.foo) + def bar8(a: A[Object]): Unit = assert(!a.foo.contains("$impl$"), a.foo) + def bar9(a: A[AnyVal]): Unit = assert(!a.foo.contains("$impl$"), a.foo) + def bar10(a: A[AnyRef]): Unit = assert(!a.foo.contains("$impl$"), a.foo) + +@main def Test = + val m = new methods {} + m.bar1(new A[Int]() {}) + m.bar2(new A[String]() {}) + m.bar3(new A[Lion]() {}) + m.bar4(new A[BigCat]() {}) + m.bar5(new A[Animal]() {}) + m.bar6(new A[List[Int]]() {}) + m.bar7(new A[IArray[Boolean]]() {}) + m.bar8(new A[Object]() {}) + m.bar9(new A[AnyVal]() {}) + m.bar10(new A[AnyRef]() {}) + m.bar11(new A[Boolean]() {}) + m.bar12(new A[Byte]() {}) + m.bar13(new A[Short]() {}) + m.bar14(new A[Long]() {}) + m.bar15(new A[Float]() {}) + m.bar16(new A[Double]() {}) + m.bar17(new A[Char]() {}) diff --git a/tests/run/specialized-trait-erasure.scala b/tests/run/specialized-trait-erasure.scala new file mode 100644 index 000000000000..035f8583d6f1 --- /dev/null +++ b/tests/run/specialized-trait-erasure.scala @@ -0,0 +1,50 @@ +// scalajs: --skip +// (getName() reflection is not supported in ScalaJS) + +//> using options -language:experimental.specializedTraits + +class Animal +class BigCat extends Animal +class Lion extends BigCat + +inline trait A[T: Specialized]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +abstract class methods: + def bar1(a: A[Int]): Int // A[Int] -> A$sp$Int + def bar2(a: A[String]): String // A[String] -> A$sp$String + def bar3(a: A[Lion]): Lion // A[Lion] -> A$sp$Animal + def bar4(a: A[BigCat]): Lion // A[BigCat] -> A$sp$Animal + def bar5(a: A[Animal]): Lion // A[Animal] -> A$sp$Animal + + def bar6(a: A[List[Int]]): Lion // A[List[Int]] -> A + def bar7(a: A[IArray[Boolean]]): Lion // A[IArray[Bool]] -> A + def bar8(a: A[Object]): Lion // A[Object] -> A + def bar9(a: A[AnyVal]): Lion // A[AnyVal] -> A + def bar10(a: A[AnyRef]): Lion // A[AnyRef] -> A + def bar11(a: A[A[Int]]): Lion // A[A[Int]] -> A + +@main def Test = + val expectedParamErasure = Map( + "bar1" -> "A$$sp$scala$Int", + "bar2" -> "A$$sp$java$lang$String", + "bar3" -> "A$$sp$Animal", + "bar4" -> "A$$sp$Animal", + "bar5" -> "A$$sp$Animal", + "bar6" -> "A", + "bar7" -> "A", + "bar8" -> "A", + "bar9" -> "A", + "bar10" -> "A", + "bar11" -> "A", + ) + + val actualParamErasure = classOf[methods].getDeclaredMethods.iterator + .filter(_.getName.startsWith("bar")) + .map(m => m.getName -> m.getParameterTypes.head.getName) + .toMap + + for (name, expected) <- expectedParamErasure do + val actual = actualParamErasure.getOrElse( + name, sys.error(s"method $name not found on class methods")) + assert(actual == expected, s"$name: expected param type $expected, got $actual") diff --git a/tests/run/specialized-trait-inline-specialization-macro/Macro_1.scala b/tests/run/specialized-trait-inline-specialization-macro/Macro_1.scala new file mode 100644 index 000000000000..3bcdbf6dd476 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialization-macro/Macro_1.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits +import scala.quoted.* + +inline def foo(): Unit = + ${fooImpl} + +def fooImpl(using Quotes): Expr[Unit] = + '{ + val x = new A[Int]() {} + assert(x.foo(15) == 10) + assert(x.bar == "A$impl$scala$Int") + } + +inline trait A[T: Specialized]: + def foo(x: T) = 10 + def bar = Thread.currentThread.getStackTrace()(1).getClassName() diff --git a/tests/run/specialized-trait-inline-specialization-macro/Test_2.scala b/tests/run/specialized-trait-inline-specialization-macro/Test_2.scala new file mode 100644 index 000000000000..527d156fc648 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialization-macro/Test_2.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +@main def Test = + foo() \ No newline at end of file diff --git a/tests/run/specialized-trait-inline-specialized-instance-hidden/A_1.scala b/tests/run/specialized-trait-inline-specialized-instance-hidden/A_1.scala new file mode 100644 index 000000000000..f6ca2ab21d07 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialized-instance-hidden/A_1.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo() = assert(Thread.currentThread.getStackTrace()(1).getClassName() == "A$impl$scala$Int") + +inline def bar = + val x = new A[Int]() {} + x.foo() + 5 diff --git a/tests/run/specialized-trait-inline-specialized-instance-hidden/B_2.scala b/tests/run/specialized-trait-inline-specialized-instance-hidden/B_2.scala new file mode 100644 index 000000000000..d57d993d3053 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialized-instance-hidden/B_2.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +@main def Test = + assert(bar == 5) diff --git a/tests/run/specialized-trait-inline-specialized-instance-with-specialization/A_1.scala b/tests/run/specialized-trait-inline-specialized-instance-with-specialization/A_1.scala new file mode 100644 index 000000000000..4026e3488c96 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialized-instance-with-specialization/A_1.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo() = assert(Thread.currentThread.getStackTrace()(1).getClassName() == "A$impl$scala$Int") + +inline def bar[T: Specialized] = new A[T]() {} diff --git a/tests/run/specialized-trait-inline-specialized-instance-with-specialization/B_2.scala b/tests/run/specialized-trait-inline-specialized-instance-with-specialization/B_2.scala new file mode 100644 index 000000000000..305743252757 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialized-instance-with-specialization/B_2.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +@main def Test = + bar[Int].foo() diff --git a/tests/run/specialized-trait-inline-specialized-instance/A_1.scala b/tests/run/specialized-trait-inline-specialized-instance/A_1.scala new file mode 100644 index 000000000000..35220628cef2 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialized-instance/A_1.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo() = assert(Thread.currentThread.getStackTrace()(1).getClassName() == "A$impl$scala$Int") + +inline def bar = new A[Int]() {} diff --git a/tests/run/specialized-trait-inline-specialized-instance/B_2.scala b/tests/run/specialized-trait-inline-specialized-instance/B_2.scala new file mode 100644 index 000000000000..b9cb802a15a1 --- /dev/null +++ b/tests/run/specialized-trait-inline-specialized-instance/B_2.scala @@ -0,0 +1,4 @@ +//> using options -language:experimental.specializedTraits + +@main def Test = + bar.foo() diff --git a/tests/run/specialized-trait-inline-trait-ordering-edge-case.scala b/tests/run/specialized-trait-inline-trait-ordering-edge-case.scala new file mode 100644 index 000000000000..e6aff3601788 --- /dev/null +++ b/tests/run/specialized-trait-inline-trait-ordering-edge-case.scala @@ -0,0 +1,17 @@ +// scalajs: --skip +//> using options -language:experimental.specializedTraits + +inline trait T1[A: Specialized] + +inline trait T2[B: Specialized]: + def foo(x: T1[B]) = "bungle" + +class Bar extends T2[Int] // Inlined by specializeInlineTraits + // DesugarSpecializedTraits then needs to see the inlined T1[Int] and generate the specialization + // This works ok right now because of the miniphase processing but it's perhaps not ideal. +@main def Test = + val fooMethods = classOf[Bar].getDeclaredMethods.iterator + .filter(_.getName.startsWith("foo")) + .map(m => m.getName -> m.getParameterTypes.head.getName) + .toList + assert(fooMethods.exists(_._2 == "T1$$sp$scala$Int")) diff --git a/tests/run/specialized-trait-inlining-loop.scala b/tests/run/specialized-trait-inlining-loop.scala new file mode 100644 index 000000000000..034c1cc256b1 --- /dev/null +++ b/tests/run/specialized-trait-inlining-loop.scala @@ -0,0 +1,25 @@ +//> using options -language:experimental.specializedTraits +inline trait T1[T: Specialized]: + def bar1 = myInlineMethod1[T] + +inline trait T2[T: Specialized]: + def bar2 = myInlineMethod2[T] + +inline trait T3[T: Specialized]: + def foo(x: T): T = x + +inline def myInlineMethod1[T: Specialized] = new T2[T]() {} +inline def myInlineMethod2[T: Specialized] = new T3[T]() {} + +@main def Test = + val x = new T1[Int]() {} + assert(x.bar1.bar2.foo(10) == 10) + +// Need to: +// 1) Create T1$impl$Int$ and inline `def bar1 = myInlineMethod1[Int]` (desugarSpecializedTraits) +// 2) Inline myInlineMethod1 into bar1 inside T1$impl$Int (inlining) +// 3) Create T2$impl$Int and fix the inlined definition of bar1/myInlineMethod1 (desugarSpecializedTraits) +// 4) Inline myInlineMethod2 into bar2 inside T2$impl$Int +// 5) Create T3$impl$Int and fix the inlined definition of bar2/myInlineMethod2 (desugarSpecializedTraits) + +// This can continue for arbitrarily many inlines and you don't know in advance that any of these specializations need to be created. diff --git a/tests/run/specialized-trait-list-of-specialized-trait.scala b/tests/run/specialized-trait-list-of-specialized-trait.scala new file mode 100644 index 000000000000..446276c0e8c7 --- /dev/null +++ b/tests/run/specialized-trait-list-of-specialized-trait.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits +inline trait Foo[T: Specialized]: + def foo = Thread.currentThread.getStackTrace()(1).getClassName() + +def bar(xs: List[Foo[Int]]) = xs.head + +@main def Test = + val myList = List(new Foo[Int]() {}, new Foo[Int]() {}) + assert(bar(myList).foo == "Foo$impl$scala$Int") diff --git a/tests/run/specialized-trait-list-zip-example.check b/tests/run/specialized-trait-list-zip-example.check new file mode 100644 index 000000000000..d12f0d8f3af1 --- /dev/null +++ b/tests/run/specialized-trait-list-zip-example.check @@ -0,0 +1,4 @@ +The population of Switzerland is 9.1 million and its capital is Bern +The population of France is 68.52 million and its capital is Paris +The population of The Netherlands is 18.4 million and its capital is Berlin +The population of Germany is 83.5 million and its capital is The Hague diff --git a/tests/run/specialized-trait-list-zip-example.scala b/tests/run/specialized-trait-list-zip-example.scala new file mode 100644 index 000000000000..fd9f6075192a --- /dev/null +++ b/tests/run/specialized-trait-list-zip-example.scala @@ -0,0 +1,45 @@ +//> using options -language:experimental.specializedTraits -Werror + +import scala.annotation.nowarn + +@nowarn("id=E234") +sealed inline trait List[+T: Specialized]: + inline def zip[S: Specialized](other: List[S]): List[(T, S)] = + def zip(xxs: List[T], yys: List[S]): List[(T, S)] = ((xxs, yys) : @unchecked) match { + case (_: Nill[_], _) => Nill() + case (_, _: Nill[_]) => Nill() + case (xxs: :+:[T @unchecked], yys: :+:[S @unchecked]) => :+:((xxs.head, yys.head), zip(xxs.tail, yys.tail)) + } + zip(this, other) + + def foreach[S](f: T => Unit): Unit = (this: List[T]) match { + case xs: :+:[T @unchecked] => f(xs.head); xs.tail.foreach(f) + case _: Nill[_] => + } + + +sealed inline trait Nill[T: Specialized] extends List[T] +sealed inline trait :+:[T: Specialized](val head: T, val tail: List[T]) extends List[T] + +object Nill { + inline def apply[T: Specialized]() = new Nill[T]() {} +} + +object :+: { + inline def apply[T: Specialized](head: T, tail: List[T]): List[T] = + new :+:[T](head, tail) {} +} + +object List: + inline def apply[T: Specialized](values: T*) = + values.foldRight[List[T]](Nill())(:+:.apply) + +@main def Test = + val xs: List[Double] = :+:(9.1, :+:(68.52, :+:(18.4, :+:(83.5, Nill[Double]())))) + val ys = List("Switzerland", "France", "The Netherlands", "Germany") + val zs = List("Bern", "Paris", "Berlin", "The Hague") + + xs.zip(ys).zip(zs).foreach( (numberCountry, capital) => + val (number, country) = numberCountry + println(s"The population of ${country} is ${number} million and its capital is ${capital}") + ) diff --git a/tests/run/specialized-trait-manual-class-extend-not-top-of-hierarchy.scala b/tests/run/specialized-trait-manual-class-extend-not-top-of-hierarchy.scala new file mode 100644 index 000000000000..712824b59efd --- /dev/null +++ b/tests/run/specialized-trait-manual-class-extend-not-top-of-hierarchy.scala @@ -0,0 +1,16 @@ +// scalajs: --skip +// (getInterfaces() reflection is not supported in ScalaJS) + +//> using options -language:experimental.specializedTraits + +class Animal +class Mammal extends Animal +class Binturong extends Mammal + +inline trait A[T: Specialized] + +class B extends A[Binturong] + +@main def Test = + val traits = classOf[B].getInterfaces() + assert(traits.exists(cl => cl.getName() == "A$$sp$Animal")) diff --git a/tests/run/specialized-trait-masked-inline-specialization-via-inlining.scala b/tests/run/specialized-trait-masked-inline-specialization-via-inlining.scala new file mode 100644 index 000000000000..5b21dbe9d3b9 --- /dev/null +++ b/tests/run/specialized-trait-masked-inline-specialization-via-inlining.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo() = assert(Thread.currentThread.getStackTrace()(1).getClassName() == "A$impl$scala$Int") + +inline def myMethod0[T: Specialized] = new A[T]() {} +inline def myMethod1 = myMethod0[Int] +inline def myMethod2 = myMethod1 +inline def myMethod3 = myMethod2 + +@main def Test = + val v = myMethod3 + v.foo() diff --git a/tests/run/specialized-trait-masked-inline-specialized.scala b/tests/run/specialized-trait-masked-inline-specialized.scala new file mode 100644 index 000000000000..69f1618b8480 --- /dev/null +++ b/tests/run/specialized-trait-masked-inline-specialized.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo() = assert(Thread.currentThread.getStackTrace()(1).getClassName() == "A$impl$scala$Int") + +inline def myMethod1 = new A[Int]() {} +inline def myMethod2 = myMethod1 +inline def myMethod3 = myMethod2 + +@main def Test = + val v = myMethod3 + v.foo() diff --git a/tests/run/specialized-trait-maths.scala b/tests/run/specialized-trait-maths.scala new file mode 100644 index 000000000000..25ca7da8ceec --- /dev/null +++ b/tests/run/specialized-trait-maths.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized](x: T): + def foo = x + +def f(b: Foo[Int]) = 37 + b.foo + +object Test: + def main(args: Array[String]): Unit = { + val x = new Foo[Int](42) {} + val y = f(x) + assert(y == 79) + } diff --git a/tests/run/specialized-trait-multiple-files/A_1.scala b/tests/run/specialized-trait-multiple-files/A_1.scala new file mode 100644 index 000000000000..9fb078f6bccf --- /dev/null +++ b/tests/run/specialized-trait-multiple-files/A_1.scala @@ -0,0 +1,6 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized]: + def foo(x: T):T = x + +class B extends A[Int] diff --git a/tests/run/specialized-trait-multiple-files/B_2.scala b/tests/run/specialized-trait-multiple-files/B_2.scala new file mode 100644 index 000000000000..7843ebadf48f --- /dev/null +++ b/tests/run/specialized-trait-multiple-files/B_2.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits + +class C extends A[Int] + +@main def Test = + val b = B() + val c = C() + + println(b.foo(10)) + println(c.foo(10)) \ No newline at end of file diff --git a/tests/run/specialized-trait-multiple-package-same-file-nested.scala b/tests/run/specialized-trait-multiple-package-same-file-nested.scala new file mode 100644 index 000000000000..d44ba22053cc --- /dev/null +++ b/tests/run/specialized-trait-multiple-package-same-file-nested.scala @@ -0,0 +1,30 @@ +//> using options -language:experimental.specializedTraits +package owner { + package package1 { + inline trait A[T: Specialized]: + def foo(x: T) = "Package 1!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + class B extends A[Int] + class C extends A[String] + } + + package package2 { + inline trait A[T: Specialized]: + def foo(x: T) = "Package 2!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + class B extends A[Int] + class C extends A[String] + } +} + +@main def Test = + val b = owner.package1.B() + val c = owner.package2.C() + assert(b.foo(10) == "Package 1!") + assert(c.foo("Hello World") == "Package 2!") + + val d = new owner.package1.A[Int]() {} + val e = new owner.package2.A[Int]() {} + assert(d.bar == "owner.package1.A$impl$scala$Int") + assert(e.bar == "owner.package2.A$impl$scala$Int") + diff --git a/tests/run/specialized-trait-multiple-package-same-file.scala b/tests/run/specialized-trait-multiple-package-same-file.scala new file mode 100644 index 000000000000..be6b83aa5465 --- /dev/null +++ b/tests/run/specialized-trait-multiple-package-same-file.scala @@ -0,0 +1,28 @@ +//> using options -language:experimental.specializedTraits + +package package1 { + inline trait A[T: Specialized]: + def foo(x: T) = "Package 1!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + class B extends A[Int] + class C extends A[String] +} + +package package2 { + inline trait A[T: Specialized]: + def foo(x: T) = "Package 2!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + class B extends A[Int] + class C extends A[String] +} + +@main def Test = + val b = package1.B() + val c = package2.C() + assert(b.foo(10) == "Package 1!") + assert(c.foo("Hello World") == "Package 2!") + + val d = new package1.A[Int]() {} + val e = new package2.A[Int]() {} + assert(d.bar == "package1.A$impl$scala$Int") + assert(e.bar == "package2.A$impl$scala$Int") diff --git a/tests/run/specialized-trait-multiple-parameter-packs-method.scala b/tests/run/specialized-trait-multiple-parameter-packs-method.scala new file mode 100644 index 000000000000..f03d2302ce02 --- /dev/null +++ b/tests/run/specialized-trait-multiple-parameter-packs-method.scala @@ -0,0 +1,20 @@ +//> using options -language:experimental.specializedTraits + + +inline trait A[T: Specialized, S: Numeric](val x: T)(val y: T)(val z: S)(p: S): + def getArgs = (x, y, z, p) + +inline def foo[P: Specialized](x: P)[Q: {Specialized, Numeric}](y: P, z: Q, p: Q) = new A[P, Q](x)(y)(z)(p) {} + +inline trait B[T: Specialized](val w: T): + inline def foo[P: Specialized](x: P, z: P)[Q: Specialized](y: Q, f: P) = + val b = new B[P](x) {} + val c = new B[Q](y) {} + c + +@main def Test = + val a = foo[String]("Good evening")[Long]("Good morning", 1_000_000_000_000_000L, 1_000_000_000_000_001L) + assert(a.getArgs == ("Good evening", "Good morning", 1_000_000_000_000_000L, 1_000_000_000_000_001L)) + + val b = new B[Boolean](true) {} + assert(b.foo[Float](1.1, 2.2)[Short](1, 3.3).w == 1) diff --git a/tests/run/specialized-trait-multiple-parameter-packs.scala b/tests/run/specialized-trait-multiple-parameter-packs.scala new file mode 100644 index 000000000000..eb7710b00601 --- /dev/null +++ b/tests/run/specialized-trait-multiple-parameter-packs.scala @@ -0,0 +1,14 @@ +//> using options -language:experimental.specializedTraits + +inline trait A[T: Specialized, S: Numeric](val x: T)(val y: T)(val z: S)(p: S): + val p2 = p + def getArgs = (x, y, z, p2) + +class B(w: Int)(e: String) extends A[String, Int](e)("Y")(100)(w) + +@main def Test = + val b = B(41)("Good Morning") + assert(b.getArgs == ("Good Morning", "Y", 100, 41)) + + val a = new A[Boolean, Long](true)(false)(1000000000)(-10) {} + assert(a.getArgs == (true, false, 1000000000, -10)) diff --git a/tests/run/specialized-trait-one-parent-is-inline.scala b/tests/run/specialized-trait-one-parent-is-inline.scala new file mode 100644 index 000000000000..e9a6fd56f831 --- /dev/null +++ b/tests/run/specialized-trait-one-parent-is-inline.scala @@ -0,0 +1,15 @@ +//> using options -language:experimental.specializedTraits + +inline trait Foo[T: Specialized] +inline trait A[T](x: T): + def foo: T = x + +class B extends A[Int](15), Foo: + val y = 1 + +def h(x: B) = x.foo + +@main def Test = + val b = B() + assert(h(b) == 15) + assert(b.y == 1) diff --git a/tests/run/specialized-trait-package-contains-object-multi-file/A_1.scala b/tests/run/specialized-trait-package-contains-object-multi-file/A_1.scala new file mode 100644 index 000000000000..0810149ba75e --- /dev/null +++ b/tests/run/specialized-trait-package-contains-object-multi-file/A_1.scala @@ -0,0 +1,11 @@ +//> using options -language:experimental.specializedTraits +package package1 { + object Outer: + object Inner: + inline trait A[T: Specialized]: + def foo(x: T) = "Package 1!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + class B extends Outer.Inner.A[Int] + class C extends Outer.Inner.A[String] +} + diff --git a/tests/run/specialized-trait-package-contains-object-multi-file/B_2.scala b/tests/run/specialized-trait-package-contains-object-multi-file/B_2.scala new file mode 100644 index 000000000000..b7984b4d8708 --- /dev/null +++ b/tests/run/specialized-trait-package-contains-object-multi-file/B_2.scala @@ -0,0 +1,11 @@ +//> using options -language:experimental.specializedTraits + +@main def Test = + val b = package1.B() + val c = package1.C() + assert(b.foo(10) == "Package 1!") + assert(c.foo("Hello World") == "Package 1!") + + val d = new package1.Outer.Inner.A[Int]() {} + println(d.bar) + assert(d.bar == "package1.Outer$Inner$$A$impl$scala$Int") diff --git a/tests/run/specialized-trait-package-contains-object.scala b/tests/run/specialized-trait-package-contains-object.scala new file mode 100644 index 000000000000..0489a1e9baa8 --- /dev/null +++ b/tests/run/specialized-trait-package-contains-object.scala @@ -0,0 +1,20 @@ +//> using options -language:experimental.specializedTraits +package package1 { + object Outer: + object Inner: + inline trait A[T: Specialized]: + def foo(x: T) = "Package 1!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + class B extends Outer.Inner.A[Int] + class C extends Outer.Inner.A[String] +} + +@main def Test = + val b = package1.B() + val c = package1.C() + assert(b.foo(10) == "Package 1!") + assert(c.foo("Hello World") == "Package 1!") + + val d = new package1.Outer.Inner.A[Int]() {} + assert(d.bar == "package1.Outer$Inner$$A$impl$scala$Int") + diff --git a/tests/run/specialized-trait-package/A_1.scala b/tests/run/specialized-trait-package/A_1.scala new file mode 100644 index 000000000000..57eecffa6c17 --- /dev/null +++ b/tests/run/specialized-trait-package/A_1.scala @@ -0,0 +1,8 @@ +//> using options -language:experimental.specializedTraits + +package package1 + +inline trait A[T: Specialized]: + def foo(x: T) = "Package 1!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() + \ No newline at end of file diff --git a/tests/run/specialized-trait-package/A_2.scala b/tests/run/specialized-trait-package/A_2.scala new file mode 100644 index 000000000000..268ee13bffe8 --- /dev/null +++ b/tests/run/specialized-trait-package/A_2.scala @@ -0,0 +1,7 @@ +//> using options -language:experimental.specializedTraits + +package package2 + +inline trait A[T: Specialized]: + def foo(x: T) = "Package 2!" + def bar = Thread.currentThread.getStackTrace()(1).getClassName() diff --git a/tests/run/specialized-trait-package/B_3.scala b/tests/run/specialized-trait-package/B_3.scala new file mode 100644 index 000000000000..93a5c9ba801b --- /dev/null +++ b/tests/run/specialized-trait-package/B_3.scala @@ -0,0 +1,21 @@ +//> using options -language:experimental.specializedTraits +class B extends package1.A[Int] +class C extends package2.A[Int] + +@main def Test = + val b = B() + val c = C() + assert(b.foo(10) == "Package 1!") + assert(c.foo(11) == "Package 2!") + + val d = new package1.A[Int]() {} + val e = new package2.A[Int]() {} + assert(d.bar == "package1.A$impl$scala$Int") + assert(e.bar == "package2.A$impl$scala$Int") + + import package1.A + import package2.A as A2 + val f = new A[Int]() {} + val g = new A2[Int]() {} + assert(d.bar == "package1.A$impl$scala$Int") + assert(e.bar == "package2.A$impl$scala$Int") diff --git a/tests/run/specialized-trait-pathological-context-bounds.scala b/tests/run/specialized-trait-pathological-context-bounds.scala new file mode 100644 index 000000000000..d299704034eb --- /dev/null +++ b/tests/run/specialized-trait-pathological-context-bounds.scala @@ -0,0 +1,16 @@ +//> using options -language:experimental.specializedTraits + +trait A[T, R, Q] + +inline trait Trait[T: {Specialized, Numeric}, S <: Object, Q: Numeric, R: Specialized, D: {Numeric, Specialized}](a: Int) extends A[S, Char, T] { + def bar = "Buna saira" +} + +def foo(v: Trait[Int, String, Int, Int, Int]) = v + +object Test: + def main(args: Array[String]): Unit = { + val a = new Trait[Int, String, Int, Int, Int](5) {} + assert(foo(a).bar == "Buna saira") + } + diff --git a/tests/run/specialized-trait-requires-inline-trait-inlining.scala b/tests/run/specialized-trait-requires-inline-trait-inlining.scala new file mode 100644 index 000000000000..2f5532f93e7c --- /dev/null +++ b/tests/run/specialized-trait-requires-inline-trait-inlining.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits + +inline trait T1[T]: + def boo(x: T): T = x + +inline trait T[T: Specialized]: + def ff = new T1[T]() { + def id(x: T): T = x + } + +@main def Test = + val a = new T[Int]() {} diff --git a/tests/run/specialized-trait-scoped-inside-object-2.scala b/tests/run/specialized-trait-scoped-inside-object-2.scala new file mode 100644 index 000000000000..d6c53971f589 --- /dev/null +++ b/tests/run/specialized-trait-scoped-inside-object-2.scala @@ -0,0 +1,12 @@ +//> using options -language:experimental.specializedTraits + +object MySpecializedStuff: + inline trait Foo[T: Specialized]: + def bar = "Bar" + + class Bar extends Foo[Char] + def foo(x: Foo[Char]) = x.bar + +@main def Test = + val x = new MySpecializedStuff.Bar() + assert(MySpecializedStuff.foo(x) == "Bar") diff --git a/tests/run/specialized-trait-scoped-inside-object-3.scala b/tests/run/specialized-trait-scoped-inside-object-3.scala new file mode 100644 index 000000000000..adec56fcfd66 --- /dev/null +++ b/tests/run/specialized-trait-scoped-inside-object-3.scala @@ -0,0 +1,13 @@ +//> using options -language:experimental.specializedTraits + +object MySpecializedStuff: + inline trait Foo[T: Specialized]: + def bar = "Bar" + +object MySpecializedStuff2: + class Bar extends MySpecializedStuff.Foo[Char] + def foo(x: MySpecializedStuff.Foo[Char]) = x.bar + +@main def Test = + val x = new MySpecializedStuff2.Bar() + assert(MySpecializedStuff2.foo(x) == "Bar") diff --git a/tests/run/specialized-trait-scoped-inside-object.scala b/tests/run/specialized-trait-scoped-inside-object.scala new file mode 100644 index 000000000000..ec14264a72a7 --- /dev/null +++ b/tests/run/specialized-trait-scoped-inside-object.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits + +object MySpecializedStuff: + inline trait Foo[T: Specialized]: + def bar = "Bar" + + def foo = new Foo[Int] {} + +@main def Test = MySpecializedStuff.foo.bar diff --git a/tests/run/specialized-trait-self-inline.check b/tests/run/specialized-trait-self-inline.check new file mode 100644 index 000000000000..ce0fe52fb9c7 --- /dev/null +++ b/tests/run/specialized-trait-self-inline.check @@ -0,0 +1,6 @@ +5 +4 +3 +2 +1 +Lift Off! diff --git a/tests/run/specialized-trait-self-inline.scala b/tests/run/specialized-trait-self-inline.scala new file mode 100644 index 000000000000..bc454581d160 --- /dev/null +++ b/tests/run/specialized-trait-self-inline.scala @@ -0,0 +1,17 @@ +//> using options -language:experimental.specializedTraits +//> using scala 3.9.0-RC1-bin-SNAPSHOT-nonbootstrapped + +import math.Numeric.Implicits.infixNumericOps +import math.Ordering.Implicits.infixOrderingOps + +inline trait Foo[T: {Specialized, Numeric}]: + inline def foo[S: Specialized](inline x: Int, inline y: Int, inline w: T, inline z: S): S = + inline if x < y then + z + else + println(w) + foo(x - 1, y, w - summon[Numeric[T]].one, z) + +@main def Test = + val x = new Foo[Short] {} + println(x.foo(10, 6, 5, "Lift Off!")) diff --git a/tests/run/specialized-trait-subtyping-once-specialized.scala b/tests/run/specialized-trait-subtyping-once-specialized.scala new file mode 100644 index 000000000000..9ae5fbd742fa --- /dev/null +++ b/tests/run/specialized-trait-subtyping-once-specialized.scala @@ -0,0 +1,9 @@ +//> using options -language:experimental.specializedTraits +inline trait T1[T: Specialized] +inline trait T2[T: Specialized] extends T1[T] + +def foo(x: T1[Int]) = println("foo") + +@main def Test = + val x = new T2[Int]() {} + foo(x) diff --git a/tests/run/specialized-trait-super-chain.scala b/tests/run/specialized-trait-super-chain.scala new file mode 100644 index 000000000000..73bd635f83e9 --- /dev/null +++ b/tests/run/specialized-trait-super-chain.scala @@ -0,0 +1,46 @@ +//> using options -language:experimental.specializedTraits +var hops = 0 + +inline trait A[T: Specialized]: + def foo() = + hops += 1 + "A" + +inline trait B[T: Specialized] extends A[T]: + override def foo() = + hops += 1 + super.foo() + +inline trait C[T: Specialized] extends B[T]: + override def foo() = + hops += 1 + "B" + +inline trait D[T: Specialized] extends C[T]: + override def foo() = + hops += 1 + super.foo() + +inline trait E[T: Specialized] extends D[T]: + override def foo() = + hops += 1 + super.foo() + +inline trait F[T: Specialized] extends E[T]: + override def foo() = + hops += 1 + super.foo() + +class C1 extends F[Int] + +@main def Test = + hops = 0 + val cl = C1() + assert(cl.foo() == "B") + assert(hops == 4) + + hops = 0 + val cl2 = new F[String]() {} + assert(cl.foo() == "B") + assert(hops == 4) + diff --git a/tests/run/specialized-trait-varargs.scala b/tests/run/specialized-trait-varargs.scala new file mode 100644 index 000000000000..ca5affc8fa2f --- /dev/null +++ b/tests/run/specialized-trait-varargs.scala @@ -0,0 +1,10 @@ +//> using options -language:experimental.specializedTraits + +inline trait ListA[T: Specialized](vals: T*): + def printVals() = + vals.foreach(println(_)) + +@main def Test = + val bungle: List[Short] = List(4123, 6, 7, 8, 10, 5, 11, 100) + val x = new ListA[Short](bungle*) {} + x.printVals() diff --git a/tests/run/specialized-trait-variance.scala b/tests/run/specialized-trait-variance.scala new file mode 100644 index 000000000000..e10d5caf8a0b --- /dev/null +++ b/tests/run/specialized-trait-variance.scala @@ -0,0 +1,53 @@ +//> using options -language:experimental.specializedTraits + +import scala.annotation.nowarn + +trait Animal: + def makeNoise: String +class Lion extends Animal: + override def makeNoise = "ROAR!" +class Dog extends Animal: + override def makeNoise: String = "BARK!" + +trait Material +class Paper extends Material +class Newspaper extends Paper + +inline trait MyList[+T: Specialized](val xs: List[T]): + def map[S](f: T => S) = xs.map(f) + +inline trait Bin[-T: Specialized]: + def throwAway(x: T) = println(s"Throwing away ${x}") + +def sound(animals: MyList[Animal]) = + animals.map(_.makeNoise) + +def throwAwayTheNewspaper(bin: Bin[Newspaper]) = + val newspaper = Newspaper() + bin.throwAway(newspaper) + +def throwAwayAnInteger(bin: Bin[Int]) = + val integer = 100 + bin.throwAway(integer) + +def throwAwayAnObject(bin: Bin[Object]) = + val obj = "good morning" + bin.throwAway(obj) + +def throwAwayAnAnyVal(bin: Bin[AnyVal]) = + val bc: AnyVal = 400 + bin.throwAway(bc) + +@main def Test = + val myDogs: MyList[Dog] = new MyList(List(Dog(), Dog(), Dog())) {} + sound(myDogs) // MyList[Dog] can be interpreted as MyList[Animal] due to covariance + + val myWastepaperBasket = new Bin[Paper]() {} + throwAwayTheNewspaper(myWastepaperBasket) // Bin[Paper] can be interpreted as Bin[Newspaper] due to contravariance + + // Fine; same erasure bucket + val myAnyBin = new Bin[Any] {} + val myAnyRefBin = new Bin[Any] {} + throwAwayAnObject(myAnyBin) + throwAwayAnObject(myAnyRefBin) + throwAwayAnAnyVal(myAnyBin) diff --git a/tests/run/specialized-trait-vector-dot-product.scala b/tests/run/specialized-trait-vector-dot-product.scala new file mode 100644 index 000000000000..4d2112f2b185 --- /dev/null +++ b/tests/run/specialized-trait-vector-dot-product.scala @@ -0,0 +1,38 @@ +//> using options -language:experimental.specializedTraits + +inline trait Vec[T: {Specialized, Numeric2}](elems: Array[T]): + private val num = summon[Numeric2[T]] + + def length = elems.length + + def apply(i: Int): T = elems(i) + + def scalarProduct(other: Vec[T]): T = + require(this.length == other.length) + var result = num.fromInt(0) + for i <- 0 until length do + result = num.plus(result, num.times(this(i), other(i))) + result + +object Vec: + inline def apply[T: {Specialized, Numeric2}](elems: Array[T]) = new Vec[T](elems) {} +end Vec + +object Test: + def main(args: Array[String]) = + implicit val v: Numeric2[Int] = new IntIsIntegral() {} + val x = Vec[Int](Array(1, 2, 3, 4, 5)) + val y = Vec[Int](Array(3, 4, 5, 6, 7)) + val z = x.scalarProduct(y) + assert(z == 85) + +inline trait Numeric2[T: Specialized]: + def fromInt(x: Int): T + def plus(x: T, y: T): T + def times(x: T, y: T): T + +class IntIsIntegral extends Numeric2[Int]: + override def fromInt(x: Int): Int = x + override def plus(x: Int, y: Int): Int = x + y + override def times(x: Int, y: Int): Int = x * y + diff --git a/tests/run/specialized-trait-vector-zip.check b/tests/run/specialized-trait-vector-zip.check new file mode 100644 index 000000000000..d017a5333fe6 --- /dev/null +++ b/tests/run/specialized-trait-vector-zip.check @@ -0,0 +1,4 @@ +The population of Switzerland is 9.1 million and its capital is Bern +The population of France is 68.52 million and its capital is Paris +The population of The Netherlands is 18.4 million and its capital is The Hague +The population of Germany is 83.5 million and its capital is Berlin diff --git a/tests/run/specialized-trait-vector-zip.scala b/tests/run/specialized-trait-vector-zip.scala new file mode 100644 index 000000000000..b9b4bddcef57 --- /dev/null +++ b/tests/run/specialized-trait-vector-zip.scala @@ -0,0 +1,15 @@ +//> using options -language:experimental.specializedTraits + +inline trait Vec[T: Specialized](val xs: List[T]): + inline def zip[S: Specialized](other: Vec[S]): Vec[(T, S)] = + new Vec[(T, S)](xs.zip(other.xs)) {} + +@main def Test = + val xs = new Vec[Double](List(9.1, 68.52, 18.4, 83.5)) {} + val ys = new Vec[String](List("Switzerland", "France", "The Netherlands", "Germany")) {} + val zs = new Vec[String](List("Bern", "Paris", "The Hague", "Berlin")) {} + + xs.zip(ys).zip(zs).xs.map( (numberCountry, capital) => + val (number, country) = numberCountry + println(s"The population of ${country} is ${number} million and its capital is ${capital}") + ) diff --git a/tests/run/trait-extends-inline-trait-uses.scala b/tests/run/trait-extends-inline-trait-uses.scala new file mode 100644 index 000000000000..78148ad9823f --- /dev/null +++ b/tests/run/trait-extends-inline-trait-uses.scala @@ -0,0 +1,15 @@ +//> using options -language:experimental.inlineTraits +inline trait Foo: + val x = 10 + var y = 100 + def z = 5 + +trait Bar extends Foo + +class Baz extends Bar + +@main def Test = + val b = Baz() + assert(b.y == 100) + assert(b.x == 10) + assert(b.z == 5)