diff --git a/library/src/scala/Boolean.scala b/library/src/scala/Boolean.scala index 5ff37694d133..5df9184b5e30 100644 --- a/library/src/scala/Boolean.scala +++ b/library/src/scala/Boolean.scala @@ -114,7 +114,7 @@ final abstract class Boolean private extends AnyVal { object Boolean extends AnyValCompanion { - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToBoolean`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -123,7 +123,7 @@ object Boolean extends AnyValCompanion { */ def box(x: Boolean): java.lang.Boolean = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Boolean. * diff --git a/library/src/scala/Byte.scala b/library/src/scala/Byte.scala index c5891be37e36..c1f0e81f746b 100644 --- a/library/src/scala/Byte.scala +++ b/library/src/scala/Byte.scala @@ -455,7 +455,7 @@ object Byte extends AnyValCompanion { /** The largest value representable as a Byte. */ final val MaxValue = java.lang.Byte.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToByte`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -464,7 +464,7 @@ object Byte extends AnyValCompanion { */ def box(x: Byte): java.lang.Byte = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Byte. * diff --git a/library/src/scala/Char.scala b/library/src/scala/Char.scala index 500ffcb05412..06a6e186901b 100644 --- a/library/src/scala/Char.scala +++ b/library/src/scala/Char.scala @@ -455,7 +455,7 @@ object Char extends AnyValCompanion { /** The largest value representable as a Char. */ final val MaxValue = java.lang.Character.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToCharacter`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -464,7 +464,7 @@ object Char extends AnyValCompanion { */ def box(x: Char): java.lang.Character = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Character. * diff --git a/library/src/scala/Double.scala b/library/src/scala/Double.scala index 08a91bf8c603..57e99999198b 100644 --- a/library/src/scala/Double.scala +++ b/library/src/scala/Double.scala @@ -230,7 +230,7 @@ object Double extends AnyValCompanion { /** The largest finite positive number representable as a Double. */ final val MaxValue = java.lang.Double.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToDouble`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -239,7 +239,7 @@ object Double extends AnyValCompanion { */ def box(x: Double): java.lang.Double = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Double. * diff --git a/library/src/scala/Float.scala b/library/src/scala/Float.scala index fae92d99e882..1f8b00886258 100644 --- a/library/src/scala/Float.scala +++ b/library/src/scala/Float.scala @@ -230,7 +230,7 @@ object Float extends AnyValCompanion { /** The largest finite positive number representable as a Float. */ final val MaxValue = java.lang.Float.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToFloat`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -239,7 +239,7 @@ object Float extends AnyValCompanion { */ def box(x: Float): java.lang.Float = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Float. * diff --git a/library/src/scala/Function0.scala b/library/src/scala/Function0.scala index af9327e6e88c..be591a9de87f 100644 --- a/library/src/scala/Function0.scala +++ b/library/src/scala/Function0.scala @@ -11,7 +11,7 @@ */ // GENERATED CODE: DO NOT EDIT. -// genprod generated these sources at: 2022-01-17T20:47:12.170348200Z +// genprod generated these sources at: 2025-08-11T16:29:31.823591209Z package scala diff --git a/library/src/scala/Int.scala b/library/src/scala/Int.scala index 335e33233541..a3fff7e991da 100644 --- a/library/src/scala/Int.scala +++ b/library/src/scala/Int.scala @@ -455,7 +455,7 @@ object Int extends AnyValCompanion { /** The largest value representable as an Int. */ final val MaxValue = java.lang.Integer.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToInteger`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -464,7 +464,7 @@ object Int extends AnyValCompanion { */ def box(x: Int): java.lang.Integer = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Integer. * diff --git a/library/src/scala/Long.scala b/library/src/scala/Long.scala index e19b7e706f96..2561c1ec74c2 100644 --- a/library/src/scala/Long.scala +++ b/library/src/scala/Long.scala @@ -452,7 +452,7 @@ object Long extends AnyValCompanion { /** The largest value representable as a Long. */ final val MaxValue = java.lang.Long.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToLong`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -461,7 +461,7 @@ object Long extends AnyValCompanion { */ def box(x: Long): java.lang.Long = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Long. * diff --git a/library/src/scala/Short.scala b/library/src/scala/Short.scala index fc58ad8640e7..03b0929ea5ce 100644 --- a/library/src/scala/Short.scala +++ b/library/src/scala/Short.scala @@ -455,7 +455,7 @@ object Short extends AnyValCompanion { /** The largest value representable as a Short. */ final val MaxValue = java.lang.Short.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToShort`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -464,7 +464,7 @@ object Short extends AnyValCompanion { */ def box(x: Short): java.lang.Short = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Short. * diff --git a/library/src/scala/Unit.scala b/library/src/scala/Unit.scala index 1799f678e3fa..e7ad25073f50 100644 --- a/library/src/scala/Unit.scala +++ b/library/src/scala/Unit.scala @@ -31,7 +31,7 @@ final abstract class Unit private extends AnyVal { @scala.annotation.compileTimeOnly("`Unit` companion object is not allowed in source; instead, use `()` for the unit value") object Unit extends AnyValCompanion { - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * This method is not intended for use in source code. * The runtime representation of this value is platform specific. @@ -41,7 +41,7 @@ object Unit extends AnyValCompanion { */ def box(x: Unit): scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a scala.runtime.BoxedUnit. * diff --git a/project/GenerateAnyVals.scala b/project/GenerateAnyVals.scala new file mode 100644 index 000000000000..12b68fc3e29e --- /dev/null +++ b/project/GenerateAnyVals.scala @@ -0,0 +1,526 @@ +package dotty.tools + +/** Code generation of the AnyVal types and their companions. */ +trait GenerateAnyValReps { + self: GenerateAnyVals => + + sealed abstract class AnyValNum(name: String, repr: Option[String], javaEquiv: String) + extends AnyValRep(name,repr,javaEquiv) { + + case class Op(op : String, doc : String) + + private def companionCoercions(deprecated: Boolean, tos: AnyValRep*): List[String] = + tos.toList.flatMap { to => + val code = s"implicit def @javaequiv@2${to.javaEquiv}(x: @name@): ${to.name} = x.to${to.name}" + if (deprecated) + List(s"""@deprecated("Implicit conversion from @name@ to ${to.name} is dangerous because it loses precision. Write `.to${to.name}` instead.", "2.13.1")""", code) + else + List(code) + } + + def coercionComment = +"""/** Language mandated coercions from @name@ to "wider" types. */ +import scala.language.implicitConversions""" + + def implicitCoercions: List[String] = { + val coercions = this match { + case B => companionCoercions(deprecated = false, S, I, L, F, D) + case S | C => companionCoercions(deprecated = false, I, L, F, D) + case I => companionCoercions(deprecated = true, F) ++ companionCoercions(deprecated = false, L, D) + case L => companionCoercions(deprecated = true, F, D) + case F => companionCoercions(deprecated = false, D) + case _ => Nil + } + if (coercions.isEmpty) Nil + else coercionComment.linesIterator.toList ++ coercions + } + + def isInteger: Boolean = isIntegerType(this) + + def unaryOps = { + val ops = List( + Op("+", "/** Returns this value, unmodified. */"), + Op("-", "/** Returns the negation of this value. */")) + + if (isInteger) + Op("~", "/**\n" + + " * Returns the bitwise negation of this value.\n" + + " * @example {{{\n" + + " * ~5 == -6\n" + + " * // in binary: ~00000101 ==\n" + + " * // 11111010\n" + + " * }}}\n" + + " */") :: ops + else ops + } + + def bitwiseOps = + if (isInteger) + List( + Op("|", "/**\n" + + " * Returns the bitwise OR of this value and `x`.\n" + + " * @example {{{\n" + + " * (0xf0 | 0xaa) == 0xfa\n" + + " * // in binary: 11110000\n" + + " * // | 10101010\n" + + " * // --------\n" + + " * // 11111010\n" + + " * }}}\n" + + " */"), + Op("&", "/**\n" + + " * Returns the bitwise AND of this value and `x`.\n" + + " * @example {{{\n" + + " * (0xf0 & 0xaa) == 0xa0\n" + + " * // in binary: 11110000\n" + + " * // & 10101010\n" + + " * // --------\n" + + " * // 10100000\n" + + " * }}}\n" + + " */"), + Op("^", "/**\n" + + " * Returns the bitwise XOR of this value and `x`.\n" + + " * @example {{{\n" + + " * (0xf0 ^ 0xaa) == 0x5a\n" + + " * // in binary: 11110000\n" + + " * // ^ 10101010\n" + + " * // --------\n" + + " * // 01011010\n" + + " * }}}\n" + + " */")) + else Nil + + def shiftOps = + if (isInteger) + List( + Op("<<", "/**\n" + + " * Returns this value bit-shifted left by the specified number of bits,\n" + + " * filling in the new right bits with zeroes.\n" + + " * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}}\n" + + " */"), + + Op(">>>", "/**\n" + + " * Returns this value bit-shifted right by the specified number of bits,\n" + + " * filling the new left bits with zeroes.\n" + + " * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}}\n" + + " * @example {{{\n" + + " * -21 >>> 3 == 536870909\n" + + " * // in binary: 11111111 11111111 11111111 11101011 >>> 3 ==\n" + + " * // 00011111 11111111 11111111 11111101\n" + + " * }}}\n" + + " */"), + + Op(">>", "/**\n" + + " * Returns this value bit-shifted right by the specified number of bits,\n" + + " * filling in the left bits with the same value as the left-most bit of this.\n" + + " * The effect of this is to retain the sign of the value.\n" + + " * @example {{{\n" + + " * -21 >> 3 == -3\n" + + " * // in binary: 11111111 11111111 11111111 11101011 >> 3 ==\n" + + " * // 11111111 11111111 11111111 11111101\n" + + " * }}}\n" + + " */")) + else Nil + + def comparisonOps = List( + Op("==", "/** Returns `true` if this value is equal to x, `false` otherwise. */"), + Op("!=", "/** Returns `true` if this value is not equal to x, `false` otherwise. */"), + Op("<", "/** Returns `true` if this value is less than x, `false` otherwise. */"), + Op("<=", "/** Returns `true` if this value is less than or equal to x, `false` otherwise. */"), + Op(">", "/** Returns `true` if this value is greater than x, `false` otherwise. */"), + Op(">=", "/** Returns `true` if this value is greater than or equal to x, `false` otherwise. */")) + + def otherOps = List( + Op("+", "/** Returns the sum of this value and `x`. */"), + Op("-", "/** Returns the difference of this value and `x`. */"), + Op("*", "/** Returns the product of this value and `x`. */"), + Op("/", "/** Returns the quotient of this value and `x`. */"), + Op("%", "/** Returns the remainder of the division of this value by `x`. */")) + + // Given two numeric value types S and T, the operation type of S and T is defined as follows: + // If both S and T are subrange types then the operation type of S and T is Int. + // Otherwise the operation type of S and T is the larger of the two types w.r.t. the ordering + // of numeric types defined in `fullTypes` below. + // Given two numeric values v and w the operation type of v and w is the operation type + // of their run-time types. + def opType(that: AnyValNum): AnyValNum = { + val fullTypes = IndexedSeq(I, L, F, D) + (fullTypes.indexOf(this), fullTypes.indexOf(that)) match { + case (-1, -1) => I // both are subrange types + case (-1, _) => that // one is subrange + case (_, -1) => this + case (r1, r2) => fullTypes(r1.max(r2)) // use the larger type + } + } + + def mkCoercions = numeric.map(x => "def to%s: %s".format(x, x)) + + def mkUnaryOps = unaryOps.map(x => "%s\n def unary_%s : %s".format(x.doc, x.op, this opType I)) + + def mkStringOps = List( + "@deprecated(\"Adding a number and a String is deprecated. Use the string interpolation `s\\\"$num$str\\\"`\", \"2.13.0\")\n def +(x: String): String" + ) + + def mkShiftOps = ( + for (op <- shiftOps ; arg <- List(I, L)) yield { + val doc = op.doc + (if (this == L || arg == I) "" else "\n @deprecated(\"shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.\", \"2.12.7\")") + "%s\n def %s(x: %s): %s".format(doc, op.op, arg, this opType I) + } + ) + + def clumps: List[List[String]] = { + val xs1 = List(mkCoercions, mkUnaryOps, mkStringOps, mkShiftOps) + .filter(!_.isEmpty) + .map(_ :+ "") // add a trailing empty line + val xs2 = List( + mkBinOpsGroup(comparisonOps, numeric, _ => Z), + mkBinOpsGroup(bitwiseOps, integer, this opType _), + mkBinOpsGroup(otherOps, numeric, this opType _) + ) + xs1 ++ xs2 + } + + def classLines = { + val groups = (clumps :+ commonClassLines).filter(!_.isEmpty) + val interpolated = groups.map(_.map { + case "" => "" + case s => interpolate(s) + }) + interpolated.flatten + } + + def objectLines = { + val comp = if (isInteger) integerCompanion else floatingCompanion + interpolate(comp + allCompanions + "\n" + nonUnitCompanions).trim.linesIterator.toList ++ (implicitCoercions map interpolate) + } + + /** Makes a set of binary operations based on the given set of ops, args, and resultFn. + * + * @param ops list of function names e.g. List(">>", "%") + * @param args list of types which should appear as arguments + * @param resultFn function which calculates return type based on arg type + * @return list of function definitions + */ + def mkBinOpsGroup(ops: List[Op], args: List[AnyValNum], resultFn: AnyValNum => AnyValRep): List[String] = ( + ops flatMap (op => + args.map(arg => + "%s\n def %s(x: %s): %s".format(op.doc, op.op, arg, resultFn(arg))) :+ "" + ) + ).toList + } + + sealed abstract class AnyValRep(val name: String, val repr: Option[String], val javaEquiv: String) { + def classLines: List[String] + def objectLines: List[String] + def commonClassLines = List( + "// Provide a more specific return type for Scaladoc", + "override def getClass(): Class[@name@] = ???" + ) + + def lcname = name.toLowerCase + def boxedSimpleName = this match { + case C => "Character" + case I => "Integer" + case _ => name + } + def boxedName = this match { + case U => "scala.runtime.BoxedUnit" + case _ => "java.lang." + boxedSimpleName + } + def zeroRep = this match { + case L => "0L" + case F => "0.0f" + case D => "0.0d" + case _ => "0" + } + + def representation = repr.map(", a " + _).getOrElse("") + + def indent(s: String) = if (s == "") "" else " " + s + def indentN(s: String) = s.linesIterator map indent mkString "\n" + + def boxUnboxInterpolations = Map( + "@boxRunTimeDoc@" -> """ + * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + *""".format(boxedSimpleName), + "@unboxRunTimeDoc@" -> """ + * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + *""".format(name), + "@unboxDoc@" -> "the %s resulting from calling %sValue() on `x`".format(name, lcname), + "@boxImpl@" -> "???", + "@unboxImpl@" -> "???" + ) + def interpolations = Map( + "@article@" -> (if (this == I) "an" else "a"), + "@name@" -> name, + "@representation@" -> representation, + "@javaequiv@" -> javaEquiv, + "@boxed@" -> boxedName, + "@lcname@" -> lcname, + "@zero@" -> zeroRep + ) ++ boxUnboxInterpolations + + def interpolate(s: String): String = interpolations.foldLeft(s) { + case (str, (key, value)) => str.replaceAll(key, value) + } + def classDoc = interpolate(classDocTemplate) + def objectDoc = "" + def mkImports = "" + + def mkClass = assemble("final abstract class " + name + " private extends AnyVal", classLines) + def mkObject = assemble("object " + name + " extends AnyValCompanion", objectLines) + def make() = List[String]( + headerTemplate, + mkImports, + classDoc, + mkClass, + objectDoc, + mkObject + ) mkString "" + + def assemble(decl: String, lines: List[String]): String = { + val body = if (lines.isEmpty) " { }\n\n" else lines.map(indent).mkString(" {\n", "\n", "\n}\n") + decl + body + "\n" + } + + override def toString = name + } +} + +trait GenerateAnyValTemplates { + def headerTemplate = """/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +// DO NOT EDIT, CHANGES WILL BE LOST +// This auto-generated code can be modified in "project/GenerateAnyVals.scala". +// Afterwards, running "sbt generateSources" regenerates this source file. + +package scala + +import scala.language.`2.13` + +""" + + def classDocTemplate = (""" +/** `@name@`@representation@ (equivalent to Java's `@javaequiv@` primitive type) is a + * subtype of [[scala.AnyVal]]. Instances of `@name@` are not + * represented by an object in the underlying runtime system. + * + * There is an implicit conversion from [[scala.@name@]] => [[scala.runtime.Rich@name@]] + * which provides useful non-primitive operations. + */ +""".trim + "\n") + + def allCompanions = """ +/** Transforms a value type into a boxed reference type. + *@boxRunTimeDoc@ + * @param x the @name@ to be boxed + * @return a @boxed@ offering `x` as its underlying value. + */ +def box(x: @name@): @boxed@ = @boxImpl@ + +/** Transforms a boxed type into a value type. Note that this + * method is not typesafe: it accepts any Object, but will throw + * an exception if the argument is not a @boxed@. + *@unboxRunTimeDoc@ + * @param x the @boxed@ to be unboxed. + * @throws ClassCastException if the argument is not a @boxed@ + * @return @unboxDoc@ + */ +def unbox(x: java.lang.Object): @name@ = @unboxImpl@ + +/** The String representation of the scala.@name@ companion object. */ +override def toString = "object scala.@name@" +""" + + def nonUnitCompanions = "" // todo + + def integerCompanion = """ +/** The smallest value representable as @article@ @name@. */ +final val MinValue = @boxed@.MIN_VALUE + +/** The largest value representable as @article@ @name@. */ +final val MaxValue = @boxed@.MAX_VALUE +""" + + def floatingCompanion = """ +/** The smallest positive value greater than @zero@ which is + * representable as a @name@. + */ +final val MinPositiveValue = @boxed@.MIN_VALUE +final val NaN = @boxed@.NaN +final val PositiveInfinity = @boxed@.POSITIVE_INFINITY +final val NegativeInfinity = @boxed@.NEGATIVE_INFINITY + +/** The negative number with the greatest (finite) absolute value which is representable + * by a @name@. Note that it differs from [[java.lang.@name@.MIN_VALUE]], which + * is the smallest positive value representable by a @name@. In Scala that number + * is called @name@.MinPositiveValue. + */ +final val MinValue = -@boxed@.MAX_VALUE + +/** The largest finite positive number representable as a @name@. */ +final val MaxValue = @boxed@.MAX_VALUE +""" +} + +class GenerateAnyVals extends GenerateAnyValReps with GenerateAnyValTemplates { + object B extends AnyValNum("Byte", Some("8-bit signed integer"), "byte") + object S extends AnyValNum("Short", Some("16-bit signed integer"), "short") + object C extends AnyValNum("Char", Some("16-bit unsigned integer"), "char") + object I extends AnyValNum("Int", Some("32-bit signed integer"), "int") + object L extends AnyValNum("Long", Some("64-bit signed integer"), "long") + object F extends AnyValNum("Float", Some("32-bit IEEE-754 floating point number"), "float") + object D extends AnyValNum("Double", Some("64-bit IEEE-754 floating point number"), "double") + object Z extends AnyValRep("Boolean", None, "boolean") { + def classLines = """ +/** Negates a Boolean expression. + * + * - `!a` results in `false` if and only if `a` evaluates to `true` and + * - `!a` results in `true` if and only if `a` evaluates to `false`. + * + * @return the negated expression + */ +def unary_! : Boolean + +/** Compares two Boolean expressions and returns `true` if they evaluate to the same value. + * + * `a == b` returns `true` if and only if + * - `a` and `b` are `true` or + * - `a` and `b` are `false`. + */ +def ==(x: Boolean): Boolean + +/** + * Compares two Boolean expressions and returns `true` if they evaluate to a different value. + * + * `a != b` returns `true` if and only if + * - `a` is `true` and `b` is `false` or + * - `a` is `false` and `b` is `true`. + */ +def !=(x: Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true. + * + * `a || b` returns `true` if and only if + * - `a` is `true` or + * - `b` is `true` or + * - `a` and `b` are `true`. + * + * @note This method uses 'short-circuit' evaluation and + * behaves as if it was declared as `def ||(x: => Boolean): Boolean`. + * If `a` evaluates to `true`, `true` is returned without evaluating `b`. + */ +def ||(x: Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if both of them evaluate to true. + * + * `a && b` returns `true` if and only if + * - `a` and `b` are `true`. + * + * @note This method uses 'short-circuit' evaluation and + * behaves as if it was declared as `def &&(x: => Boolean): Boolean`. + * If `a` evaluates to `false`, `false` is returned without evaluating `b`. + */ +def &&(x: Boolean): Boolean + +// Compiler won't build with these seemingly more accurate signatures +// def ||(x: => Boolean): Boolean +// def &&(x: => Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true. + * + * `a | b` returns `true` if and only if + * - `a` is `true` or + * - `b` is `true` or + * - `a` and `b` are `true`. + * + * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`. + */ +def |(x: Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if both of them evaluate to true. + * + * `a & b` returns `true` if and only if + * - `a` and `b` are `true`. + * + * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`. + */ +def &(x: Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if they evaluate to a different value. + * + * `a ^ b` returns `true` if and only if + * - `a` is `true` and `b` is `false` or + * - `a` is `false` and `b` is `true`. + */ +def ^(x: Boolean): Boolean + +// Provide a more specific return type for Scaladoc +override def getClass(): Class[Boolean] = ??? + """.trim.linesIterator.toList + + def objectLines = interpolate(allCompanions + "\n" + nonUnitCompanions).linesIterator.toList + } + object U extends AnyValRep("Unit", None, "void") { + override def classDoc = """ +/** `Unit` is a subtype of [[scala.AnyVal]]. There is only one value of type + * `Unit`, `()`, and it is not represented by any object in the underlying + * runtime system. A method with return type `Unit` is analogous to a Java + * method which is declared `void`. + */ +""".trim + "\n" + def classLines = List( + "// Provide a more specific return type for Scaladoc", + "override def getClass(): Class[Unit] = ???" + ) + def objectLines = interpolate(allCompanions).linesIterator.toList + + private def nono = "`Unit` companion object is not allowed in source; instead, use `()` for the unit value" + override def mkObject = s"""@scala.annotation.compileTimeOnly("$nono")\n${super.mkObject}""" + + override def boxUnboxInterpolations = Map( + "@boxRunTimeDoc@" -> """ + * This method is not intended for use in source code. + * The runtime representation of this value is platform specific. + *""", + "@unboxRunTimeDoc@" -> """ + * This method is not intended for use in source code. + * The result of successfully unboxing a value is `()`. + *""", + "@unboxDoc@" -> "the Unit value ()", + "@boxImpl@" -> "scala.runtime.BoxedUnit.UNIT", + "@unboxImpl@" -> "x.asInstanceOf[scala.runtime.BoxedUnit]" + ) + } + + def isSubrangeType = Set(B, S, C) + def isIntegerType = Set(B, S, C, I, L) + def isFloatingType = Set(F, D) + def isWideType = Set(L, D) + + def integer = numeric filter isIntegerType + def numeric = List(B, S, C, I, L, F, D) + def values = List(U, Z) ++ numeric + + def make() = values map (x => (x.name, x.make())) +} + +object GenerateAnyVals { + def run(outDir: java.io.File): Unit = { + val av = new GenerateAnyVals + + av.make() foreach { case (name, code ) => + val file = new java.io.File(outDir, name + ".scala") + sbt.IO.write(file, code, java.nio.charset.StandardCharsets.UTF_8, false) + } + } +} diff --git a/project/GenerateFunctionConverters.scala b/project/GenerateFunctionConverters.scala new file mode 100644 index 000000000000..5bdc04175969 --- /dev/null +++ b/project/GenerateFunctionConverters.scala @@ -0,0 +1,450 @@ +package dotty.tools + +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +object GenerateFunctionConverters { + import scala.tools.nsc._ + + case class Artifact(name: String, content: String) + + val copyright = + s"""/* + | * Scala (https://www.scala-lang.org) + | * + | * Copyright EPFL and Lightbend, Inc. dba Akka + | * + | * Licensed under Apache License 2.0 + | * (http://www.apache.org/licenses/LICENSE-2.0). + | * + | * See the NOTICE file distributed with this work for + | * additional information regarding copyright ownership. + | */ + | + |// GENERATED CODE: DO NOT EDIT. + |""".stripMargin + + val packaging = "package scala.jdk" + + val settings = new Settings(msg => sys.error(msg)) + def us(cl: ClassLoader): List[String] = cl match { + case ucl: java.net.URLClassLoader => ucl.getURLs.map(u => new java.io.File(u.toURI).getAbsolutePath).toList ::: us(ucl.getParent) + case _ => Nil + } + settings.classpath.value = us(settings.getClass.getClassLoader).mkString(java.io.File.pathSeparator) + val compiler = new Global(settings) + val run = new compiler.Run + + import compiler._, definitions._ + + locally { + // make sure `java.lang.Double` prints as `java.lang.Double`, not just `Double` (which resolves to `scala.Double`) + val f = classOf[scala.reflect.internal.Definitions#DefinitionsClass].getDeclaredField("UnqualifiedOwners") + f.setAccessible(true) + f.set(definitions, definitions.UnqualifiedOwners.filter(_.fullNameString != "java.lang")) + } + + def primitiveBox(tp: Type): Type = tp.typeSymbol match { + case UnitClass => BoxedUnitClass.tpe + case ByteClass => BoxedByteClass.tpe + case ShortClass => BoxedShortClass.tpe + case CharClass => BoxedCharacterClass.tpe + case IntClass => BoxedIntClass.tpe + case LongClass => BoxedLongClass.tpe + case FloatClass => BoxedFloatClass.tpe + case DoubleClass => BoxedDoubleClass.tpe + case BooleanClass => BoxedBooleanClass.tpe + case _ => tp + } + + implicit class IndentMe(v: Vector[String]) { + /** Adds indentation to every line in the vector. */ + def indent: Vector[String] = v.map(" " + _) + } + + implicit class WriteToBuilder(v: Vector[Vector[String]]) { + /** Writes the vector into a builder, with `join` inserted between each group. */ + def writeTo(builder: collection.mutable.Builder[String, Vector[String]], join: String = "") = { + var first = true + for (vi <- v) { + if (!first) builder += join + first = false + builder ++= vi + } + } + } + + implicit class FlattenMe(v: Vector[Vector[String]]) { + /** Joins the given `Vector[Vector[String]]`, with `join` inserted in between. */ + def mkVec(join: String = ""): Vector[String] = { + val vb = Vector.newBuilder[String] + v.writeTo(vb) + vb.result() + } + } + + implicit class DoubleFlattenMe(v: Vector[Vector[Vector[String]]]) { + /** Like `mkVec`, but done twice, with `join` inserted twice between outer blocks. */ + def mkVecVec(join: String = ""): Vector[String] = { + val vb = Vector.newBuilder[String] + var first = true + for (vi <- v) { + if (!first) { vb += join; vb += join } + first = false + vi.writeTo(vb, join) + } + vb.result() + } + } + + implicit class StringExtensions(s: String) { + // work around scala/bug#11125 + /** Splits the string into a `Vector` of lines. */ + def toVec = Predef.augmentString(s).lines.toVector + /** Is the string a blank line? */ + def nonBlank = s.trim.length > 0 + } + + implicit class TreeToText(t: Tree) { + // work around scala/bug#11125 + def text = Predef.augmentString(showCode(t).replace("$", "")).lines.toVector + } + + /** Blocks of code with priority (lower priority has higher precedence in implicit search). */ + case class Prioritized(lines: Vector[String], priority: Int) { + def withPriority(i: Int) = copy(priority = i) + } + + /** Contains code for conversion between functions and SAMs (single abstract methods). */ + case class SamConversionCode( + base: String, + wrappedAsScala: Vector[String], + asScalaAnyVal: Vector[String], + implicitToScala: Vector[String], + asScalaDef: Vector[String], + wrappedAsJava: Vector[String], + asJavaAnyVal: Vector[String], + implicitToJava: Prioritized, + asJavaDef: Vector[String] + ) { + def impls: Vector[Vector[String]] = Vector(wrappedAsScala, asScalaAnyVal, wrappedAsJava, asJavaAnyVal) + def defs: Vector[Vector[String]] = Vector(asScalaDef, asJavaDef) + def withPriority(i: Int): SamConversionCode = copy(implicitToJava = implicitToJava.withPriority(i)) + } + object SamConversionCode { + def apply(scc: SamConversionCode*): (Vector[String], Vector[String], Vector[Vector[String]]) = { + val sccDepthSet = scc.map(_.implicitToJava.priority).to[collection.SortedSet] + val codes = + { + val size = sccDepthSet.size + // normalize the elements to a fix [0..size-1] range + if (sccDepthSet.firstKey != 0 || sccDepthSet.lastKey != size - 1) { + val sccDepthMap = sccDepthSet.iterator.zipWithIndex.toMap + scc.map(x => x.withPriority(sccDepthMap(x.implicitToJava.priority))) + } + else scc + }.toVector.sortBy(_.base) + def priorityName(n: Int): String = s"Priority${n}FunctionExtensions" + def priorityHeader(n: Int): String = { + val pre = s"trait ${priorityName(n)}" + if (n < sccDepthSet.size - 1) s"$pre extends ${priorityName(n+1)}" else pre + } + val impls = + "object FunctionWrappers {" +: { + codes.map(_.impls).mkVecVec().indent + } :+ "}" + val explicitDefs = codes.map(_.defs).mkVecVec() + val traits = codes.groupBy(_.implicitToJava.priority).toVector.sortBy(- _._1).map{ case (k,vs) => + s"import language.implicitConversions" +: + "" +: + s"${priorityHeader(k)} {" +: + s" import FunctionWrappers._" +: + s" " +: + { + vs.map(_.implicitToJava.lines).mkVec().indent ++ + ( + if (k == 0) Vector.fill(3)(" ") ++ codes.map(_.implicitToScala).mkVec().indent + else Vector() + ) + } :+ + s"}" + } + (impls, explicitDefs, traits) + } + } + + private def buildWrappersViaReflection: Seq[SamConversionCode] = { + + val pack: Symbol = rootMirror.getPackageIfDefined("java.util.function") + + case class Jfn(iface: Symbol, sam: Symbol) { + lazy val genericCount = iface.typeParams.length + lazy val name = sam.name.toTermName + lazy val title = iface.name.encoded + lazy val params = sam.info.params + lazy val sig = sam typeSignatureIn iface.info + lazy val pTypes = sig.params.map(_.info) + lazy val rType = sig.resultType + def arity = params.length + } + + val sams = pack.info.decls. + map(d => (d, d.typeSignature.members.filter(_.isAbstract).toList)). + collect{ case (d, m :: Nil) if d.isAbstract => Jfn(d, m) } + + def generate(jfn: Jfn): SamConversionCode = { + def mkRef(tp: Type): Tree = if (tp.typeSymbol.isTypeParameter) Ident(tp.typeSymbol.name.toTypeName) else tq"$tp" + + // Types for the Java SAM and the corresponding Scala function, plus all type parameters + val scalaType = gen.mkAttributedRef(FunctionClass(jfn.arity)) + val javaType = gen.mkAttributedRef(jfn.iface) + val tnParams: List[TypeName] = jfn.iface.typeParams.map(_.name.toTypeName) + val tdParams: List[TypeDef] = tnParams.map(TypeDef(NoMods, _, Nil, EmptyTree)) + val javaTargs: List[Tree] = tdParams.map(_.name).map(Ident(_)) + val scalaTargTps = jfn.pTypes :+ jfn.rType + val scalaTargBoxedTps = scalaTargTps.map(primitiveBox) + val scalaTargs: List[Tree] = scalaTargTps.map(mkRef) + val scalaTargsBoxed: List[Tree] = scalaTargBoxedTps.map(mkRef) + val boxComment = + if (scalaTargTps.map(_.typeSymbol) != scalaTargBoxedTps.map(_.typeSymbol)) + Literal(Constant("primitiveComment")) + else + Literal(Constant("noComment")) + + // Conversion wrappers have three or four components that we need to name + // (1) The wrapper class that wraps a Java SAM as Scala function, or vice versa (ClassN) + // (2) A value class that provides .asJava or .asScala to request the conversion (ValCN) + // (3) A name for an explicit conversion method (DefN) + // (4) An implicit conversion method name (ImpN) that invokes the value class + + // Names for Java conversions to Scala + val j2sClassN = TypeName("FromJava" + jfn.title) + val j2sCompanionN = j2sClassN.toTermName + val j2sValCN = TypeName("Rich" + jfn.title + "As" + scalaType.name.encoded) + val j2sDefN = TermName("asScalaFrom" + jfn.title) + val j2sImpN = TermName("enrichAsScalaFrom" + jfn.title) + + // Names for Scala conversions to Java + val s2jAsJavaTitle = TermName("asJava" + jfn.title) + val s2jClassN = TypeName("AsJava" + jfn.title) + val s2jCompanionN = s2jClassN.toTermName + val s2jValCN = TypeName("Rich" + scalaType.name.encoded + "As" + jfn.title) + val s2jDefN = TermName("asJava" + jfn.title) + val s2jImpN = TermName("enrichAsJava" + jfn.title) + + // Argument lists for the function / SAM + val vParams = (jfn.params zip jfn.pTypes).map{ case (p,t) => + ValDef(NoMods, p.name.toTermName, if (t.typeSymbol.isTypeParameter) Ident(t.typeSymbol.name) else gen.mkAttributedRef(t.typeSymbol), EmptyTree) + } + val vParamRefs = vParams.map(_.name).map(Ident(_)) + + val j2sClassTree = + q"""case class $j2sClassN[..$tdParams](jf: $javaType[..$javaTargs]) extends $scalaType[..$scalaTargs] { + def apply(..$vParams) = jf.${jfn.name}(..$vParamRefs) + }""" + + val j2sValCTree = + q"""class $j2sValCN[..$tdParams](private val underlying: $javaType[..$javaTargs]) extends AnyVal { + @inline def asScala: $scalaType[..$scalaTargs] = underlying match { + case $s2jCompanionN(sf) => sf.asInstanceOf[$scalaType[..$scalaTargs]] + case _ => new $j2sClassN[..$tnParams](underlying) + } + }""" + + val j2sDefTree = + q"""@deprecated($boxComment) @inline def $j2sDefN[..$tdParams](jf: $javaType[..$javaTargs]): $scalaType[..$scalaTargsBoxed] = jf match { + case $s2jCompanionN(f) => f.asInstanceOf[$scalaType[..$scalaTargsBoxed]] + case _ => new $j2sClassN[..$tnParams](jf).asInstanceOf[$scalaType[..$scalaTargsBoxed]] + }""" + + val j2sImpTree = + q"""@inline implicit def $j2sImpN[..$tdParams](jf: $javaType[..$javaTargs]): $j2sValCN[..$tnParams] = new $j2sValCN[..$tnParams](jf)""" + + val s2jClassTree = + q"""case class $s2jClassN[..$tdParams](sf: $scalaType[..$scalaTargs]) extends $javaType[..$javaTargs] { + def ${jfn.name}(..$vParams) = sf.apply(..$vParamRefs) + }""" + + val s2jDefTree = + q"""@deprecated($boxComment) @inline def $s2jDefN[..$tdParams](sf: $scalaType[..$scalaTargsBoxed]): $javaType[..$javaTargs] = (sf: AnyRef) match { + case $j2sCompanionN(f) => f.asInstanceOf[$javaType[..$javaTargs]] + case _ => new $s2jClassN[..$tnParams](sf.asInstanceOf[$scalaType[..$scalaTargs]]) + }""" + + // This is especially tricky because functions are contravariant in their arguments + // Need to prevent e.g. Any => String from "downcasting" itself to Int => String; we want the more exact conversion + val (s2jImpTree, priority) = + if (jfn.pTypes.forall(! _.isFinalType) && jfn.sig == jfn.sam.typeSignature) + ( + q"""@inline implicit def $s2jImpN[..$tdParams](sf: $scalaType[..$scalaTargs]): $s2jValCN[..$tnParams] = new $s2jValCN[..$tnParams](sf)""", + tdParams.length + ) + else { + // Some types are not generic or are re-used; we had better catch those. + // Making up new type names, so switch everything to TypeName or TypeDef + // Instead of foo[A](f: (Int, A) => Long): Fuu[A] = new Foo[A](f) + // we want foo[X, A](f: (X, A) => Long)(implicit evX: Int =:= X): Fuu[A] = new Foo[A](f.asInstanceOf[(Int, A) => Long]) + // Instead of bar[A](f: A => A): Brr[A] = new Foo[A](f) + // we want bar[A, B](f: A => B)(implicit evB: A =:= B): Brr[A] = new Foo[A](f.asInstanceOf[A => B]) + val An = "A(\\d+)".r + val numberedA = collection.mutable.Set.empty[Int] + val evidences = collection.mutable.ArrayBuffer.empty[(TypeName, TypeName)] + numberedA ++= scalaTargs.map(_.toString).collect{ case An(digits) if (digits.length < 10) => digits.toInt } + val scalafnTnames = (jfn.pTypes :+ jfn.rType).zipWithIndex.map{ + case (pt, i) if (i < jfn.pTypes.length && pt.isFinalType) || (!pt.isFinalType && jfn.pTypes.take(i).exists(_ == pt)) => + val j = Iterator.from(i).dropWhile(numberedA).next + val genericName = TypeName(s"A$j") + numberedA += j + evidences += ((genericName, pt.typeSymbol.name.toTypeName)) + genericName + case (pt, _) => pt.typeSymbol.name.toTypeName + } + val scalafnTdefs = scalafnTnames. + map(TypeDef(NoMods, _, Nil, EmptyTree)). + dropRight(if (jfn.rType.isFinalType) 1 else 0) + val evs = evidences.map{ case (generic, specific) => ValDef(NoMods, TermName("ev"+generic.toString), tq"$generic =:= $specific", EmptyTree) } + val tree = + q"""@inline implicit def $s2jImpN[..$scalafnTdefs](sf: $scalaType[..$scalafnTnames])(implicit ..$evs): $s2jValCN[..$tnParams] = + new $s2jValCN[..$tnParams](sf.asInstanceOf[$scalaType[..$scalaTargs]]) + """ + (tree, tdParams.length) + } + + val s2jValFullNameAsJavaMethodTree = + if (priority > 0) + q"""@inline def $s2jAsJavaTitle: $javaType[..$javaTargs] = underlying match { + case $j2sCompanionN(sf) => sf.asInstanceOf[$javaType[..$javaTargs]] + case _ => new $s2jClassN[..$tnParams](underlying) + }""" + else EmptyTree + + val s2jValCTree = + q"""class $s2jValCN[..$tdParams](private val underlying: $scalaType[..$scalaTargs]) extends AnyVal { + @inline def asJava: $javaType[..$javaTargs] = underlying match { + case $j2sCompanionN(jf) => jf.asInstanceOf[$javaType[..$javaTargs]] + case _ => new $s2jClassN[..$tnParams](underlying) + } + $s2jValFullNameAsJavaMethodTree + }""" + + SamConversionCode( + base = jfn.title, + wrappedAsScala = j2sClassTree.text, + asScalaAnyVal = j2sValCTree.text, + implicitToScala = j2sImpTree.text, + asScalaDef = j2sDefTree.text, + wrappedAsJava = s2jClassTree.text, + asJavaAnyVal = s2jValCTree.text, + implicitToJava = Prioritized(s2jImpTree.text, priority), + asJavaDef = s2jDefTree.text + ) + } + + sams.toSeq.map(generate) + } + + def sourceFile(subPack: String, body: String): String = { + val body1 = if (body.startsWith("import "))body else "\n" + body + s"""$copyright + | + |$packaging$subPack + | + |import scala.language.`2.13` + |$body1 + |""".stripMargin + } + + def sameText(f: java.io.File, text: String): Boolean = { + val x = scala.io.Source.fromFile(f) + val lines = try { x.getLines().toVector } finally { x.close } + // work around scala/bug#11125 + lines.iterator.filter(_.nonBlank) == Predef.augmentString(text).lines.filter(_.nonBlank) + } + + def write(outDir: java.io.File, artifact: Artifact): Unit = { + val f = scala.tools.nsc.io.Path(outDir.getAbsolutePath) / artifact.name + if (!f.exists || !sameText(f.jfile, artifact.content)) { + f.parent.createDirectory(force = true) + f.toFile writeAll artifact.content + } + } + + def run(outDir: java.io.File): Unit = { + val (impls, explicitDefs, defss) = SamConversionCode(buildWrappersViaReflection: _*) + val javaFunConvsNoComments = + s"""/** This object contains methods that convert between Scala and Java function types. + | * + | * The explicit conversion methods defined here are intended to be used in Java code. For Scala + | * code, it is recommended to use the extension methods defined in [[scala.jdk.FunctionConverters]]. + | * + | * For details how the function converters work, see [[scala.jdk.FunctionConverters]]. + | * + | */ + |object FunctionConverters { + | import scala.jdk.FunctionWrappers._ + | + |${explicitDefs.indent.mkString("\n")} + |}""".stripMargin + + // cannot generate comments with quasiquotes + val javaFunConvs = javaFunConvsNoComments.replace(""" @deprecated("primitiveComment") """, + s""" /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + | * primitive type `scala.X` to improve compatibility when using it in Java code (the + | * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + | * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + | * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + | */ + |""".stripMargin + " ").replace("""@deprecated("noComment") """, "") + + val scalaFunConvs = + """/** This object provides extension methods that convert between Scala and Java function types. + | * + | * When writing Java code, use the explicit conversion methods defined in + | * [[javaapi.FunctionConverters]] instead. + | * + | * Using the `.asJava` extension method on a Scala function produces the most specific possible + | * Java function type: + | * + | * {{{ + | * scala> import scala.jdk.FunctionConverters._ + | * scala> val f = (x: Int) => x + 1 + | * + | * scala> val jf1 = f.asJava + | * jf1: java.util.function.IntUnaryOperator = ... + | * }}} + | * + | * More generic Java function types can be created using the corresponding `asJavaXYZ` extension + | * method: + | * + | * {{{ + | * scala> val jf2 = f.asJavaFunction + | * jf2: java.util.function.Function[Int,Int] = ... + | * + | * scala> val jf3 = f.asJavaUnaryOperator + | * jf3: java.util.function.UnaryOperator[Int] = ... + | * }}} + | * + | * Converting a Java function to Scala is done using the `asScala` extension method: + | * + | * {{{ + | * scala> List(1,2,3).map(jf2.asScala) + | * res1: List[Int] = List(2, 3, 4) + | * }}} + | */ + |object FunctionConverters extends Priority0FunctionExtensions""".stripMargin + + write(outDir, Artifact("jdk/javaapi/FunctionConverters.scala", sourceFile(".javaapi", javaFunConvs))) + write(outDir, Artifact("jdk/FunctionConverters.scala", sourceFile("", scalaFunConvs))) + write(outDir, Artifact("jdk/FunctionWrappers.scala", sourceFile("", impls.mkString("\n")))) + write(outDir, Artifact("jdk/FunctionExtensions.scala", sourceFile("", defss.map(_.mkString("\n")).mkString("\n\n\n\n")))) + } +} diff --git a/project/GenerateLibraryNTemplates.scala b/project/GenerateLibraryNTemplates.scala new file mode 100644 index 000000000000..94c026a98d0f --- /dev/null +++ b/project/GenerateLibraryNTemplates.scala @@ -0,0 +1,486 @@ +package dotty.tools + +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +/** + * This program generates the ProductN, TupleN, FunctionN, and AbstractFunctionN, + * where 0 <= N <= MaxArity. Usage: sbt generateSources + */ +object GenerateLibraryNTemplates { + final val MaxArity = 22 + def arities = (1 to MaxArity).toList + + class Group(val name: String) { + def className(i: Int) = name + i + def fileName(i: Int) = className(i) + ".scala" + } + + def productFiles = arities map Product.make + def tupleFiles = arities map Tuple.make + def functionFiles = (0 :: arities) map Function.make + def absFunctionFiles = (0 :: arities) map AbstractFunction.make + def allfiles = productFiles ::: tupleFiles ::: functionFiles ::: absFunctionFiles + + trait Arity extends Group { + def i: Int // arity + + def typeArgsString(xs: Seq[String]) = xs.mkString("[", ", ", "]") + def typeArgsToTupleSyntacticSugarString(xs: Seq[String]) = xs.mkString("(", ", ", ")") + + def to = (1 to i).toList + def s = if (i == 1) "" else "s" + def className = name + i + def classAnnotation = "" + def fileName = className + ".scala" + def targs = to map ("T" + _) + def vdefs = to map ("v" + _) + def xdefs = to map ("x" + _) + def mdefs = to map ("_" + _) + def invariantArgs = typeArgsString(targs) + def covariantArgs = typeArgsString(targs map (covariantSpecs + "+" + _)) + def covariantSpecs = "" + def contravariantSpecs = "" + def contraCoArgs = typeArgsString((targs map (contravariantSpecs + "-" + _)) ::: List(covariantSpecs + "+R")) + def constructorArgs = (targs).map( _.toLowerCase ) mkString ", " + def fields = (mdefs, targs).zipped.map(_ + ": " + _) mkString ", " + def funArgs = (vdefs, targs).zipped.map(_ + ": " + _) mkString ", " + + def genprodString = " See scala.Function0 for timestamp." + def moreMethods = "" + def companionObject = "" + def packageDef = "scala" + def imports = "" + + def header = """/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +// GENERATED CODE: DO NOT EDIT.%s + +package %s + +import scala.language.`2.13` +%s +""".format(genprodString, packageDef, imports).trim + } + + def run(outDir: java.io.File): Unit = { + val out = outDir.getAbsolutePath + def writeFile(node: scala.xml.Node): Unit = { + import scala.tools.nsc.io._ + val f = Path(out) / node.attributes("name").toString + f.parent.createDirectory(force = true) + f.toFile writeAll node.text + } + allfiles foreach writeFile + } +} + +import GenerateLibraryNTemplates._ + + +/* zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + F U N C T I O N +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz */ + +object FunctionZero extends Function(0) { + override def genprodString = "\n// genprod generated these sources at: " + java.time.Instant.now() + override def covariantSpecs = "@specialized(Specializable.Primitives) " + override def descriptiveComment = " " + functionNTemplate.format("greeting", "anonfun0", +raw""" + * val name = "world" + * val greeting = () => s"hello, $$name" + * + * val anonfun0 = new Function0[String] { + * def apply(): String = s"hello, $$name" + * } + * assert(greeting() == anonfun0()) + * """) + override def moreMethods = "" +} + +object FunctionOne extends Function(1) { + override def classAnnotation = "@annotation.implicitNotFound(msg = \"No implicit view available from ${T1} => ${R}.\")\n" + override def contravariantSpecs = "@specialized(Specializable.Arg) " + override def covariantSpecs = "@specialized(Specializable.Return) " + + override def descriptiveComment = " " + functionNTemplate.format("succ", "anonfun1", +""" + * val succ = (x: Int) => x + 1 + * val anonfun1 = new Function1[Int, Int] { + * def apply(x: Int): Int = x + 1 + * } + * assert(succ(0) == anonfun1(0)) + * """) + """ + * + * Note that the difference between `Function1` and [[scala.PartialFunction]] + * is that the latter can specify inputs which it will not handle.""" + + override def moreMethods = """ + /** Composes two instances of `Function1` in a new `Function1`, with this function applied last. + * + * @tparam A the type to which function `g` can be applied + * @param g a function A => T1 + * @return a new function `f` such that `f(x) == apply(g(x))` + */ + @annotation.unspecialized def compose[A](g: A => T1): A => R = { x => apply(g(x)) } + + /** Composes two instances of `Function1` in a new `Function1`, with this function applied first. + * + * @tparam A the result type of function `g` + * @param g a function R => A + * @return a new function `f` such that `f(x) == g(apply(x))` + */ + @annotation.unspecialized def andThen[A](g: R => A): T1 => A = { x => g(apply(x)) } +""" + override def companionObject = +""" +object Function1 { + + implicit final class UnliftOps[A, B] private[Function1](private val f: A => Option[B]) extends AnyVal { + /** Converts an optional function to a partial function. + * + * @example Unlike [[Function.unlift]], this [[UnliftOps.unlift]] method can be used in extractors. + * {{{ + * val of: Int => Option[String] = { i => + * if (i == 2) { + * Some("matched by an optional function") + * } else { + * None + * } + * } + * + * util.Random.nextInt(4) match { + * case of.unlift(m) => // Convert an optional function to a pattern + * println(m) + * case _ => + * println("Not matched") + * } + * }}} + */ + def unlift: PartialFunction[A, B] = Function.unlift(f) + } + +} +""" +} + +object FunctionTwo extends Function(2) { + override def contravariantSpecs = "@specialized(Specializable.Args) " + override def covariantSpecs = "@specialized(Specializable.Return) " + + override def descriptiveComment = " " + functionNTemplate.format("max", "anonfun2", +""" + * val max = (x: Int, y: Int) => if (x < y) y else x + * + * val anonfun2 = new Function2[Int, Int, Int] { + * def apply(x: Int, y: Int): Int = if (x < y) y else x + * } + * assert(max(0, 1) == anonfun2(0, 1)) + * """) +} + +object Function { + def make(i: Int) = apply(i)() + def apply(i: Int) = i match { + case 0 => FunctionZero + case 1 => FunctionOne + case 2 => FunctionTwo + case _ => new Function(i) + } +} + +class Function(val i: Int) extends Group("Function") with Arity { + def descriptiveComment = "" + def functionNTemplate = +""" + * In the following example, the definition of `%s` is + * shorthand, conceptually, for the anonymous class definition + * `%s`, although the implementation details of how the + * function value is constructed may differ: + * + * {{{ + * object Main extends App {%s} + * }}}""" + + def toStr = "\"" + ("" format i) + "\"" + def apply() = { +{header} +{companionObject} +/** A function of {i} parameter{s}. + *{descriptiveComment} + */ +{classAnnotation}trait {className}{contraCoArgs} extends AnyRef {{ self => + /** Apply the body of this function to the argument{s}. + * @return the result of function application. + */ + def apply({funArgs}): R +{moreMethods} + override def toString(): String = {toStr} +}} + +} + + private def commaXs = xdefs.mkString("(", ", ", ")") + + // (x1: T1) => (x2: T2) => (x3: T3) => (x4: T4) => apply(x1,x2,x3,x4) + def shortCurry = { + val body = "apply" + commaXs + (xdefs, targs).zipped.map("(%s: %s) => ".format(_, _)).mkString("", "", body) + } + + // (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7) => self.apply(x1,x2,x3,x4,x5,x6,x7)).curried + def longCurry = ((xdefs, targs).zipped.map(_ + ": " + _) drop 1).mkString( + "(x1: T1) => ((", + ", ", + ") => self.apply%s).curried".format(commaXs) + ) + + // f(x1,x2,x3,x4,x5,x6) == (f.curried)(x1)(x2)(x3)(x4)(x5)(x6) + def curryComment = { +""" /** Creates a curried version of this function. + * + * @return a function `f` such that `f%s == apply%s` + */""".format(xdefs.map("(" + _ + ")").mkString, commaXs) + } + + def tupleMethod = { + def comment = +""" /** Creates a tupled version of this function: instead of %d arguments, + * it accepts a single [[scala.Tuple%d]] argument. + * + * @return a function `f` such that `f(%s) == f(Tuple%d%s) == apply%s` + */ +""".format(i, i, commaXs, i, commaXs, commaXs) + def body = "case (%s) => apply%s".format(commaXs, commaXs) + + comment + "\n @annotation.unspecialized def tupled: (%s) => R = {\n %s\n }".format( + typeArgsToTupleSyntacticSugarString(targs), body) + } + + def curryMethod = { + val body = if (i < 5) shortCurry else longCurry + + curryComment + + "\n @annotation.unspecialized def curried: %s => R = {\n %s\n }\n".format( + targs mkString " => ", body + ) + } + + override def moreMethods = curryMethod + tupleMethod +} // object Function + + +/* zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + T U P L E +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz */ + +object Tuple { + val zipImports = "" + + def make(i: Int) = apply(i)() + def apply(i: Int) = i match { + case 1 => TupleOne + case 2 => TupleTwo + case 3 => TupleThree + case _ => new Tuple(i) + } +} + +object TupleOne extends Tuple(1) +{ + override def covariantSpecs = "@specialized(Int, Long, Double) " +} + +object TupleTwo extends Tuple(2) +{ + override def imports = Tuple.zipImports + override def covariantSpecs = "@specialized(Int, Long, Double, Char, Boolean/*, AnyRef*/) " + override def moreMethods = """ + /** Swaps the elements of this `Tuple`. + * @return a new Tuple where the first element is the second element of this Tuple and the + * second element is the first element of this Tuple. + */ + def swap: Tuple2[T2,T1] = Tuple2(_2, _1) +""" +} + +object TupleThree extends Tuple(3) { + override def imports = Tuple.zipImports +} + +class Tuple(val i: Int) extends Group("Tuple") with Arity { + private def idiomatic = + if (i < 2) "" + else " Note that it is more idiomatic to create a %s via `(%s)`".format(className, constructorArgs) + + private def params = ( + 1 to i map (x => " * @param _%d Element %d of this Tuple%d".format(x, x, i)) + ) mkString "\n" + + // prettifies it a little if it's overlong + def mkToString() = { + def str(xs: List[String]) = xs.mkString(""" + "," + """) + if (i <= MaxArity / 2) str(mdefs) + else { + val s1 = str(mdefs take (i / 2)) + val s2 = str(mdefs drop (i / 2)) + s1 + " +\n \",\" + " + s2 + } + } + + def apply() = { +{header} + +/** A tuple of {i} elements; the canonical representation of a [[scala.{Product.className(i)}]]. + * + * @constructor Create a new tuple with {i} elements.{idiomatic} +{params} + */ +final case class {className}{covariantArgs}({fields}) + extends {Product.className(i)}{invariantArgs} +{{ + override def toString(): String = "(" + {mkToString} + ")" + {moreMethods} +}} +} +} // object Tuple + + +/* zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + P R O D U C T +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz */ + +object Product extends Group("Product") +{ + def make(i: Int) = apply(i)() + def apply(i: Int) = i match { + case 1 => ProductOne + case 2 => ProductTwo + case _ => new Product(i) + } +} + +object ProductOne extends Product(1) +{ + override def covariantSpecs = "@specialized(Int, Long, Double) " +} + +object ProductTwo extends Product(2) +{ + override def covariantSpecs = "@specialized(Int, Long, Double) " +} + +class Product(val i: Int) extends Group("Product") with Arity { + val productElementComment = s""" + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= $i). + */ +""" + + def cases = { + val xs = for ((x, i) <- mdefs.zipWithIndex) yield "case %d => %s".format(i, x) + val default = "case _ => throw new IndexOutOfBoundsException(s\"$n is out of bounds (min 0, max " + (i-1) +")\")" + "\n" + ((xs ::: List(default)).map(" " + _ + "\n").mkString) + } + def proj = { + (mdefs,targs).zipped.map( (_,_) ).zipWithIndex.map { case ((method,typeName),index) => + """| /** A projection of element %d of this Product. + | * @return A projection of element %d. + | */ + | def %s: %s + |""".stripMargin.format(index + 1, index + 1, method, typeName) + }.mkString + } + + def apply() = { +{header} + +object {className} {{ + def unapply{invariantArgs}(x: {className}{invariantArgs}): Option[{className}{invariantArgs}] = + Some(x) +}} + +/** {className} is a Cartesian product of {i} component{s}. + */ +trait {className}{covariantArgs} extends Any with Product {{ + /** The arity of this product. + * @return {i} + */ + override def productArity: Int = {i} + + {productElementComment} + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match {{ {cases} }} + +{proj} +{moreMethods} +}} +} + +} + +/** Abstract functions **/ + +object AbstractFunctionZero extends AbstractFunction(0) { + override def covariantSpecs = FunctionZero.covariantSpecs +} + +object AbstractFunctionOne extends AbstractFunction(1) { + override def covariantSpecs = FunctionOne.covariantSpecs + override def contravariantSpecs = FunctionOne.contravariantSpecs +} + +object AbstractFunctionTwo extends AbstractFunction(2) { + override def covariantSpecs = FunctionTwo.covariantSpecs + override def contravariantSpecs = FunctionTwo.contravariantSpecs +} + +class AbstractFunction(val i: Int) extends Group("AbstractFunction") with Arity +{ + override def packageDef = "scala.runtime" + + val superTypeArgs = typeArgsString(targs ::: List("R")) + + def apply() = { +{header} + +abstract class {className}{contraCoArgs} extends Function{i}{superTypeArgs} {{ +{moreMethods} +}} +} + +} +object AbstractFunction +{ + def make(i: Int) = apply(i)() + def apply(i: Int) = i match { + case 0 => AbstractFunctionZero + case 1 => AbstractFunctionOne + case 2 => AbstractFunctionTwo + case _ => new AbstractFunction(i) + } +} + diff --git a/project/ScalaLibraryPlugin.scala b/project/ScalaLibraryPlugin.scala index a0d4dda50883..c7670f7324af 100644 --- a/project/ScalaLibraryPlugin.scala +++ b/project/ScalaLibraryPlugin.scala @@ -81,7 +81,19 @@ object ScalaLibraryPlugin extends AutoPlugin { previous .withAnalysis(analysis.copy(stamps = stamps)) // update the analysis with the correct stamps .withHasModified(true) // mark it as updated for sbt to update its caches - } + }, + + // Generate (Product|TupleN|Function|AbstractFunction)*.scala files and scaladoc stubs for all AnyVal sources. + // They should really go into a managedSources dir instead of overwriting sources checked into git but scaladoc + // source links (could be fixed by shipping these sources with the scaladoc bundles) and scala-js source maps + // rely on them being on github. + commands += Command.command("generateSources") { state => + val dir = ((ThisBuild / baseDirectory).value / "library" / "src" / "scala").getAbsoluteFile + dotty.tools.GenerateLibraryNTemplates.run(dir) + dotty.tools.GenerateAnyVals.run(dir) + dotty.tools.GenerateFunctionConverters.run(dir) + state + }, ) private lazy val filesToCopy = Set(