diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index c5be301df8d7..19ddcf7b16fe 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 @@ -1078,19 +1081,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 @@ -1100,6 +1124,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 349f80d11d8a..4988b81918df 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 b384cbdcb084..b1007a32f5c4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4206,85 +4206,242 @@ 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 { + // 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 + } + + 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 { + 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 } + 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 = + // 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.normalized).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(0)) + b <- extractorB(args(1)) + } yield runConstantOp(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] = + expectArgsNum(3) for { - a <- extractor(args.head.normalized) - b <- extractor(args.tail.head.normalized) - } yield ConstantType(Constant(op(a, b))) + a <- extractorA(args(0)) + b <- extractorB(args(1)) + 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.Equals => constantFold2(constValue, _ == _) + case tpnme.NotEquals => constantFold2(constValue, _ != _) + case tpnme.ToString => constantFold1(constValue, _.toString) + case tpnme.IsConst => isConst(args.head).map(b => ConstantType(Constant(b))) 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.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.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 => 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 => 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 => 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 => 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 => 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.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 diff --git a/library/src/scala/compiletime/ops/any.scala b/library/src/scala/compiletime/ops/any.scala index 0cb7e6d06431..56605979474d 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 @@ -21,3 +23,33 @@ 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 + * 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 + */ + @experimental + type IsConst[X] <: Boolean + + /** String conversion of a constant singleton type. + * ```scala + * val s1: ToString[1] = "1" + * val sTrue: ToString[true] = "true" + * ``` + * @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 new file mode 100644 index 000000000000..e9b13ab9dcd6 --- /dev/null +++ b/library/src/scala/compiletime/ops/double.scala @@ -0,0 +1,139 @@ +package scala.compiletime +package ops + +import scala.annotation.experimental + +@experimental +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.5 + * ``` + * @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: Negate[-1.0] = 1.0 + * val neg2: Negate[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..27a1a19c17c1 --- /dev/null +++ b/library/src/scala/compiletime/ops/float.scala @@ -0,0 +1,139 @@ +package scala.compiletime +package ops + +import scala.annotation.experimental + +@experimental +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.5f + * ``` + * @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: Negate[-1.0f] = 1.0f + * val neg2: Negate[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..3776e9e0d0d0 100644 --- a/library/src/scala/compiletime/ops/int.scala +++ b/library/src/scala/compiletime/ops/int.scala @@ -1,8 +1,10 @@ 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 + /** 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 { @@ -181,4 +183,47 @@ object int: * ``` * @syntax markdown */ + @deprecated("Use compiletime.ops.any.ToString instead.","3.2.0") type ToString[X <: Int] <: String + + /** Long conversion of an `Int` singleton type. + * ```scala + * val x: ToLong[1] = 1L + * ``` + * @syntax markdown + */ + @experimental + type ToLong[X <: Int] <: Long + + /** Float conversion of an `Int` singleton type. + * ```scala + * val x: ToFloat[1] = 1.0f + * ``` + * @syntax markdown + */ + @experimental + type ToFloat[X <: Int] <: Float + + /** Double conversion of an `Int` singleton type. + * ```scala + * val x: ToDouble[1] = 1.0 + * ``` + * @syntax markdown + */ + @experimental + 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 + */ + @experimental + type NumberOfLeadingZeros[X <: Int] <: Int diff --git a/library/src/scala/compiletime/ops/long.scala b/library/src/scala/compiletime/ops/long.scala new file mode 100644 index 000000000000..718dec710068 --- /dev/null +++ b/library/src/scala/compiletime/ops/long.scala @@ -0,0 +1,217 @@ +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: + * + * ```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: Negate[-1L] = 1L + * val neg2: Negate[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 + + /** 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..6214f983db32 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 @@ -9,3 +11,35 @@ object string: * @syntax markdown */ type +[X <: String, Y <: String] <: String + + /** Length of a `String` singleton type. + * ```scala + * val helloSize: Length["hello"] = 5 + * ``` + * @syntax markdown + */ + @experimental + 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 + */ + @experimental + 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 + */ + @experimental + type Matches[S <: String, Regex <: String] <: Boolean diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index ae3673016db1..33ab5d2613e7 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -9,6 +9,12 @@ 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$"), // Should have been added in 3.1.0 // These are only allowed on imports and therefore should not be present in binaries emitted before 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..45f63cf68ff1 100644 --- a/tests/neg/singleton-ops-any.scala +++ b/tests/neg/singleton-ops-any.scala @@ -11,4 +11,30 @@ 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 + 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] } 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..e85b6204d1fa 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 @@ -70,8 +73,6 @@ object Test { val t48: ToString[213] = "213" val t49: ToString[-1] = "-1" - val t50: ToString[0] = "-0" // error - val t51: ToString[200] = "100" // error 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 }