From f213effa5569b2a725cb8fd9c0bf9d474330cef7 Mon Sep 17 00:00:00 2001 From: soronpo Date: Thu, 26 Aug 2021 13:45:35 -0400 Subject: [PATCH 1/9] Adds compiletime.ops.{long, float, double}, adds other ops, and fixes termref type not being considered. fix check file more ops wip Added ops.float and ops.double --- .../dotty/tools/dotc/core/Definitions.scala | 35 ++- .../src/dotty/tools/dotc/core/StdNames.scala | 55 +++-- .../src/dotty/tools/dotc/core/Types.scala | 134 ++++++++++- library/src/scala/compiletime/ops/any.scala | 19 ++ .../src/scala/compiletime/ops/double.scala | 136 +++++++++++ library/src/scala/compiletime/ops/float.scala | 136 +++++++++++ library/src/scala/compiletime/ops/int.scala | 39 +++ library/src/scala/compiletime/ops/long.scala | 222 ++++++++++++++++++ .../src/scala/compiletime/ops/string.scala | 29 +++ tests/neg/singleton-ops-any.check | 14 ++ tests/neg/singleton-ops-any.scala | 20 ++ tests/neg/singleton-ops-double.scala | 77 ++++++ tests/neg/singleton-ops-float.scala | 77 ++++++ tests/neg/singleton-ops-int.scala | 22 +- tests/neg/singleton-ops-long.scala | 113 +++++++++ tests/neg/singleton-ops-string.scala | 10 + 16 files changed, 1099 insertions(+), 39 deletions(-) create mode 100644 library/src/scala/compiletime/ops/double.scala create mode 100644 library/src/scala/compiletime/ops/float.scala create mode 100644 library/src/scala/compiletime/ops/long.scala create mode 100644 tests/neg/singleton-ops-double.scala create mode 100644 tests/neg/singleton-ops-float.scala create mode 100644 tests/neg/singleton-ops-long.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 60f375c116fc..3810e9e84d8a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -246,6 +246,9 @@ class Definitions { @tu lazy val CompiletimeOpsPackage: Symbol = requiredPackage("scala.compiletime.ops") @tu lazy val CompiletimeOpsAnyModuleClass: Symbol = requiredModule("scala.compiletime.ops.any").moduleClass @tu lazy val CompiletimeOpsIntModuleClass: Symbol = requiredModule("scala.compiletime.ops.int").moduleClass + @tu lazy val CompiletimeOpsLongModuleClass: Symbol = requiredModule("scala.compiletime.ops.long").moduleClass + @tu lazy val CompiletimeOpsFloatModuleClass: Symbol = requiredModule("scala.compiletime.ops.float").moduleClass + @tu lazy val CompiletimeOpsDoubleModuleClass: Symbol = requiredModule("scala.compiletime.ops.double").moduleClass @tu lazy val CompiletimeOpsStringModuleClass: Symbol = requiredModule("scala.compiletime.ops.string").moduleClass @tu lazy val CompiletimeOpsBooleanModuleClass: Symbol = requiredModule("scala.compiletime.ops.boolean").moduleClass @@ -1077,19 +1080,40 @@ class Definitions { final def isCompiletime_S(sym: Symbol)(using Context): Boolean = sym.name == tpnme.S && sym.owner == CompiletimeOpsIntModuleClass - private val compiletimePackageAnyTypes: Set[Name] = Set(tpnme.Equals, tpnme.NotEquals) - private val compiletimePackageIntTypes: Set[Name] = Set( + private val compiletimePackageAnyTypes: Set[Name] = Set( + tpnme.Equals, tpnme.NotEquals, tpnme.IsConst, tpnme.ToString + ) + private val compiletimePackageNumericTypes: Set[Name] = Set( tpnme.Plus, tpnme.Minus, tpnme.Times, tpnme.Div, tpnme.Mod, tpnme.Lt, tpnme.Gt, tpnme.Ge, tpnme.Le, - tpnme.Abs, tpnme.Negate, tpnme.Min, tpnme.Max, tpnme.ToString, + tpnme.Abs, tpnme.Negate, tpnme.Min, tpnme.Max + ) + private val compiletimePackageIntTypes: Set[Name] = compiletimePackageNumericTypes ++ Set[Name]( + tpnme.ToString, //ToString is moved to ops.any and deprecated for ops.int + tpnme.NumberOfLeadingZeros, tpnme.ToLong, tpnme.ToFloat, tpnme.ToDouble, + tpnme.Xor, tpnme.BitwiseAnd, tpnme.BitwiseOr, tpnme.ASR, tpnme.LSL, tpnme.LSR + ) + private val compiletimePackageLongTypes: Set[Name] = compiletimePackageNumericTypes ++ Set[Name]( + tpnme.NumberOfLeadingZeros, tpnme.ToInt, tpnme.ToFloat, tpnme.ToDouble, tpnme.Xor, tpnme.BitwiseAnd, tpnme.BitwiseOr, tpnme.ASR, tpnme.LSL, tpnme.LSR ) + private val compiletimePackageFloatTypes: Set[Name] = compiletimePackageNumericTypes ++ Set[Name]( + tpnme.ToInt, tpnme.ToLong, tpnme.ToDouble + ) + private val compiletimePackageDoubleTypes: Set[Name] = compiletimePackageNumericTypes ++ Set[Name]( + tpnme.ToInt, tpnme.ToLong, tpnme.ToFloat + ) private val compiletimePackageBooleanTypes: Set[Name] = Set(tpnme.Not, tpnme.Xor, tpnme.And, tpnme.Or) - private val compiletimePackageStringTypes: Set[Name] = Set(tpnme.Plus) + private val compiletimePackageStringTypes: Set[Name] = Set( + tpnme.Plus, tpnme.Length, tpnme.Substring, tpnme.Matches + ) private val compiletimePackageOpTypes: Set[Name] = Set(tpnme.S) ++ compiletimePackageAnyTypes ++ compiletimePackageIntTypes + ++ compiletimePackageLongTypes + ++ compiletimePackageFloatTypes + ++ compiletimePackageDoubleTypes ++ compiletimePackageBooleanTypes ++ compiletimePackageStringTypes @@ -1099,6 +1123,9 @@ class Definitions { isCompiletime_S(sym) || sym.owner == CompiletimeOpsAnyModuleClass && compiletimePackageAnyTypes.contains(sym.name) || sym.owner == CompiletimeOpsIntModuleClass && compiletimePackageIntTypes.contains(sym.name) + || sym.owner == CompiletimeOpsLongModuleClass && compiletimePackageLongTypes.contains(sym.name) + || sym.owner == CompiletimeOpsFloatModuleClass && compiletimePackageFloatTypes.contains(sym.name) + || sym.owner == CompiletimeOpsDoubleModuleClass && compiletimePackageDoubleTypes.contains(sym.name) || sym.owner == CompiletimeOpsBooleanModuleClass && compiletimePackageBooleanTypes.contains(sym.name) || sym.owner == CompiletimeOpsStringModuleClass && compiletimePackageStringTypes.contains(sym.name) ) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 2e0b229ca42c..9f4418e1834b 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -211,29 +211,38 @@ object StdNames { final val IOOBException: N = "IndexOutOfBoundsException" final val FunctionXXL: N = "FunctionXXL" - final val Abs: N = "Abs" - final val And: N = "&&" - final val BitwiseAnd: N = "BitwiseAnd" - final val BitwiseOr: N = "BitwiseOr" - final val Div: N = "/" - final val Equals: N = "==" - final val Ge: N = ">=" - final val Gt: N = ">" - final val Le: N = "<=" - final val Lt: N = "<" - final val Max: N = "Max" - final val Min: N = "Min" - final val Minus: N = "-" - final val Mod: N = "%" - final val Negate: N = "Negate" - final val Not: N = "!" - final val NotEquals: N = "!=" - final val Or: N = "||" - final val Plus: N = "+" - final val S: N = "S" - final val Times: N = "*" - final val ToString: N = "ToString" - final val Xor: N = "^" + final val Abs: N = "Abs" + final val And: N = "&&" + final val BitwiseAnd: N = "BitwiseAnd" + final val BitwiseOr: N = "BitwiseOr" + final val Div: N = "/" + final val Equals: N = "==" + final val Ge: N = ">=" + final val Gt: N = ">" + final val IsConst: N = "IsConst" + final val Le: N = "<=" + final val Length: N = "Length" + final val Lt: N = "<" + final val Matches: N = "Matches" + final val Max: N = "Max" + final val Min: N = "Min" + final val Minus: N = "-" + final val Mod: N = "%" + final val Negate: N = "Negate" + final val Not: N = "!" + final val NotEquals: N = "!=" + final val NumberOfLeadingZeros: N = "NumberOfLeadingZeros" + final val Or: N = "||" + final val Plus: N = "+" + final val S: N = "S" + final val Substring: N = "Substring" + final val Times: N = "*" + final val ToInt: N = "ToInt" + final val ToLong: N = "ToLong" + final val ToFloat: N = "ToFloat" + final val ToDouble: N = "ToDouble" + final val ToString: N = "ToString" + final val Xor: N = "^" final val ClassfileAnnotation: N = "ClassfileAnnotation" final val ClassManifest: N = "ClassManifest" diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 6650771963f9..0a3b7ff130a8 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4194,37 +4194,76 @@ object Types { def tryCompiletimeConstantFold(using Context): Type = tycon match { case tycon: TypeRef if defn.isCompiletimeAppliedType(tycon.symbol) => - def constValue(tp: Type): Option[Any] = tp.dealias match { + extension (tp : Type) def fixForEvaluation : Type = + tp.normalized.dealias match { + case tp : TermRef => tp.underlying + case tp => tp + } + + def constValue(tp: Type): Option[Any] = tp.fixForEvaluation match { case ConstantType(Constant(n)) => Some(n) case _ => None } - def boolValue(tp: Type): Option[Boolean] = tp.dealias match { + def boolValue(tp: Type): Option[Boolean] = tp.fixForEvaluation match { case ConstantType(Constant(n: Boolean)) => Some(n) case _ => None } - def intValue(tp: Type): Option[Int] = tp.dealias match { + def intValue(tp: Type): Option[Int] = tp.fixForEvaluation match { case ConstantType(Constant(n: Int)) => Some(n) case _ => None } - def stringValue(tp: Type): Option[String] = tp.dealias match { - case ConstantType(Constant(n: String)) => Some(n) + def longValue(tp: Type): Option[Long] = tp.fixForEvaluation match { + case ConstantType(Constant(n: Long)) => Some(n) + case _ => None + } + + def floatValue(tp: Type): Option[Float] = tp.fixForEvaluation match { + case ConstantType(Constant(n: Float)) => Some(n) + case _ => None + } + + def doubleValue(tp: Type): Option[Double] = tp.fixForEvaluation match { + case ConstantType(Constant(n: Double)) => Some(n) case _ => None } + def stringValue(tp: Type): Option[String] = tp.fixForEvaluation match { + case ConstantType(Constant(n: String)) => Some(n) + case _ => None + } + def isConst : Option[Type] = args.head.fixForEvaluation match { + case ConstantType(_) => Some(ConstantType(Constant(true))) + case _ => Some(ConstantType(Constant(false))) + } def natValue(tp: Type): Option[Int] = intValue(tp).filter(n => n >= 0 && n < Int.MaxValue) def constantFold1[T](extractor: Type => Option[T], op: T => Any): Option[Type] = - extractor(args.head.normalized).map(a => ConstantType(Constant(op(a)))) + extractor(args.head).map(a => ConstantType(Constant(op(a)))) def constantFold2[T](extractor: Type => Option[T], op: (T, T) => Any): Option[Type] = + constantFold2AB(extractor, extractor, op) + + def constantFold2AB[TA, TB](extractorA: Type => Option[TA], extractorB: Type => Option[TB], op: (TA, TB) => Any): Option[Type] = for { - a <- extractor(args.head.normalized) - b <- extractor(args.tail.head.normalized) + a <- extractorA(args.head) + b <- extractorB(args.last) } yield ConstantType(Constant(op(a, b))) + def constantFold3[TA, TB, TC]( + extractorA: Type => Option[TA], + extractorB: Type => Option[TB], + extractorC: Type => Option[TC], + op: (TA, TB, TC) => Any + ): Option[Type] = + for { + a <- extractorA(args.head) + b <- extractorB(args(1)) + c <- extractorC(args.last) + } yield ConstantType(Constant(op(a, b, c))) + trace(i"compiletime constant fold $this", typr, show = true) { val name = tycon.symbol.name val owner = tycon.symbol.owner @@ -4236,10 +4275,13 @@ object Types { } else if (owner == defn.CompiletimeOpsAnyModuleClass) name match { case tpnme.Equals if nArgs == 2 => constantFold2(constValue, _ == _) case tpnme.NotEquals if nArgs == 2 => constantFold2(constValue, _ != _) + case tpnme.ToString if nArgs == 1 => constantFold1(constValue, _.toString) + case tpnme.IsConst if nArgs == 1 => isConst case _ => None } else if (owner == defn.CompiletimeOpsIntModuleClass) name match { case tpnme.Abs if nArgs == 1 => constantFold1(intValue, _.abs) case tpnme.Negate if nArgs == 1 => constantFold1(intValue, x => -x) + //ToString is deprecated for ops.int, and moved to ops.any case tpnme.ToString if nArgs == 1 => constantFold1(intValue, _.toString) case tpnme.Plus if nArgs == 2 => constantFold2(intValue, _ + _) case tpnme.Minus if nArgs == 2 => constantFold2(intValue, _ - _) @@ -4264,9 +4306,85 @@ object Types { case tpnme.LSR if nArgs == 2 => constantFold2(intValue, _ >>> _) case tpnme.Min if nArgs == 2 => constantFold2(intValue, _ min _) case tpnme.Max if nArgs == 2 => constantFold2(intValue, _ max _) + case tpnme.NumberOfLeadingZeros if nArgs == 1 => constantFold1(intValue, Integer.numberOfLeadingZeros(_)) + case tpnme.ToLong if nArgs == 1 => constantFold1(intValue, _.toLong) + case tpnme.ToFloat if nArgs == 1 => constantFold1(intValue, _.toFloat) + case tpnme.ToDouble if nArgs == 1 => constantFold1(intValue, _.toDouble) + case _ => None + } else if (owner == defn.CompiletimeOpsLongModuleClass) name match { + case tpnme.Abs if nArgs == 1 => constantFold1(longValue, _.abs) + case tpnme.Negate if nArgs == 1 => constantFold1(longValue, x => -x) + case tpnme.Plus if nArgs == 2 => constantFold2(longValue, _ + _) + case tpnme.Minus if nArgs == 2 => constantFold2(longValue, _ - _) + case tpnme.Times if nArgs == 2 => constantFold2(longValue, _ * _) + case tpnme.Div if nArgs == 2 => constantFold2(longValue, { + case (_, 0L) => throw new TypeError("Division by 0") + case (a, b) => a / b + }) + case tpnme.Mod if nArgs == 2 => constantFold2(longValue, { + case (_, 0L) => throw new TypeError("Modulo by 0") + case (a, b) => a % b + }) + case tpnme.Lt if nArgs == 2 => constantFold2(longValue, _ < _) + case tpnme.Gt if nArgs == 2 => constantFold2(longValue, _ > _) + case tpnme.Ge if nArgs == 2 => constantFold2(longValue, _ >= _) + case tpnme.Le if nArgs == 2 => constantFold2(longValue, _ <= _) + case tpnme.Xor if nArgs == 2 => constantFold2(longValue, _ ^ _) + case tpnme.BitwiseAnd if nArgs == 2 => constantFold2(longValue, _ & _) + case tpnme.BitwiseOr if nArgs == 2 => constantFold2(longValue, _ | _) + case tpnme.ASR if nArgs == 2 => constantFold2(longValue, _ >> _) + case tpnme.LSL if nArgs == 2 => constantFold2(longValue, _ << _) + case tpnme.LSR if nArgs == 2 => constantFold2(longValue, _ >>> _) + case tpnme.Min if nArgs == 2 => constantFold2(longValue, _ min _) + case tpnme.Max if nArgs == 2 => constantFold2(longValue, _ max _) + case tpnme.NumberOfLeadingZeros if nArgs == 1 => + constantFold1(longValue, java.lang.Long.numberOfLeadingZeros(_)) + case tpnme.ToInt if nArgs == 1 => constantFold1(longValue, _.toInt) + case tpnme.ToFloat if nArgs == 1 => constantFold1(longValue, _.toFloat) + case tpnme.ToDouble if nArgs == 1 => constantFold1(longValue, _.toDouble) + case _ => None + } else if (owner == defn.CompiletimeOpsFloatModuleClass) name match { + case tpnme.Abs if nArgs == 1 => constantFold1(floatValue, _.abs) + case tpnme.Negate if nArgs == 1 => constantFold1(floatValue, x => -x) + case tpnme.Plus if nArgs == 2 => constantFold2(floatValue, _ + _) + case tpnme.Minus if nArgs == 2 => constantFold2(floatValue, _ - _) + case tpnme.Times if nArgs == 2 => constantFold2(floatValue, _ * _) + case tpnme.Div if nArgs == 2 => constantFold2(floatValue, _ / _) + case tpnme.Mod if nArgs == 2 => constantFold2(floatValue, _ % _) + case tpnme.Lt if nArgs == 2 => constantFold2(floatValue, _ < _) + case tpnme.Gt if nArgs == 2 => constantFold2(floatValue, _ > _) + case tpnme.Ge if nArgs == 2 => constantFold2(floatValue, _ >= _) + case tpnme.Le if nArgs == 2 => constantFold2(floatValue, _ <= _) + case tpnme.Min if nArgs == 2 => constantFold2(floatValue, _ min _) + case tpnme.Max if nArgs == 2 => constantFold2(floatValue, _ max _) + case tpnme.ToInt if nArgs == 1 => constantFold1(floatValue, _.toInt) + case tpnme.ToLong if nArgs == 1 => constantFold1(floatValue, _.toLong) + case tpnme.ToDouble if nArgs == 1 => constantFold1(floatValue, _.toDouble) + case _ => None + } else if (owner == defn.CompiletimeOpsDoubleModuleClass) name match { + case tpnme.Abs if nArgs == 1 => constantFold1(doubleValue, _.abs) + case tpnme.Negate if nArgs == 1 => constantFold1(doubleValue, x => -x) + case tpnme.Plus if nArgs == 2 => constantFold2(doubleValue, _ + _) + case tpnme.Minus if nArgs == 2 => constantFold2(doubleValue, _ - _) + case tpnme.Times if nArgs == 2 => constantFold2(doubleValue, _ * _) + case tpnme.Div if nArgs == 2 => constantFold2(doubleValue, _ / _) + case tpnme.Mod if nArgs == 2 => constantFold2(doubleValue, _ % _) + case tpnme.Lt if nArgs == 2 => constantFold2(doubleValue, _ < _) + case tpnme.Gt if nArgs == 2 => constantFold2(doubleValue, _ > _) + case tpnme.Ge if nArgs == 2 => constantFold2(doubleValue, _ >= _) + case tpnme.Le if nArgs == 2 => constantFold2(doubleValue, _ <= _) + case tpnme.Min if nArgs == 2 => constantFold2(doubleValue, _ min _) + case tpnme.Max if nArgs == 2 => constantFold2(doubleValue, _ max _) + case tpnme.ToInt if nArgs == 1 => constantFold1(doubleValue, _.toInt) + case tpnme.ToLong if nArgs == 1 => constantFold1(doubleValue, _.toLong) + case tpnme.ToFloat if nArgs == 1 => constantFold1(doubleValue, _.toFloat) case _ => None } else if (owner == defn.CompiletimeOpsStringModuleClass) name match { case tpnme.Plus if nArgs == 2 => constantFold2(stringValue, _ + _) + case tpnme.Length if nArgs == 1 => constantFold1(stringValue, _.length) + case tpnme.Matches if nArgs == 2 => constantFold2(stringValue, _ matches _) + case tpnme.Substring if nArgs == 3 => + constantFold3(stringValue, intValue, intValue, (s, b, e) => s.substring(b, e)) case _ => None } else if (owner == defn.CompiletimeOpsBooleanModuleClass) name match { case tpnme.Not if nArgs == 1 => constantFold1(boolValue, x => !x) diff --git a/library/src/scala/compiletime/ops/any.scala b/library/src/scala/compiletime/ops/any.scala index 0cb7e6d06431..0a6ed4c3e3d7 100644 --- a/library/src/scala/compiletime/ops/any.scala +++ b/library/src/scala/compiletime/ops/any.scala @@ -21,3 +21,22 @@ object any: * @syntax markdown */ type !=[X, Y] <: Boolean + + /** Tests if a type is a constant. + * ```scala + * val c1: IsConst[1] = true + * val c2: IsConst["hi"] = true + * val c3: IsConst[false] = true + * ``` + * @syntax markdown + */ + type IsConst[X] <: Boolean + + /** String conversion of a constant singleton type. + * ```scala + * val s1: ToString[1] = "1" + * val sTrue: ToString[true] = "true" + * ``` + * @syntax markdown + */ + type ToString[X] <: String \ No newline at end of file diff --git a/library/src/scala/compiletime/ops/double.scala b/library/src/scala/compiletime/ops/double.scala new file mode 100644 index 000000000000..783b0ac8b287 --- /dev/null +++ b/library/src/scala/compiletime/ops/double.scala @@ -0,0 +1,136 @@ +package scala.compiletime +package ops + +object double: + /** Addition of two `Double` singleton types. + * ```scala + * val sum: 2.0 + 2.0 = 4.0 + * ``` + * @syntax markdown + */ + type +[X <: Double, Y <: Double] <: Double + + /** Subtraction of two `Double` singleton types. + * ```scala + * val sub: 4.0 - 2.0 = 2.0 + * ``` + * @syntax markdown + */ + type -[X <: Double, Y <: Double] <: Double + + /** Multiplication of two `Double` singleton types. + * ```scala + * val mul: 4.0 * 2.0 = 8.0 + * ``` + * @syntax markdown + */ + type *[X <: Double, Y <: Double] <: Double + + /** Integer division of two `Double` singleton types. + * ```scala + * val div: 5.0 / 2.0 = 2.0 + * ``` + * @syntax markdown + */ + type /[X <: Double, Y <: Double] <: Double + + /** Remainder of the division of `X` by `Y`. + * ```scala + * val mod: 5.0 % 2.0 = 1.0 + * ``` + * @syntax markdown + */ + type %[X <: Double, Y <: Double] <: Double + + /** Less-than comparison of two `Double` singleton types. + * ```scala + * val lt1: 4.0 < 2.0 = false + * val lt2: 2.0 < 4.0 = true + * ``` + * @syntax markdown + */ + type <[X <: Double, Y <: Double] <: Boolean + + /** Greater-than comparison of two `Double` singleton types. + * ```scala + * val gt1: 4.0 > 2.0 = true + * val gt2: 2.0 > 2.0 = false + * ``` + * @syntax markdown + */ + type >[X <: Double, Y <: Double] <: Boolean + + /** Greater-or-equal comparison of two `Double` singleton types. + * ```scala + * val ge1: 4.0 >= 2.0 = true + * val ge2: 2.0 >= 3.0 = false + * ``` + * @syntax markdown + */ + type >=[X <: Double, Y <: Double] <: Boolean + + /** Less-or-equal comparison of two `Double` singleton types. + * ```scala + * val lt1: 4.0 <= 2.0 = false + * val lt2: 2.0 <= 2.0 = true + * ``` + * @syntax markdown + */ + type <=[X <: Double, Y <: Double] <: Boolean + + /** Absolute value of an `Double` singleton type. + * ```scala + * val abs: Abs[-1.0] = 1.0 + * ``` + * @syntax markdown + */ + type Abs[X <: Double] <: Double + + /** Negation of an `Double` singleton type. + * ```scala + * val neg1: Neg[-1.0] = 1.0 + * val neg2: Neg[1.0] = -1.0 + * ``` + * @syntax markdown + */ + type Negate[X <: Double] <: Double + + /** Minimum of two `Double` singleton types. + * ```scala + * val min: Min[-1.0, 1.0] = -1.0 + * ``` + * @syntax markdown + */ + type Min[X <: Double, Y <: Double] <: Double + + /** Maximum of two `Double` singleton types. + * ```scala + * val max: Max[-1.0, 1.0] = 1.0 + * ``` + * @syntax markdown + */ + type Max[X <: Double, Y <: Double] <: Double + + /** Int conversion of a `Double` singleton type. + * ```scala + * val x: ToInt[1.0] = 1 + * ``` + * @syntax markdown + */ + type ToInt[X <: Double] <: Int + + /** Long conversion of a `Double` singleton type. + * ```scala + * val x: ToLong[1.0] = 1L + * ``` + * @syntax markdown + */ + type ToLong[X <: Double] <: Long + + /** Float conversion of a `Double` singleton type. + * ```scala + * val x: ToFloat[1.0] = 1.0f + * ``` + * @syntax markdown + */ + type ToFloat[X <: Double] <: Float \ No newline at end of file diff --git a/library/src/scala/compiletime/ops/float.scala b/library/src/scala/compiletime/ops/float.scala new file mode 100644 index 000000000000..7330959f461b --- /dev/null +++ b/library/src/scala/compiletime/ops/float.scala @@ -0,0 +1,136 @@ +package scala.compiletime +package ops + +object float: + /** Addition of two `Float` singleton types. + * ```scala + * val sum: 2.0f + 2.0f = 4.0f + * ``` + * @syntax markdown + */ + type +[X <: Float, Y <: Float] <: Float + + /** Subtraction of two `Float` singleton types. + * ```scala + * val sub: 4.0f - 2.0f = 2.0f + * ``` + * @syntax markdown + */ + type -[X <: Float, Y <: Float] <: Float + + /** Multiplication of two `Float` singleton types. + * ```scala + * val mul: 4.0f * 2.0f = 8.0f + * ``` + * @syntax markdown + */ + type *[X <: Float, Y <: Float] <: Float + + /** Integer division of two `Float` singleton types. + * ```scala + * val div: 5.0f / 2.0f = 2.0f + * ``` + * @syntax markdown + */ + type /[X <: Float, Y <: Float] <: Float + + /** Remainder of the division of `X` by `Y`. + * ```scala + * val mod: 5.0f % 2.0f = 1.0f + * ``` + * @syntax markdown + */ + type %[X <: Float, Y <: Float] <: Float + + /** Less-than comparison of two `Float` singleton types. + * ```scala + * val lt1: 4.0f < 2.0f = false + * val lt2: 2.0f < 4.0f = true + * ``` + * @syntax markdown + */ + type <[X <: Float, Y <: Float] <: Boolean + + /** Greater-than comparison of two `Float` singleton types. + * ```scala + * val gt1: 4.0f > 2.0f = true + * val gt2: 2.0f > 2.0f = false + * ``` + * @syntax markdown + */ + type >[X <: Float, Y <: Float] <: Boolean + + /** Greater-or-equal comparison of two `Float` singleton types. + * ```scala + * val ge1: 4.0f >= 2.0f = true + * val ge2: 2.0f >= 3.0f = false + * ``` + * @syntax markdown + */ + type >=[X <: Float, Y <: Float] <: Boolean + + /** Less-or-equal comparison of two `Float` singleton types. + * ```scala + * val lt1: 4.0f <= 2.0f = false + * val lt2: 2.0f <= 2.0f = true + * ``` + * @syntax markdown + */ + type <=[X <: Float, Y <: Float] <: Boolean + + /** Absolute value of an `Float` singleton type. + * ```scala + * val abs: Abs[-1.0f] = 1.0f + * ``` + * @syntax markdown + */ + type Abs[X <: Float] <: Float + + /** Negation of an `Float` singleton type. + * ```scala + * val neg1: Neg[-1.0f] = 1.0f + * val neg2: Neg[1.0f] = -1.0f + * ``` + * @syntax markdown + */ + type Negate[X <: Float] <: Float + + /** Minimum of two `Float` singleton types. + * ```scala + * val min: Min[-1.0f, 1.0f] = -1.0f + * ``` + * @syntax markdown + */ + type Min[X <: Float, Y <: Float] <: Float + + /** Maximum of two `Float` singleton types. + * ```scala + * val max: Max[-1.0f, 1.0f] = 1.0f + * ``` + * @syntax markdown + */ + type Max[X <: Float, Y <: Float] <: Float + + /** Int conversion of a `Float` singleton type. + * ```scala + * val x: ToInt[1.0f] = 1 + * ``` + * @syntax markdown + */ + type ToInt[X <: Float] <: Int + + /** Long conversion of a `Float` singleton type. + * ```scala + * val x: ToLong[1.0f] = 1L + * ``` + * @syntax markdown + */ + type ToLong[X <: Float] <: Long + + /** Double conversion of a `Float` singleton type. + * ```scala + * val x: ToDouble[1.0f] = 1.0 + * ``` + * @syntax markdown + */ + type ToDouble[X <: Float] <: Double diff --git a/library/src/scala/compiletime/ops/int.scala b/library/src/scala/compiletime/ops/int.scala index 70339eed12e7..890909963b01 100644 --- a/library/src/scala/compiletime/ops/int.scala +++ b/library/src/scala/compiletime/ops/int.scala @@ -181,4 +181,43 @@ object int: * ``` * @syntax markdown */ + @deprecated("Use compiletime.ops.any.ToString instead.","3.1.0") type ToString[X <: Int] <: String + + /** Long conversion of an `Int` singleton type. + * ```scala + * val x: ToLong[1] = 1L + * ``` + * @syntax markdown + */ + type ToLong[X <: Int] <: Long + + /** Float conversion of an `Int` singleton type. + * ```scala + * val x: ToFloat[1] = 1.0f + * ``` + * @syntax markdown + */ + type ToFloat[X <: Int] <: Float + + /** Double conversion of an `Int` singleton type. + * ```scala + * val x: ToDouble[1] = 1.0 + * ``` + * @syntax markdown + */ + type ToDouble[X <: Int] <: Double + + /** Number of zero bits preceding the highest-order ("leftmost") + * one-bit in the two's complement binary representation of the specified `Int` singleton type. + * Returns 32 if the specified singleton type has no one-bits in its two's complement representation, + * in other words if it is equal to zero. + * ```scala + * val zero_lzc: NumberOfLeadingZeros[0] = 32 + * val eight_lzc: NumberOfLeadingZeros[8] = 28 + * type Log2[N <: Int] = 31 - NumberOfLeadingZeros[N] + * val log2of8: Log2[8] = 3 + * ``` + * @syntax markdown + */ + type NumberOfLeadingZeros[X <: Int] <: Int \ No newline at end of file diff --git a/library/src/scala/compiletime/ops/long.scala b/library/src/scala/compiletime/ops/long.scala new file mode 100644 index 000000000000..920d7c81d349 --- /dev/null +++ b/library/src/scala/compiletime/ops/long.scala @@ -0,0 +1,222 @@ +package scala.compiletime +package ops + +object long: + /** Successor of a natural number where zero is the type 0 and successors are reduced as if the definition was + * + * ```scala + * type S[N <: Long] <: Long = N match { + * case 0L => 1L + * case 1L => 2L + * case 2L => 3L + * ... + * case 9223372036854775806L => 9223372036854775807L + * } + * ``` + * @syntax markdown + */ + type S[N <: Long] <: Long + + /** Addition of two `Long` singleton types. + * ```scala + * val sum: 2L + 2L = 4L + * ``` + * @syntax markdown + */ + type +[X <: Long, Y <: Long] <: Long + + /** Subtraction of two `Long` singleton types. + * ```scala + * val sub: 4L - 2L = 2L + * ``` + * @syntax markdown + */ + type -[X <: Long, Y <: Long] <: Long + + /** Multiplication of two `Long` singleton types. + * ```scala + * val mul: 4L * 2L = 8L + * ``` + * @syntax markdown + */ + type *[X <: Long, Y <: Long] <: Long + + /** Integer division of two `Long` singleton types. + * ```scala + * val div: 5L / 2L = 2L + * ``` + * @syntax markdown + */ + type /[X <: Long, Y <: Long] <: Long + + /** Remainder of the division of `X` by `Y`. + * ```scala + * val mod: 5L % 2L = 1L + * ``` + * @syntax markdown + */ + type %[X <: Long, Y <: Long] <: Long + + /** Binary left shift of `X` by `Y`. + * ```scala + * val lshift: 1L << 2L = 4L + * ``` + * @syntax markdown + */ + type <<[X <: Long, Y <: Long] <: Long + + /** Binary right shift of `X` by `Y`. + * ```scala + * val rshift: 10L >> 1L = 5L + * ``` + * @syntax markdown + */ + type >>[X <: Long, Y <: Long] <: Long + + /** Binary right shift of `X` by `Y`, filling the left with zeros. + * ```scala + * val rshiftzero: 10L >>> 1L = 5L + * ``` + * @syntax markdown + */ + type >>>[X <: Long, Y <: Long] <: Long + + /** Bitwise xor of `X` and `Y`. + * ```scala + * val xor: 10L ^ 30L = 20L + * ``` + * @syntax markdown + */ + type ^[X <: Long, Y <: Long] <: Long + + /** Less-than comparison of two `Long` singleton types. + * ```scala + * val lt1: 4L < 2L = false + * val lt2: 2L < 4L = true + * ``` + * @syntax markdown + */ + type <[X <: Long, Y <: Long] <: Boolean + + /** Greater-than comparison of two `Long` singleton types. + * ```scala + * val gt1: 4L > 2L = true + * val gt2: 2L > 2L = false + * ``` + * @syntax markdown + */ + type >[X <: Long, Y <: Long] <: Boolean + + /** Greater-or-equal comparison of two `Long` singleton types. + * ```scala + * val ge1: 4L >= 2L = true + * val ge2: 2L >= 3L = false + * ``` + * @syntax markdown + */ + type >=[X <: Long, Y <: Long] <: Boolean + + /** Less-or-equal comparison of two `Long` singleton types. + * ```scala + * val lt1: 4L <= 2L = false + * val lt2: 2L <= 2L = true + * ``` + * @syntax markdown + */ + type <=[X <: Long, Y <: Long] <: Boolean + + /** Bitwise and of `X` and `Y`. + * ```scala + * val and1: BitwiseAnd[4L, 4L] = 4L + * val and2: BitwiseAnd[10L, 5L] = 0L + * ``` + * @syntax markdown + */ + type BitwiseAnd[X <: Long, Y <: Long] <: Long + + /** Bitwise or of `X` and `Y`. + * ```scala + * val or: BitwiseOr[10L, 11L] = 11L + * ``` + * @syntax markdown + */ + type BitwiseOr[X <: Long, Y <: Long] <: Long + + /** Absolute value of an `Long` singleton type. + * ```scala + * val abs: Abs[-1L] = 1L + * ``` + * @syntax markdown + */ + type Abs[X <: Long] <: Long + + /** Negation of an `Long` singleton type. + * ```scala + * val neg1: Neg[-1L] = 1L + * val neg2: Neg[1L] = -1L + * ``` + * @syntax markdown + */ + type Negate[X <: Long] <: Long + + /** Minimum of two `Long` singleton types. + * ```scala + * val min: Min[-1L, 1L] = -1L + * ``` + * @syntax markdown + */ + type Min[X <: Long, Y <: Long] <: Long + + /** Maximum of two `Long` singleton types. + * ```scala + * val max: Max[-1L, 1L] = 1L + * ``` + * @syntax markdown + */ + type Max[X <: Long, Y <: Long] <: Long + + /** String conversion of an `Long` singleton type. + * ```scala + * val abs: ToString[1L] = "1" + * ``` + * @syntax markdown + */ + type ToString[X <: Long] <: String + + /** Number of zero bits preceding the highest-order ("leftmost") + * one-bit in the two's complement binary representation of the specified `Long` singleton type. + * Returns 64 if the specified singleton type has no one-bits in its two's complement representation, + * in other words if it is equal to zero. + * ```scala + * val zero_lzc: NumberOfLeadingZeros[0L] = 64 + * val eight_lzc: NumberOfLeadingZeros[8L] = 60 + * type Log2[N <: Long] = int.-[63, NumberOfLeadingZeros[N]] + * val log2of8: Log2[8L] = 3 + * ``` + * @syntax markdown + */ + type NumberOfLeadingZeros[X <: Long] <: Int + + /** Int conversion of a `Long` singleton type. + * ```scala + * val x: ToInt[1L] = 1 + * ``` + * @syntax markdown + */ + type ToInt[X <: Long] <: Int + + /** Float conversion of a `Long` singleton type. + * ```scala + * val x: ToFloat[1L] = 1.0f + * ``` + * @syntax markdown + */ + type ToFloat[X <: Long] <: Float + + /** Double conversion of a `Long` singleton type. + * ```scala + * val x: ToDouble[1L] = 1.0 + * ``` + * @syntax markdown + */ + type ToDouble[X <: Long] <: Double diff --git a/library/src/scala/compiletime/ops/string.scala b/library/src/scala/compiletime/ops/string.scala index 0969ceb60053..d30f03b904c0 100644 --- a/library/src/scala/compiletime/ops/string.scala +++ b/library/src/scala/compiletime/ops/string.scala @@ -9,3 +9,32 @@ object string: * @syntax markdown */ type +[X <: String, Y <: String] <: String + + /** Length of a `String` singleton type. + * ```scala + * val helloSize: Size["hello"] = 5 + * ``` + * @syntax markdown + */ + type Length[X <: String] <: Int + + /** Substring of a `String` singleton type, with a singleton type + * begin inclusive index `IBeg`, and a singleton type exclusive end index `IEnd`. + * The substring begins at the specified IBeg and extends to the character at index IEnd - 1. + * Thus the length of the substring is IEnd-IBeg. + * ```scala + * val x: Substring["hamburger", 4, 8] = "urge" + * val y: Substring["smiles", 1, 5] = "mile" + * ``` + * @syntax markdown + */ + type Substring[S <: String, IBeg <: Int, IEnd <: Int] <: String + + /** Tests if this `String` singleton type matches the given + * regular expression `String` singleton type. + * ```scala + * val x: Matches["unhappy", "un.*"] = true + * ``` + * @syntax markdown + */ + type Matches[S <: String, Regex <: String] <: Boolean diff --git a/tests/neg/singleton-ops-any.check b/tests/neg/singleton-ops-any.check index 6de768fa0b2e..be234d405b4e 100644 --- a/tests/neg/singleton-ops-any.check +++ b/tests/neg/singleton-ops-any.check @@ -26,3 +26,17 @@ longer explanation available when compiling with `-explain` | Required: (false : Boolean) longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/singleton-ops-any.scala:18:27 ------------------------------------------------- +18 | val t04: ToString[Int] = "Int" // error + | ^^^^^ + | Found: ("Int" : String) + | Required: compiletime.ops.any.ToString[Int] + +longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/singleton-ops-any.scala:32:26 ------------------------------------------------- +32 | val t48: IsConst[Any] = true // error + | ^^^^ + | Found: (true : Boolean) + | Required: (false : Boolean) + +longer explanation available when compiling with `-explain` diff --git a/tests/neg/singleton-ops-any.scala b/tests/neg/singleton-ops-any.scala index 0d7c05b55fec..33951f58bcd9 100644 --- a/tests/neg/singleton-ops-any.scala +++ b/tests/neg/singleton-ops-any.scala @@ -11,4 +11,24 @@ object Test { val t37: 0 != 1 = true val t38: false != 5 = false // error val t39: 10 != 10 = true // error + + val t01: ToString[1] = "1" + val t02: ToString[-2L] = "-2" + val t03: ToString[true] = "true" + val t04: ToString[Int] = "Int" // error + val t05: ToString[3.33] = "3.33" + val t06: ToString["123"] = "123" + + val t40: IsConst[1] = true + val t41: IsConst[2L] = true + val t42: IsConst[-1.0] = true + val t43: IsConst[false] = true + val t44: IsConst["hi"] = true + val t45: IsConst[Int] = false + val one : Int = 1 + val t46 : IsConst[one.type] = false + final val two = 2 + val t47 : IsConst[two.type] = true + val t48: IsConst[Any] = true // error + } diff --git a/tests/neg/singleton-ops-double.scala b/tests/neg/singleton-ops-double.scala new file mode 100644 index 000000000000..006b86270657 --- /dev/null +++ b/tests/neg/singleton-ops-double.scala @@ -0,0 +1,77 @@ +import scala.compiletime.ops.double.* + +object Test { + summon[2.0 + 3.0 =:= 6.0 - 1.0] + summon[1763.0 =:= 41.0 * 43.0] + summon[2.0 + 2.0 =:= 3.0] // error + summon[29.0 * 31.0 =:= 900.0] // error + summon[Double <:< Double + 1.0] // error + summon[1.0 + Double <:< Double] + + val t0: 2.0 + 3.0 = 5.0 + val t1: 2.0 + 2.0 = 5.0 // error + val t2: -1.0 + 1.0 = 0.0 + val t3: -5.0 + -5.0 = -11.0 // error + + val t4: 10.0 * 20.0 = 200.0 + val t5: 30.0 * 10.0 = 400.0 // error + val t6: -10.0 * 2.0 = -20.0 + val t7: -2.0 * -2.0 = 4.0 + + val t8: 10.0 / 2.0 = 5.0 + val t9: 11.0 / -2.0 = -5.5 + val t10: 2.0 / 4.0 = 2.0 // error + + val t12: 10.0 % 3.0 = 1.0 + val t13: 12.0 % 2.0 = 1.0 // error + val t14: 1.0 % -3.0 = 1.0 + + val t16: 1.0 < 0.0 = false + val t17: 0.0 < 1.0 = true + val t18: 10.0 < 5.0 = true // error + val t19: 5.0 < 10.0 = false // error + + val t20: 1.0 <= 0.0 = false + val t21: 1.0 <= 1.0 = true + val t22: 10.0 <= 5.0 = true // error + val t23: 5.0 <= 10.0 = false // error + + val t24: 1.0 > 0.0 = true + val t25: 0.0 > 1.0 = false + val t26: 10.0 > 5.0 = false // error + val t27: 5.0 > 10.0 = true // error + + val t28: 1.0 >= 1.0 = true + val t29: 0.0 >= 1.0 = false + val t30: 10.0 >= 5.0 = false // error + val t31: 5.0 >= 10.0 = true // error + + val t32: Abs[0.0] = 0.0 + val t33: Abs[-1.0] = 1.0 + val t34: Abs[-1.0] = -1.0 // error + val t35: Abs[1.0] = -1.0 // error + + val t36: Negate[-10.0] = 10.0 + val t37: Negate[10.0] = -10.0 + val t38: Negate[1.0] = 1.0 // error + val t39: Negate[-1.0] = -1.0 // error + + val t40: Max[-1.0, 10.0] = 10.0 + val t41: Max[4.0, 2.0] = 4.0 + val t42: Max[2.0, 2.0] = 1.0 // error + val t43: Max[-1.0, -1.0] = 0.0 // error + + val t44: Min[-1.0, 10.0] = -1.0 + val t45: Min[4.0, 2.0] = 2.0 + val t46: Min[2.0, 2.0] = 1.0 // error + val t47: Min[-1.0, -1.0] = 0.0 // error + + val t79: ToInt[1.0] = 1 + val t80: ToInt[3.0] = 2 // error + + val t81: ToLong[1.0] = 1L + val t82: ToLong[2.0] = 2 // error + + val t83: ToFloat[1.0] = 1.0f + val t84: ToFloat[2.0] = 2 // error +} diff --git a/tests/neg/singleton-ops-float.scala b/tests/neg/singleton-ops-float.scala new file mode 100644 index 000000000000..f7263fc804a6 --- /dev/null +++ b/tests/neg/singleton-ops-float.scala @@ -0,0 +1,77 @@ +import scala.compiletime.ops.float.* + +object Test { + summon[2.0f + 3.0f =:= 6.0f - 1.0f] + summon[1763.0f =:= 41.0f * 43.0f] + summon[2.0f + 2.0f =:= 3.0f] // error + summon[29.0f * 31.0f =:= 900.0f] // error + summon[Float <:< Float + 1.0f] // error + summon[1.0f + Float <:< Float] + + val t0: 2.0f + 3.0f = 5.0f + val t1: 2.0f + 2.0f = 5.0f // error + val t2: -1.0f + 1.0f = 0.0f + val t3: -5.0f + -5.0f = -11.0f // error + + val t4: 10.0f * 20.0f = 200.0f + val t5: 30.0f * 10.0f = 400.0f // error + val t6: -10.0f * 2.0f = -20.0f + val t7: -2.0f * -2.0f = 4.0f + + val t8: 10.0f / 2.0f = 5.0f + val t9: 11.0f / -2.0f = -5.5f + val t10: 2.0f / 4.0f = 2.0f // error + + val t12: 10.0f % 3.0f = 1.0f + val t13: 12.0f % 2.0f = 1.0f // error + val t14: 1.0f % -3.0f = 1.0f + + val t16: 1.0f < 0.0f = false + val t17: 0.0f < 1.0f = true + val t18: 10.0f < 5.0f = true // error + val t19: 5.0f < 10.0f = false // error + + val t20: 1.0f <= 0.0f = false + val t21: 1.0f <= 1.0f = true + val t22: 10.0f <= 5.0f = true // error + val t23: 5.0f <= 10.0f = false // error + + val t24: 1.0f > 0.0f = true + val t25: 0.0f > 1.0f = false + val t26: 10.0f > 5.0f = false // error + val t27: 5.0f > 10.0f = true // error + + val t28: 1.0f >= 1.0f = true + val t29: 0.0f >= 1.0f = false + val t30: 10.0f >= 5.0f = false // error + val t31: 5.0f >= 10.0f = true // error + + val t32: Abs[0.0f] = 0.0f + val t33: Abs[-1.0f] = 1.0f + val t34: Abs[-1.0f] = -1.0f // error + val t35: Abs[1.0f] = -1.0f // error + + val t36: Negate[-10.0f] = 10.0f + val t37: Negate[10.0f] = -10.0f + val t38: Negate[1.0f] = 1.0f // error + val t39: Negate[-1.0f] = -1.0f // error + + val t40: Max[-1.0f, 10.0f] = 10.0f + val t41: Max[4.0f, 2.0f] = 4.0f + val t42: Max[2.0f, 2.0f] = 1.0f // error + val t43: Max[-1.0f, -1.0f] = 0.0f // error + + val t44: Min[-1.0f, 10.0f] = -1.0f + val t45: Min[4.0f, 2.0f] = 2.0f + val t46: Min[2.0f, 2.0f] = 1.0f // error + val t47: Min[-1.0f, -1.0f] = 0.0f // error + + val t79: ToInt[1.0f] = 1 + val t80: ToInt[3.0f] = 2 // error + + val t81: ToLong[1.0f] = 1L + val t82: ToLong[2.0f] = 2 // error + + val t83: ToDouble[1.0f] = 1.0 + val t84: ToDouble[2.0f] = 2 // error +} diff --git a/tests/neg/singleton-ops-int.scala b/tests/neg/singleton-ops-int.scala index d2fd3a73afcd..ca2d3d6ea107 100644 --- a/tests/neg/singleton-ops-int.scala +++ b/tests/neg/singleton-ops-int.scala @@ -9,6 +9,9 @@ object Test { summon[1 + Int <:< Int] val t0: 2 + 3 = 5 + final val two = 2 + final val three = 3 + val t0_b : two.type + three.type = 5 val t1: 2 + 2 = 5 // error val t2: -1 + 1 = 0 val t3: -5 + -5 = -11 // error @@ -68,10 +71,8 @@ object Test { val t46: Min[2, 2] = 1 // error val t47: Min[-1, -1] = 0 // error - val t48: ToString[213] = "213" - val t49: ToString[-1] = "-1" - val t50: ToString[0] = "-0" // error - val t51: ToString[200] = "100" // error + val t48: ToString[213] = "213" // warning (deprecation) + val t49: ToString[-1] = "-1" // warning (deprecation) val t52: 1 ^ 2 = 3 val t53: 1 ^ 3 = 3 // error @@ -102,4 +103,17 @@ object Test { val t73: -7 >>> 3 = 536870911 val t74: -7 >>> 3 = -1 // error + val t75: NumberOfLeadingZeros[0] = 32 + val t76: NumberOfLeadingZeros[8] = 28 + val t77: NumberOfLeadingZeros[-1] = 0 + val t78: NumberOfLeadingZeros[-1] = 1 // error + + val t79: ToLong[1] = 1L + val t80: ToLong[2] = 2 // error + + val t81: ToFloat[1] = 1.0f + val t82: ToFloat[2] = 2 // error + + val t83: ToDouble[1] = 1.0 + val t84: ToDouble[2] = 2 // error } diff --git a/tests/neg/singleton-ops-long.scala b/tests/neg/singleton-ops-long.scala new file mode 100644 index 000000000000..5af2069beb27 --- /dev/null +++ b/tests/neg/singleton-ops-long.scala @@ -0,0 +1,113 @@ +import scala.compiletime.ops.long.* + +object Test { + summon[2L + 3L =:= 6L - 1L] + summon[1763L =:= 41L * 43L] + summon[2L + 2L =:= 3L] // error + summon[29L * 31L =:= 900L] // error + summon[Long <:< Long + 1L] // error + summon[1L + Long <:< Long] + + val t0: 2L + 3L = 5L + val t1: 2L + 2L = 5L // error + val t2: -1L + 1L = 0L + val t3: -5L + -5L = -11L // error + + val t4: 10L * 20L = 200L + val t5: 30L * 10L = 400L // error + val t6: -10L * 2L = -20L + val t7: -2L * -2L = 4L + + val t8: 10L / 2L = 5L + val t9: 11L / -2L = -5L // Integer division + val t10: 2L / 4L = 2L // error + val t11: -1L / 0L = 1L // error + + val t12: 10L % 3L = 1L + val t13: 12L % 2L = 1L // error + val t14: 1L % -3L = 1L + val t15: -3L % 0L = 0L // error + + val t16: 1L < 0L = false + val t17: 0L < 1L = true + val t18: 10L < 5L = true // error + val t19: 5L < 10L = false // error + + val t20: 1L <= 0L = false + val t21: 1L <= 1L = true + val t22: 10L <= 5L = true // error + val t23: 5L <= 10L = false // error + + val t24: 1L > 0L = true + val t25: 0L > 1L = false + val t26: 10L > 5L = false // error + val t27: 5L > 10L = true // error + + val t28: 1L >= 1L = true + val t29: 0L >= 1L = false + val t30: 10L >= 5L = false // error + val t31: 5L >= 10L = true // error + + val t32: Abs[0L] = 0L + val t33: Abs[-1L] = 1L + val t34: Abs[-1L] = -1L // error + val t35: Abs[1L] = -1L // error + + val t36: Negate[-10L] = 10L + val t37: Negate[10L] = -10L + val t38: Negate[1L] = 1L // error + val t39: Negate[-1L] = -1L // error + + val t40: Max[-1L, 10L] = 10L + val t41: Max[4L, 2L] = 4L + val t42: Max[2L, 2L] = 1L // error + val t43: Max[-1L, -1L] = 0L // error + + val t44: Min[-1L, 10L] = -1L + val t45: Min[4L, 2L] = 2L + val t46: Min[2L, 2L] = 1L // error + val t47: Min[-1L, -1L] = 0L // error + + val t52: 1L ^ 2L = 3L + val t53: 1L ^ 3L = 3L // error + val t54: -1L ^ -2L = 1L + val t55: -1L ^ -3L = 1L // error + + val t56: BitwiseOr[1L, 2L] = 3L + val t57: BitwiseOr[10L, 12L] = 13L // error + val t58: BitwiseOr[-11L, 12L] = -3L + val t59: BitwiseOr[-111L, -10L] = 0L // error + + val t60: BitwiseAnd[1L, 1L] = 1L + val t61: BitwiseAnd[1L, 2L] = 0L + val t62: BitwiseAnd[-1L, -3L] = 3L // error + val t63: BitwiseAnd[-1L, -1L] = 1L // error + + val t64: 1L << 1L = 2L + val t65: 1L << 2L = 4L + val t66: 1L << 3L = 8L + val t67: 1L << 4L = 0L // error + + val t68: 100L >> 2L = 25L + val t69: 123456789L >> 71L = 964506L + val t70: -7L >> 3L = -1L + val t71: -7L >> 3L = 0L // error + + val t72: -1L >>> 10000L = 281474976710655L + val t73: -7L >>> 3L = 2305843009213693951L + val t74: -7L >>> 3L = -1L // error + + val t75: NumberOfLeadingZeros[0L] = 64 + val t76: NumberOfLeadingZeros[8L] = 60 + val t77: NumberOfLeadingZeros[-1L] = 0 + val t78: NumberOfLeadingZeros[-1L] = 1 // error + + val t79: ToInt[1L] = 1 + val t80: ToInt[3L] = 2 // error + + val t81: ToFloat[1L] = 1.0f + val t82: ToFloat[2L] = 2 // error + + val t83: ToDouble[1L] = 1.0 + val t84: ToDouble[2L] = 2 // error +} diff --git a/tests/neg/singleton-ops-string.scala b/tests/neg/singleton-ops-string.scala index 46093121d3c4..d9cf2377564b 100644 --- a/tests/neg/singleton-ops-string.scala +++ b/tests/neg/singleton-ops-string.scala @@ -5,4 +5,14 @@ object Test { val t1: "" + "" = "" val t2: "3" + "" = "33" // error val t3: "Hello " + "world" = "error" // error + + val t4: Length["Hello"] = 5 + val t5: Length[""] = 0 + val t6: Length["1"] = 7 // error + + val t7: Substring["hamburger", 4, 8] = "urge" + val t8: Substring["hamburger", 4, 8] = "urger" // error + + val t9: Matches["hamburger", "ham.*"] = true + val t10: Matches["hamburger", "ham.*"] = false // error } From 6792387adeca7aa8d92dc251381afea694d46e76 Mon Sep 17 00:00:00 2001 From: oronpo Date: Mon, 25 Oct 2021 18:31:15 -0400 Subject: [PATCH 2/9] add experimental annotation to added type operations and update MiMa Update MiMaFilters.scala Update MiMaFilters.scala --- library/src/scala/compiletime/ops/any.scala | 4 ++++ library/src/scala/compiletime/ops/double.scala | 3 +++ library/src/scala/compiletime/ops/float.scala | 3 +++ library/src/scala/compiletime/ops/int.scala | 6 ++++++ library/src/scala/compiletime/ops/long.scala | 3 +++ library/src/scala/compiletime/ops/string.scala | 5 +++++ project/MiMaFilters.scala | 6 ++++++ 7 files changed, 30 insertions(+) diff --git a/library/src/scala/compiletime/ops/any.scala b/library/src/scala/compiletime/ops/any.scala index 0a6ed4c3e3d7..60b86414a385 100644 --- a/library/src/scala/compiletime/ops/any.scala +++ b/library/src/scala/compiletime/ops/any.scala @@ -1,6 +1,8 @@ package scala.compiletime package ops +import annotation.experimental + object any: /** Equality comparison of two singleton types. * ```scala @@ -30,6 +32,7 @@ object any: * ``` * @syntax markdown */ + @experimental type IsConst[X] <: Boolean /** String conversion of a constant singleton type. @@ -39,4 +42,5 @@ object any: * ``` * @syntax markdown */ + @experimental type ToString[X] <: String \ No newline at end of file diff --git a/library/src/scala/compiletime/ops/double.scala b/library/src/scala/compiletime/ops/double.scala index 783b0ac8b287..e06ff64c3f05 100644 --- a/library/src/scala/compiletime/ops/double.scala +++ b/library/src/scala/compiletime/ops/double.scala @@ -1,6 +1,9 @@ package scala.compiletime package ops +import scala.annotation.experimental + +@experimental object double: /** Addition of two `Double` singleton types. * ```scala diff --git a/library/src/scala/compiletime/ops/float.scala b/library/src/scala/compiletime/ops/float.scala index 7330959f461b..d5aeb2d5f4f7 100644 --- a/library/src/scala/compiletime/ops/float.scala +++ b/library/src/scala/compiletime/ops/float.scala @@ -1,6 +1,9 @@ package scala.compiletime package ops +import scala.annotation.experimental + +@experimental object float: /** Addition of two `Float` singleton types. * ```scala diff --git a/library/src/scala/compiletime/ops/int.scala b/library/src/scala/compiletime/ops/int.scala index 890909963b01..090e3312421f 100644 --- a/library/src/scala/compiletime/ops/int.scala +++ b/library/src/scala/compiletime/ops/int.scala @@ -1,6 +1,8 @@ package scala.compiletime package ops +import annotation.experimental + object int: /** Successor of a natural number where zero is the type 0 and successors are reduced as if the definition was * @@ -190,6 +192,7 @@ object int: * ``` * @syntax markdown */ + @experimental type ToLong[X <: Int] <: Long /** Float conversion of an `Int` singleton type. @@ -198,6 +201,7 @@ object int: * ``` * @syntax markdown */ + @experimental type ToFloat[X <: Int] <: Float /** Double conversion of an `Int` singleton type. @@ -206,6 +210,7 @@ object int: * ``` * @syntax markdown */ + @experimental type ToDouble[X <: Int] <: Double /** Number of zero bits preceding the highest-order ("leftmost") @@ -220,4 +225,5 @@ object int: * ``` * @syntax markdown */ + @experimental type NumberOfLeadingZeros[X <: Int] <: Int \ No newline at end of file diff --git a/library/src/scala/compiletime/ops/long.scala b/library/src/scala/compiletime/ops/long.scala index 920d7c81d349..f3158e12c2f6 100644 --- a/library/src/scala/compiletime/ops/long.scala +++ b/library/src/scala/compiletime/ops/long.scala @@ -1,6 +1,9 @@ package scala.compiletime package ops +import scala.annotation.experimental + +@experimental object long: /** Successor of a natural number where zero is the type 0 and successors are reduced as if the definition was * diff --git a/library/src/scala/compiletime/ops/string.scala b/library/src/scala/compiletime/ops/string.scala index d30f03b904c0..51e336ab7adc 100644 --- a/library/src/scala/compiletime/ops/string.scala +++ b/library/src/scala/compiletime/ops/string.scala @@ -1,6 +1,8 @@ package scala.compiletime package ops +import scala.annotation.experimental + object string: /** Concatenation of two `String` singleton types. * ```scala @@ -16,6 +18,7 @@ object string: * ``` * @syntax markdown */ + @experimental type Length[X <: String] <: Int /** Substring of a `String` singleton type, with a singleton type @@ -28,6 +31,7 @@ object string: * ``` * @syntax markdown */ + @experimental type Substring[S <: String, IBeg <: Int, IEnd <: Int] <: String /** Tests if this `String` singleton type matches the given @@ -37,4 +41,5 @@ object string: * ``` * @syntax markdown */ + @experimental type Matches[S <: String, Regex <: String] <: Boolean diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 4ef5e4d69c57..4e949a753bd7 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -9,5 +9,11 @@ object MiMaFilters { ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuples.append"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeReprMethods.substituteTypes"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeReprMethods.substituteTypes"), + ProblemFilters.exclude[MissingClassProblem]("scala.compiletime.ops.double"), + ProblemFilters.exclude[MissingClassProblem]("scala.compiletime.ops.double$"), + ProblemFilters.exclude[MissingClassProblem]("scala.compiletime.ops.float"), + ProblemFilters.exclude[MissingClassProblem]("scala.compiletime.ops.float$"), + ProblemFilters.exclude[MissingClassProblem]("scala.compiletime.ops.long"), + ProblemFilters.exclude[MissingClassProblem]("scala.compiletime.ops.long$"), ) } From 32043aa16aa7f7a526049e5f958be35f373bcb19 Mon Sep 17 00:00:00 2001 From: oronpo Date: Mon, 25 Oct 2021 18:35:36 -0400 Subject: [PATCH 3/9] Deprecation of `int.ToString` will only start from 3.2.0 --- library/src/scala/compiletime/ops/int.scala | 2 +- tests/neg/singleton-ops-int.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/src/scala/compiletime/ops/int.scala b/library/src/scala/compiletime/ops/int.scala index 090e3312421f..8d62b98a27e4 100644 --- a/library/src/scala/compiletime/ops/int.scala +++ b/library/src/scala/compiletime/ops/int.scala @@ -183,7 +183,7 @@ object int: * ``` * @syntax markdown */ - @deprecated("Use compiletime.ops.any.ToString instead.","3.1.0") + @deprecated("Use compiletime.ops.any.ToString instead.","3.2.0") type ToString[X <: Int] <: String /** Long conversion of an `Int` singleton type. diff --git a/tests/neg/singleton-ops-int.scala b/tests/neg/singleton-ops-int.scala index ca2d3d6ea107..e85b6204d1fa 100644 --- a/tests/neg/singleton-ops-int.scala +++ b/tests/neg/singleton-ops-int.scala @@ -71,8 +71,8 @@ object Test { val t46: Min[2, 2] = 1 // error val t47: Min[-1, -1] = 0 // error - val t48: ToString[213] = "213" // warning (deprecation) - val t49: ToString[-1] = "-1" // warning (deprecation) + val t48: ToString[213] = "213" + val t49: ToString[-1] = "-1" val t52: 1 ^ 2 = 3 val t53: 1 ^ 3 = 3 // error From d433ce848f83674e46dd17f268c239017e6fb18c Mon Sep 17 00:00:00 2001 From: oronpo Date: Mon, 25 Oct 2021 19:31:49 -0400 Subject: [PATCH 4/9] Address review comments. Main changes: * Apply common protection against wrong number of arguments. * Exception in an operation is converted into a type error. --- .../src/dotty/tools/dotc/core/Types.scala | 240 +++++++++--------- 1 file changed, 126 insertions(+), 114 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0a3b7ff130a8..b8f489c3eab4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4196,6 +4196,11 @@ object Types { case tycon: TypeRef if defn.isCompiletimeAppliedType(tycon.symbol) => extension (tp : Type) def fixForEvaluation : Type = tp.normalized.dealias match { + //enable operations for constant singleton terms. E.g.: + //``` + //final val one = 1 + //type Two = one.type + one.type + //``` case tp : TermRef => tp.underlying case tp => tp } @@ -4234,23 +4239,43 @@ object Types { case ConstantType(Constant(n: String)) => Some(n) case _ => None } - def isConst : Option[Type] = args.head.fixForEvaluation match { + + def isConst(tp : Type) : Option[Type] = tp.fixForEvaluation match { case ConstantType(_) => Some(ConstantType(Constant(true))) case _ => Some(ConstantType(Constant(false))) } + + def expectArgsNum(expectedNum : Int) : Unit = + //We can use assert instead of a compiler type error because this error should not + //occur since the type signature of the operation enforces the proper number of args. + assert(args.length == expectedNum, s"Type operation expects $expectedNum arguments but found ${args.length}") + def natValue(tp: Type): Option[Int] = intValue(tp).filter(n => n >= 0 && n < Int.MaxValue) + //Runs the op and returns the result as a constant type. + //If the op throws an exception, then this exception is converted into a type error. + def runConstantOp(op : => Any): Type = + val result = try { + op + } catch { + case e : Throwable => + throw new TypeError(e.getMessage) + } + ConstantType(Constant(result)) + def constantFold1[T](extractor: Type => Option[T], op: T => Any): Option[Type] = - extractor(args.head).map(a => ConstantType(Constant(op(a)))) + expectArgsNum(1) + extractor(args.head).map(a => runConstantOp(op(a))) def constantFold2[T](extractor: Type => Option[T], op: (T, T) => Any): Option[Type] = constantFold2AB(extractor, extractor, op) def constantFold2AB[TA, TB](extractorA: Type => Option[TA], extractorB: Type => Option[TB], op: (TA, TB) => Any): Option[Type] = + expectArgsNum(2) for { - a <- extractorA(args.head) - b <- extractorB(args.last) - } yield ConstantType(Constant(op(a, b))) + a <- extractorA(args(0)) + b <- extractorB(args(1)) + } yield runConstantOp(op(a, b)) def constantFold3[TA, TB, TC]( extractorA: Type => Option[TA], @@ -4258,139 +4283,126 @@ object Types { extractorC: Type => Option[TC], op: (TA, TB, TC) => Any ): Option[Type] = + expectArgsNum(3) for { - a <- extractorA(args.head) + a <- extractorA(args(0)) b <- extractorB(args(1)) - c <- extractorC(args.last) - } yield ConstantType(Constant(op(a, b, c))) + c <- extractorC(args(2)) + } yield runConstantOp(op(a, b, c)) trace(i"compiletime constant fold $this", typr, show = true) { val name = tycon.symbol.name val owner = tycon.symbol.owner - val nArgs = args.length val constantType = if (defn.isCompiletime_S(tycon.symbol)) { - if (nArgs == 1) constantFold1(natValue, _ + 1) - else None + constantFold1(natValue, _ + 1) } else if (owner == defn.CompiletimeOpsAnyModuleClass) name match { - case tpnme.Equals if nArgs == 2 => constantFold2(constValue, _ == _) - case tpnme.NotEquals if nArgs == 2 => constantFold2(constValue, _ != _) - case tpnme.ToString if nArgs == 1 => constantFold1(constValue, _.toString) - case tpnme.IsConst if nArgs == 1 => isConst + case tpnme.Equals => constantFold2(constValue, _ == _) + case tpnme.NotEquals => constantFold2(constValue, _ != _) + case tpnme.ToString => constantFold1(constValue, _.toString) + case tpnme.IsConst => isConst(args.head) case _ => None } else if (owner == defn.CompiletimeOpsIntModuleClass) name match { - case tpnme.Abs if nArgs == 1 => constantFold1(intValue, _.abs) - case tpnme.Negate if nArgs == 1 => constantFold1(intValue, x => -x) + case tpnme.Abs => constantFold1(intValue, _.abs) + case tpnme.Negate => constantFold1(intValue, x => -x) //ToString is deprecated for ops.int, and moved to ops.any - case tpnme.ToString if nArgs == 1 => constantFold1(intValue, _.toString) - case tpnme.Plus if nArgs == 2 => constantFold2(intValue, _ + _) - case tpnme.Minus if nArgs == 2 => constantFold2(intValue, _ - _) - case tpnme.Times if nArgs == 2 => constantFold2(intValue, _ * _) - case tpnme.Div if nArgs == 2 => constantFold2(intValue, { - case (_, 0) => throw new TypeError("Division by 0") - case (a, b) => a / b - }) - case tpnme.Mod if nArgs == 2 => constantFold2(intValue, { - case (_, 0) => throw new TypeError("Modulo by 0") - case (a, b) => a % b - }) - case tpnme.Lt if nArgs == 2 => constantFold2(intValue, _ < _) - case tpnme.Gt if nArgs == 2 => constantFold2(intValue, _ > _) - case tpnme.Ge if nArgs == 2 => constantFold2(intValue, _ >= _) - case tpnme.Le if nArgs == 2 => constantFold2(intValue, _ <= _) - case tpnme.Xor if nArgs == 2 => constantFold2(intValue, _ ^ _) - case tpnme.BitwiseAnd if nArgs == 2 => constantFold2(intValue, _ & _) - case tpnme.BitwiseOr if nArgs == 2 => constantFold2(intValue, _ | _) - case tpnme.ASR if nArgs == 2 => constantFold2(intValue, _ >> _) - case tpnme.LSL if nArgs == 2 => constantFold2(intValue, _ << _) - case tpnme.LSR if nArgs == 2 => constantFold2(intValue, _ >>> _) - case tpnme.Min if nArgs == 2 => constantFold2(intValue, _ min _) - case tpnme.Max if nArgs == 2 => constantFold2(intValue, _ max _) - case tpnme.NumberOfLeadingZeros if nArgs == 1 => constantFold1(intValue, Integer.numberOfLeadingZeros(_)) - case tpnme.ToLong if nArgs == 1 => constantFold1(intValue, _.toLong) - case tpnme.ToFloat if nArgs == 1 => constantFold1(intValue, _.toFloat) - case tpnme.ToDouble if nArgs == 1 => constantFold1(intValue, _.toDouble) + case tpnme.ToString => constantFold1(intValue, _.toString) + case tpnme.Plus => constantFold2(intValue, _ + _) + case tpnme.Minus => constantFold2(intValue, _ - _) + case tpnme.Times => constantFold2(intValue, _ * _) + case tpnme.Div => constantFold2(intValue, _ / _) + case tpnme.Mod => constantFold2(intValue, _ % _) + case tpnme.Lt => constantFold2(intValue, _ < _) + case tpnme.Gt => constantFold2(intValue, _ > _) + case tpnme.Ge => constantFold2(intValue, _ >= _) + case tpnme.Le => constantFold2(intValue, _ <= _) + case tpnme.Xor => constantFold2(intValue, _ ^ _) + case tpnme.BitwiseAnd => constantFold2(intValue, _ & _) + case tpnme.BitwiseOr => constantFold2(intValue, _ | _) + case tpnme.ASR => constantFold2(intValue, _ >> _) + case tpnme.LSL => constantFold2(intValue, _ << _) + case tpnme.LSR => constantFold2(intValue, _ >>> _) + case tpnme.Min => constantFold2(intValue, _ min _) + case tpnme.Max => constantFold2(intValue, _ max _) + case tpnme.NumberOfLeadingZeros => constantFold1(intValue, Integer.numberOfLeadingZeros(_)) + case tpnme.ToLong => constantFold1(intValue, _.toLong) + case tpnme.ToFloat => constantFold1(intValue, _.toFloat) + case tpnme.ToDouble => constantFold1(intValue, _.toDouble) case _ => None } else if (owner == defn.CompiletimeOpsLongModuleClass) name match { - case tpnme.Abs if nArgs == 1 => constantFold1(longValue, _.abs) - case tpnme.Negate if nArgs == 1 => constantFold1(longValue, x => -x) - case tpnme.Plus if nArgs == 2 => constantFold2(longValue, _ + _) - case tpnme.Minus if nArgs == 2 => constantFold2(longValue, _ - _) - case tpnme.Times if nArgs == 2 => constantFold2(longValue, _ * _) - case tpnme.Div if nArgs == 2 => constantFold2(longValue, { - case (_, 0L) => throw new TypeError("Division by 0") - case (a, b) => a / b - }) - case tpnme.Mod if nArgs == 2 => constantFold2(longValue, { - case (_, 0L) => throw new TypeError("Modulo by 0") - case (a, b) => a % b - }) - case tpnme.Lt if nArgs == 2 => constantFold2(longValue, _ < _) - case tpnme.Gt if nArgs == 2 => constantFold2(longValue, _ > _) - case tpnme.Ge if nArgs == 2 => constantFold2(longValue, _ >= _) - case tpnme.Le if nArgs == 2 => constantFold2(longValue, _ <= _) - case tpnme.Xor if nArgs == 2 => constantFold2(longValue, _ ^ _) - case tpnme.BitwiseAnd if nArgs == 2 => constantFold2(longValue, _ & _) - case tpnme.BitwiseOr if nArgs == 2 => constantFold2(longValue, _ | _) - case tpnme.ASR if nArgs == 2 => constantFold2(longValue, _ >> _) - case tpnme.LSL if nArgs == 2 => constantFold2(longValue, _ << _) - case tpnme.LSR if nArgs == 2 => constantFold2(longValue, _ >>> _) - case tpnme.Min if nArgs == 2 => constantFold2(longValue, _ min _) - case tpnme.Max if nArgs == 2 => constantFold2(longValue, _ max _) - case tpnme.NumberOfLeadingZeros if nArgs == 1 => + case tpnme.Abs => constantFold1(longValue, _.abs) + case tpnme.Negate => constantFold1(longValue, x => -x) + case tpnme.Plus => constantFold2(longValue, _ + _) + case tpnme.Minus => constantFold2(longValue, _ - _) + case tpnme.Times => constantFold2(longValue, _ * _) + case tpnme.Div => constantFold2(longValue, _ / _) + case tpnme.Mod => constantFold2(longValue, _ % _) + case tpnme.Lt => constantFold2(longValue, _ < _) + case tpnme.Gt => constantFold2(longValue, _ > _) + case tpnme.Ge => constantFold2(longValue, _ >= _) + case tpnme.Le => constantFold2(longValue, _ <= _) + case tpnme.Xor => constantFold2(longValue, _ ^ _) + case tpnme.BitwiseAnd => constantFold2(longValue, _ & _) + case tpnme.BitwiseOr => constantFold2(longValue, _ | _) + case tpnme.ASR => constantFold2(longValue, _ >> _) + case tpnme.LSL => constantFold2(longValue, _ << _) + case tpnme.LSR => constantFold2(longValue, _ >>> _) + case tpnme.Min => constantFold2(longValue, _ min _) + case tpnme.Max => constantFold2(longValue, _ max _) + case tpnme.NumberOfLeadingZeros => constantFold1(longValue, java.lang.Long.numberOfLeadingZeros(_)) - case tpnme.ToInt if nArgs == 1 => constantFold1(longValue, _.toInt) - case tpnme.ToFloat if nArgs == 1 => constantFold1(longValue, _.toFloat) - case tpnme.ToDouble if nArgs == 1 => constantFold1(longValue, _.toDouble) + case tpnme.ToInt => constantFold1(longValue, _.toInt) + case tpnme.ToFloat => constantFold1(longValue, _.toFloat) + case tpnme.ToDouble => constantFold1(longValue, _.toDouble) case _ => None } else if (owner == defn.CompiletimeOpsFloatModuleClass) name match { - case tpnme.Abs if nArgs == 1 => constantFold1(floatValue, _.abs) - case tpnme.Negate if nArgs == 1 => constantFold1(floatValue, x => -x) - case tpnme.Plus if nArgs == 2 => constantFold2(floatValue, _ + _) - case tpnme.Minus if nArgs == 2 => constantFold2(floatValue, _ - _) - case tpnme.Times if nArgs == 2 => constantFold2(floatValue, _ * _) - case tpnme.Div if nArgs == 2 => constantFold2(floatValue, _ / _) - case tpnme.Mod if nArgs == 2 => constantFold2(floatValue, _ % _) - case tpnme.Lt if nArgs == 2 => constantFold2(floatValue, _ < _) - case tpnme.Gt if nArgs == 2 => constantFold2(floatValue, _ > _) - case tpnme.Ge if nArgs == 2 => constantFold2(floatValue, _ >= _) - case tpnme.Le if nArgs == 2 => constantFold2(floatValue, _ <= _) - case tpnme.Min if nArgs == 2 => constantFold2(floatValue, _ min _) - case tpnme.Max if nArgs == 2 => constantFold2(floatValue, _ max _) - case tpnme.ToInt if nArgs == 1 => constantFold1(floatValue, _.toInt) - case tpnme.ToLong if nArgs == 1 => constantFold1(floatValue, _.toLong) - case tpnme.ToDouble if nArgs == 1 => constantFold1(floatValue, _.toDouble) + case tpnme.Abs => constantFold1(floatValue, _.abs) + case tpnme.Negate => constantFold1(floatValue, x => -x) + case tpnme.Plus => constantFold2(floatValue, _ + _) + case tpnme.Minus => constantFold2(floatValue, _ - _) + case tpnme.Times => constantFold2(floatValue, _ * _) + case tpnme.Div => constantFold2(floatValue, _ / _) + case tpnme.Mod => constantFold2(floatValue, _ % _) + case tpnme.Lt => constantFold2(floatValue, _ < _) + case tpnme.Gt => constantFold2(floatValue, _ > _) + case tpnme.Ge => constantFold2(floatValue, _ >= _) + case tpnme.Le => constantFold2(floatValue, _ <= _) + case tpnme.Min => constantFold2(floatValue, _ min _) + case tpnme.Max => constantFold2(floatValue, _ max _) + case tpnme.ToInt => constantFold1(floatValue, _.toInt) + case tpnme.ToLong => constantFold1(floatValue, _.toLong) + case tpnme.ToDouble => constantFold1(floatValue, _.toDouble) case _ => None } else if (owner == defn.CompiletimeOpsDoubleModuleClass) name match { - case tpnme.Abs if nArgs == 1 => constantFold1(doubleValue, _.abs) - case tpnme.Negate if nArgs == 1 => constantFold1(doubleValue, x => -x) - case tpnme.Plus if nArgs == 2 => constantFold2(doubleValue, _ + _) - case tpnme.Minus if nArgs == 2 => constantFold2(doubleValue, _ - _) - case tpnme.Times if nArgs == 2 => constantFold2(doubleValue, _ * _) - case tpnme.Div if nArgs == 2 => constantFold2(doubleValue, _ / _) - case tpnme.Mod if nArgs == 2 => constantFold2(doubleValue, _ % _) - case tpnme.Lt if nArgs == 2 => constantFold2(doubleValue, _ < _) - case tpnme.Gt if nArgs == 2 => constantFold2(doubleValue, _ > _) - case tpnme.Ge if nArgs == 2 => constantFold2(doubleValue, _ >= _) - case tpnme.Le if nArgs == 2 => constantFold2(doubleValue, _ <= _) - case tpnme.Min if nArgs == 2 => constantFold2(doubleValue, _ min _) - case tpnme.Max if nArgs == 2 => constantFold2(doubleValue, _ max _) - case tpnme.ToInt if nArgs == 1 => constantFold1(doubleValue, _.toInt) - case tpnme.ToLong if nArgs == 1 => constantFold1(doubleValue, _.toLong) - case tpnme.ToFloat if nArgs == 1 => constantFold1(doubleValue, _.toFloat) + case tpnme.Abs => constantFold1(doubleValue, _.abs) + case tpnme.Negate => constantFold1(doubleValue, x => -x) + case tpnme.Plus => constantFold2(doubleValue, _ + _) + case tpnme.Minus => constantFold2(doubleValue, _ - _) + case tpnme.Times => constantFold2(doubleValue, _ * _) + case tpnme.Div => constantFold2(doubleValue, _ / _) + case tpnme.Mod => constantFold2(doubleValue, _ % _) + case tpnme.Lt => constantFold2(doubleValue, _ < _) + case tpnme.Gt => constantFold2(doubleValue, _ > _) + case tpnme.Ge => constantFold2(doubleValue, _ >= _) + case tpnme.Le => constantFold2(doubleValue, _ <= _) + case tpnme.Min => constantFold2(doubleValue, _ min _) + case tpnme.Max => constantFold2(doubleValue, _ max _) + case tpnme.ToInt => constantFold1(doubleValue, _.toInt) + case tpnme.ToLong => constantFold1(doubleValue, _.toLong) + case tpnme.ToFloat => constantFold1(doubleValue, _.toFloat) case _ => None } else if (owner == defn.CompiletimeOpsStringModuleClass) name match { - case tpnme.Plus if nArgs == 2 => constantFold2(stringValue, _ + _) - case tpnme.Length if nArgs == 1 => constantFold1(stringValue, _.length) - case tpnme.Matches if nArgs == 2 => constantFold2(stringValue, _ matches _) - case tpnme.Substring if nArgs == 3 => + case tpnme.Plus => constantFold2(stringValue, _ + _) + case tpnme.Length => constantFold1(stringValue, _.length) + case tpnme.Matches => constantFold2(stringValue, _ matches _) + case tpnme.Substring => constantFold3(stringValue, intValue, intValue, (s, b, e) => s.substring(b, e)) case _ => None } else if (owner == defn.CompiletimeOpsBooleanModuleClass) name match { - case tpnme.Not if nArgs == 1 => constantFold1(boolValue, x => !x) - case tpnme.And if nArgs == 2 => constantFold2(boolValue, _ && _) - case tpnme.Or if nArgs == 2 => constantFold2(boolValue, _ || _) - case tpnme.Xor if nArgs == 2 => constantFold2(boolValue, _ ^ _) + case tpnme.Not => constantFold1(boolValue, x => !x) + case tpnme.And => constantFold2(boolValue, _ && _) + case tpnme.Or => constantFold2(boolValue, _ || _) + case tpnme.Xor => constantFold2(boolValue, _ ^ _) case _ => None } else None From e8d00f8e2f8f2224ed9e7e22667c614da30a25e4 Mon Sep 17 00:00:00 2001 From: oronpo Date: Mon, 25 Oct 2021 21:10:02 -0400 Subject: [PATCH 5/9] improve `any.IsConst` to be evaluated only if its argument is concrete and known --- .../src/dotty/tools/dotc/core/Types.scala | 35 ++++++++++++++++--- library/src/scala/compiletime/ops/any.scala | 9 +++++ tests/neg/singleton-ops-any.scala | 8 ++++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b8f489c3eab4..0571d1f31ef1 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4240,9 +4240,36 @@ object Types { case _ => None } - def isConst(tp : Type) : Option[Type] = tp.fixForEvaluation match { - case ConstantType(_) => Some(ConstantType(Constant(true))) - case _ => Some(ConstantType(Constant(false))) + val opsSet = Set( + defn.CompiletimeOpsAnyModuleClass, + defn.CompiletimeOpsIntModuleClass, + defn.CompiletimeOpsLongModuleClass, + defn.CompiletimeOpsFloatModuleClass, + defn.CompiletimeOpsBooleanModuleClass, + defn.CompiletimeOpsStringModuleClass + ) + + //Returns Some(true) if the type is a constant. + //Returns Some(false) if the type is not a constant. + //Returns None if there is not enough information to determine if the type is a constant. + //The type is a constant if it is a constant type or a type operation composition of constant types. + //If we get a type reference for an argument, then the result is not yet known. + def isConst(tp : Type) : Option[Boolean] = tp.dealias match { + //known to be constant + case ConstantType(_) => Some(true) + //currently not a concrete known type + case TypeRef(NoPrefix,_) => None + //currently not a concrete known type + case _ : TypeParamRef => None + //constant if the term is constant + case t : TermRef => isConst(t.underlying) + //an operation type => recursively check all argument compositions + case applied : AppliedType if opsSet.contains(applied.typeSymbol.owner) => + val argsConst = applied.args.map(isConst) + if (argsConst.exists(_.isEmpty)) None + else Some(argsConst.forall(_.get)) + //all other types are considered not to be constant + case _ => Some(false) } def expectArgsNum(expectedNum : Int) : Unit = @@ -4300,7 +4327,7 @@ object Types { case tpnme.Equals => constantFold2(constValue, _ == _) case tpnme.NotEquals => constantFold2(constValue, _ != _) case tpnme.ToString => constantFold1(constValue, _.toString) - case tpnme.IsConst => isConst(args.head) + case tpnme.IsConst => isConst(args.head).map(b => ConstantType(Constant(b))) case _ => None } else if (owner == defn.CompiletimeOpsIntModuleClass) name match { case tpnme.Abs => constantFold1(intValue, _.abs) diff --git a/library/src/scala/compiletime/ops/any.scala b/library/src/scala/compiletime/ops/any.scala index 60b86414a385..56605979474d 100644 --- a/library/src/scala/compiletime/ops/any.scala +++ b/library/src/scala/compiletime/ops/any.scala @@ -29,6 +29,15 @@ object any: * val c1: IsConst[1] = true * val c2: IsConst["hi"] = true * val c3: IsConst[false] = true + * val c4: IsConst[Any] = false + * ``` + * If the type is not yet known, then `IsConst` remains unevaluated, and + * will be evaluated only at its concrete type application. E.g.: + * ```scala + * //def `isConst`` returns the type `IsConst[X]`, since `X` is not yet known. + * def isConst[X] : IsConst[X] = ??? + * val c5 : true = isConst[1] //now the type is known to be a constant + * val c6 : false = isConst[Any] //now the type is known to be not a constant * ``` * @syntax markdown */ diff --git a/tests/neg/singleton-ops-any.scala b/tests/neg/singleton-ops-any.scala index 33951f58bcd9..45f63cf68ff1 100644 --- a/tests/neg/singleton-ops-any.scala +++ b/tests/neg/singleton-ops-any.scala @@ -30,5 +30,11 @@ object Test { final val two = 2 val t47 : IsConst[two.type] = true val t48: IsConst[Any] = true // error - + def isConst[X] : IsConst[X] = ??? + val t49 : true = isConst[1] + val t50 : false = isConst[one.type] + def isConst2[X <: Int, Y <: Int] : IsConst[X == Y] = ??? + val t51 : true = isConst2[1, 1] + val t52 : false = isConst2[1, one.type] + val t53 : true = isConst2[1, two.type] } From 762d90edd4222c1ce066a8ff9343a48a721c8b0c Mon Sep 17 00:00:00 2001 From: oronpo Date: Mon, 25 Oct 2021 21:44:16 -0400 Subject: [PATCH 6/9] Fix documentation errors. Remove redundant `long.ToString` --- library/src/scala/compiletime/ops/double.scala | 6 +++--- library/src/scala/compiletime/ops/float.scala | 6 +++--- library/src/scala/compiletime/ops/long.scala | 14 +++----------- library/src/scala/compiletime/ops/string.scala | 2 +- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/library/src/scala/compiletime/ops/double.scala b/library/src/scala/compiletime/ops/double.scala index e06ff64c3f05..e9b13ab9dcd6 100644 --- a/library/src/scala/compiletime/ops/double.scala +++ b/library/src/scala/compiletime/ops/double.scala @@ -31,7 +31,7 @@ object double: /** Integer division of two `Double` singleton types. * ```scala - * val div: 5.0 / 2.0 = 2.0 + * val div: 5.0 / 2.0 = 2.5 * ``` * @syntax markdown */ @@ -91,8 +91,8 @@ object double: /** Negation of an `Double` singleton type. * ```scala - * val neg1: Neg[-1.0] = 1.0 - * val neg2: Neg[1.0] = -1.0 + * val neg1: Negate[-1.0] = 1.0 + * val neg2: Negate[1.0] = -1.0 * ``` * @syntax markdown */ diff --git a/library/src/scala/compiletime/ops/float.scala b/library/src/scala/compiletime/ops/float.scala index d5aeb2d5f4f7..27a1a19c17c1 100644 --- a/library/src/scala/compiletime/ops/float.scala +++ b/library/src/scala/compiletime/ops/float.scala @@ -31,7 +31,7 @@ object float: /** Integer division of two `Float` singleton types. * ```scala - * val div: 5.0f / 2.0f = 2.0f + * val div: 5.0f / 2.0f = 2.5f * ``` * @syntax markdown */ @@ -91,8 +91,8 @@ object float: /** Negation of an `Float` singleton type. * ```scala - * val neg1: Neg[-1.0f] = 1.0f - * val neg2: Neg[1.0f] = -1.0f + * val neg1: Negate[-1.0f] = 1.0f + * val neg2: Negate[1.0f] = -1.0f * ``` * @syntax markdown */ diff --git a/library/src/scala/compiletime/ops/long.scala b/library/src/scala/compiletime/ops/long.scala index f3158e12c2f6..92e6bd370f5b 100644 --- a/library/src/scala/compiletime/ops/long.scala +++ b/library/src/scala/compiletime/ops/long.scala @@ -12,7 +12,7 @@ object long: * case 0L => 1L * case 1L => 2L * case 2L => 3L - * ... + * // ... * case 9223372036854775806L => 9223372036854775807L * } * ``` @@ -155,8 +155,8 @@ object long: /** Negation of an `Long` singleton type. * ```scala - * val neg1: Neg[-1L] = 1L - * val neg2: Neg[1L] = -1L + * val neg1: Negate[-1L] = 1L + * val neg2: Negate[1L] = -1L * ``` * @syntax markdown */ @@ -178,14 +178,6 @@ object long: */ type Max[X <: Long, Y <: Long] <: Long - /** String conversion of an `Long` singleton type. - * ```scala - * val abs: ToString[1L] = "1" - * ``` - * @syntax markdown - */ - type ToString[X <: Long] <: String - /** Number of zero bits preceding the highest-order ("leftmost") * one-bit in the two's complement binary representation of the specified `Long` singleton type. * Returns 64 if the specified singleton type has no one-bits in its two's complement representation, diff --git a/library/src/scala/compiletime/ops/string.scala b/library/src/scala/compiletime/ops/string.scala index 51e336ab7adc..6214f983db32 100644 --- a/library/src/scala/compiletime/ops/string.scala +++ b/library/src/scala/compiletime/ops/string.scala @@ -14,7 +14,7 @@ object string: /** Length of a `String` singleton type. * ```scala - * val helloSize: Size["hello"] = 5 + * val helloSize: Length["hello"] = 5 * ``` * @syntax markdown */ From 3083cae4158481c54a88c6a7ad0eb9b7d202b4da Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Wed, 17 Nov 2021 17:43:36 +0100 Subject: [PATCH 7/9] Fix code formatting --- .../dotty/tools/dotc/core/Definitions.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 60 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index dc0856a95101..804dcf2e87e9 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1090,7 +1090,7 @@ class Definitions { tpnme.Abs, tpnme.Negate, tpnme.Min, tpnme.Max ) private val compiletimePackageIntTypes: Set[Name] = compiletimePackageNumericTypes ++ Set[Name]( - tpnme.ToString, //ToString is moved to ops.any and deprecated for ops.int + tpnme.ToString, // ToString is moved to ops.any and deprecated for ops.int tpnme.NumberOfLeadingZeros, tpnme.ToLong, tpnme.ToFloat, tpnme.ToDouble, tpnme.Xor, tpnme.BitwiseAnd, tpnme.BitwiseOr, tpnme.ASR, tpnme.LSL, tpnme.LSR ) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 287030136b00..b1007a32f5c4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4206,14 +4206,14 @@ object Types { def tryCompiletimeConstantFold(using Context): Type = tycon match { case tycon: TypeRef if defn.isCompiletimeAppliedType(tycon.symbol) => - extension (tp : Type) def fixForEvaluation : Type = + extension (tp: Type) def fixForEvaluation: Type = tp.normalized.dealias match { - //enable operations for constant singleton terms. E.g.: - //``` - //final val one = 1 - //type Two = one.type + one.type - //``` - case tp : TermRef => tp.underlying + // enable operations for constant singleton terms. E.g.: + // ``` + // final val one = 1 + // type Two = one.type + one.type + // ``` + case tp: TermRef => tp.underlying case tp => tp } @@ -4261,43 +4261,43 @@ object Types { defn.CompiletimeOpsStringModuleClass ) - //Returns Some(true) if the type is a constant. - //Returns Some(false) if the type is not a constant. - //Returns None if there is not enough information to determine if the type is a constant. - //The type is a constant if it is a constant type or a type operation composition of constant types. - //If we get a type reference for an argument, then the result is not yet known. - def isConst(tp : Type) : Option[Boolean] = tp.dealias match { - //known to be constant + // Returns Some(true) if the type is a constant. + // Returns Some(false) if the type is not a constant. + // Returns None if there is not enough information to determine if the type is a constant. + // The type is a constant if it is a constant type or a type operation composition of constant types. + // If we get a type reference for an argument, then the result is not yet known. + def isConst(tp: Type): Option[Boolean] = tp.dealias match { + // known to be constant case ConstantType(_) => Some(true) - //currently not a concrete known type + // currently not a concrete known type case TypeRef(NoPrefix,_) => None - //currently not a concrete known type - case _ : TypeParamRef => None - //constant if the term is constant - case t : TermRef => isConst(t.underlying) - //an operation type => recursively check all argument compositions - case applied : AppliedType if opsSet.contains(applied.typeSymbol.owner) => + // currently not a concrete known type + case _: TypeParamRef => None + // constant if the term is constant + case t: TermRef => isConst(t.underlying) + // an operation type => recursively check all argument compositions + case applied: AppliedType if opsSet.contains(applied.typeSymbol.owner) => val argsConst = applied.args.map(isConst) if (argsConst.exists(_.isEmpty)) None else Some(argsConst.forall(_.get)) - //all other types are considered not to be constant + // all other types are considered not to be constant case _ => Some(false) } - def expectArgsNum(expectedNum : Int) : Unit = - //We can use assert instead of a compiler type error because this error should not - //occur since the type signature of the operation enforces the proper number of args. + def expectArgsNum(expectedNum: Int): Unit = + // We can use assert instead of a compiler type error because this error should not + // occur since the type signature of the operation enforces the proper number of args. assert(args.length == expectedNum, s"Type operation expects $expectedNum arguments but found ${args.length}") def natValue(tp: Type): Option[Int] = intValue(tp).filter(n => n >= 0 && n < Int.MaxValue) - //Runs the op and returns the result as a constant type. - //If the op throws an exception, then this exception is converted into a type error. - def runConstantOp(op : => Any): Type = + // Runs the op and returns the result as a constant type. + // If the op throws an exception, then this exception is converted into a type error. + def runConstantOp(op: => Any): Type = val result = try { op } catch { - case e : Throwable => + case e: Throwable => throw new TypeError(e.getMessage) } ConstantType(Constant(result)) @@ -4344,7 +4344,7 @@ object Types { } else if (owner == defn.CompiletimeOpsIntModuleClass) name match { case tpnme.Abs => constantFold1(intValue, _.abs) case tpnme.Negate => constantFold1(intValue, x => -x) - //ToString is deprecated for ops.int, and moved to ops.any + // ToString is deprecated for ops.int, and moved to ops.any case tpnme.ToString => constantFold1(intValue, _.toString) case tpnme.Plus => constantFold2(intValue, _ + _) case tpnme.Minus => constantFold2(intValue, _ - _) From 37695628652976096424ae65781ea3b47400c5d8 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 22 Nov 2021 12:04:50 +0200 Subject: [PATCH 8/9] Add missing colon --- library/src/scala/compiletime/ops/long.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/scala/compiletime/ops/long.scala b/library/src/scala/compiletime/ops/long.scala index 92e6bd370f5b..718dec710068 100644 --- a/library/src/scala/compiletime/ops/long.scala +++ b/library/src/scala/compiletime/ops/long.scala @@ -5,7 +5,7 @@ import scala.annotation.experimental @experimental object long: - /** Successor of a natural number where zero is the type 0 and successors are reduced as if the definition was + /** Successor of a natural number where zero is the type 0 and successors are reduced as if the definition was: * * ```scala * type S[N <: Long] <: Long = N match { From 18c23f49c3dd1e56e6feae00f7954840b61862be Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 22 Nov 2021 12:05:27 +0200 Subject: [PATCH 9/9] Add missing colon --- library/src/scala/compiletime/ops/int.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/scala/compiletime/ops/int.scala b/library/src/scala/compiletime/ops/int.scala index 8d62b98a27e4..3776e9e0d0d0 100644 --- a/library/src/scala/compiletime/ops/int.scala +++ b/library/src/scala/compiletime/ops/int.scala @@ -4,7 +4,7 @@ package ops import annotation.experimental object int: - /** Successor of a natural number where zero is the type 0 and successors are reduced as if the definition was + /** Successor of a natural number where zero is the type 0 and successors are reduced as if the definition was: * * ```scala * type S[N <: Int] <: Int = N match { @@ -226,4 +226,4 @@ object int: * @syntax markdown */ @experimental - type NumberOfLeadingZeros[X <: Int] <: Int \ No newline at end of file + type NumberOfLeadingZeros[X <: Int] <: Int