Skip to content

Add compiletime Error type #7951

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ">"
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
17 changes: 17 additions & 0 deletions library/src/scala/compiletime/ops/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
12 changes: 12 additions & 0 deletions tests/neg/singleton-ops-error.check
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions tests/neg/singleton-ops-error.scala
Original file line number Diff line number Diff line change
@@ -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"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be Require[T > 0, ...

def apply[T <: Int](value: T)(given RequirePositive[T]): Positive = value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking about this, I'm actually surprised this is working. This style is similar to the singleton-ops library and assumes that there is an implicit for RequirePositive[T <: Int], but why should there be? If there is no error, RequirePositive[T <: Int] is actually Any, so how come it works?

}

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
}