diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 1268bdfde6ce..db0878d0d0b0 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -958,7 +958,7 @@ class Definitions { tpnme.Abs, tpnme.Negate, tpnme.Min, tpnme.Max, tpnme.ToString, ) 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.Error, tpnme.Plus) final def isCompiletimeAppliedType(sym: Symbol)(implicit ctx: Context): Boolean = { def isOpsPackageObjectAppliedType: Boolean = diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index f4179577ad90..cdfb91dbca76 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -211,6 +211,7 @@ object StdNames { final val Abs: N = "Abs" final val And: N = "&&" final val Div: N = "/" + final val Error: N = "Error" final val Equals: N = "==" final val Ge: N = ">=" final val Gt: N = ">" diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index fefe1b5c1cd1..d27e715cc38b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3715,7 +3715,8 @@ object Types { case tpnme.Max if nArgs == 2 => constantFold2(intValue, _ max _) case _ => None } else if (owner == defn.CompiletimeOpsPackageObjectString.moduleClass) name match { - case tpnme.Plus if nArgs == 2 => constantFold2(stringValue, _ + _) + case tpnme.Error if nArgs == 1 => constantFold1(stringValue, msg => throw new TypeError(msg)) + case tpnme.Plus if nArgs == 2 => constantFold2(stringValue, _ + _) case _ => None } else if (owner == defn.CompiletimeOpsPackageObjectBoolean.moduleClass) name match { case tpnme.Not if nArgs == 1 => constantFold1(boolValue, x => !x) diff --git a/library/src/scala/compiletime/ops/package.scala b/library/src/scala/compiletime/ops/package.scala index d7714f63e487..34b8d86197dd 100644 --- a/library/src/scala/compiletime/ops/package.scala +++ b/library/src/scala/compiletime/ops/package.scala @@ -24,6 +24,23 @@ package object ops { } object string { + /** Throws a compiletime error when evaluated by the compiler. For example: + * + * ```scala + * val x: Error["My error message"] = ??? // error: My error message + * ``` + * or + * ```scala + * import scala.compiletime.ops.int.>= + * type Positive[X <: Int] = (X >= 0) match { + * case true => X + * case false => Error["Expected a positive integer"] + * } + * val x: Positive[-1] = -1 // error: Expected a positive integer + * ``` + */ + type Error[Msg <: String] <: Nothing + /** Concatenation of two `String` singleton types. * ```scala * val hello: "hello " + "world" = "hello world" diff --git a/tests/neg/singleton-ops-error.check b/tests/neg/singleton-ops-error.check new file mode 100644 index 000000000000..96b3911e3b63 --- /dev/null +++ b/tests/neg/singleton-ops-error.check @@ -0,0 +1,12 @@ +-- Error: tests/neg/singleton-ops-error.scala:18:33 -------------------------------------------------------------------- +18 | val t1: Positive = Positive[-1](-1) // error + | ^ + | The provided value (-1) isn't positive +-- Error: tests/neg/singleton-ops-error.scala:20:23 -------------------------------------------------------------------- +20 | val t3 = Positive[-1](-1) // error + | ^ + | The provided value (-1) isn't positive +-- Error: tests/neg/singleton-ops-error.scala:22:11 -------------------------------------------------------------------- +22 | val err: Error["My error message"] = ??? // error + | ^ + | My error message diff --git a/tests/neg/singleton-ops-error.scala b/tests/neg/singleton-ops-error.scala new file mode 100644 index 000000000000..ba083e519f3f --- /dev/null +++ b/tests/neg/singleton-ops-error.scala @@ -0,0 +1,23 @@ +import scala.compiletime.ops.int.{ToString, >=} +import scala.compiletime.ops.string.{+, Error} + +object Test { + type Require[Cond <: Boolean, Msg <: String] = Cond match { + case true => Any + case false => Error[Msg] + } + + opaque type Positive = Int + + object Positive { + type RequirePositive[T <: Int] = Require[T >= 0, "The provided value (" + ToString[T] + ") isn't positive"] + def apply[T <: Int](value: T)(given RequirePositive[T]): Positive = value + } + + val t0: Positive = Positive[1](1) + val t1: Positive = Positive[-1](-1) // error + val t2 = Positive[1](1) + val t3 = Positive[-1](-1) // error + + val err: Error["My error message"] = ??? // error +}