Skip to content

Commit da71e1d

Browse files
committed
Move compiletime ops into subpackages
As discussed IRL with @OlivierBlanvillain, the previous approach with a match type dispatching to the correct overloaded op is not ideal, since adding more ops will mean modifying previous ops. For now, we therefore focus on having a good internal implementation of the ops. In some cases, the syntax is not ideal. For instance: import scala.compiletime.ops.int._ import scala.compiletime.ops.string._ summon[1 + 2] // ^ Reference to + is ambiguous // it is both imported by import scala.compiletime.ops.int._ // and imported subsequently by import scala.compiletime.ops.string._ Or: import scala.compiletime.ops._ summon[1 int.+ 2] // ^ an identifier expected, but '.' found These cases can be improved by allowing infix qualified types, or implementing resolution of member types (i.e. `A + B` as `A#+[B]` instead of `+[A, B]`).
1 parent 7b9ccb4 commit da71e1d

11 files changed

+187
-169
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ class Definitions {
236236
@tu lazy val CompiletimeOpsPackageObject: Symbol = ctx.requiredModule("scala.compiletime.ops.package")
237237
@tu lazy val CompiletimeOpsPackageObjectInt: Symbol = ctx.requiredModule("scala.compiletime.ops.package.int")
238238
@tu lazy val CompiletimeOpsPackageObjectString: Symbol = ctx.requiredModule("scala.compiletime.ops.package.string")
239+
@tu lazy val CompiletimeOpsPackageObjectBoolean: Symbol = ctx.requiredModule("scala.compiletime.ops.package.boolean")
239240

240241
/** The `scalaShadowing` package is used to safely modify classes and
241242
* objects in scala so that they can be used from dotty. They will
@@ -901,20 +902,20 @@ class Definitions {
901902
final def isCompiletime_S(sym: Symbol)(implicit ctx: Context): Boolean =
902903
sym.name == tpnme.S && sym.owner == CompiletimePackageObject.moduleClass
903904

904-
private val compiletimePackageTypes: Set[Name] = Set(
905-
tpnme.Equals, tpnme.NotEquals,
906-
tpnme.Minus, tpnme.Times, tpnme.Div, tpnme.Mod,
905+
private val compiletimePackageTypes: Set[Name] = Set(tpnme.Equals, tpnme.NotEquals)
906+
private val compiletimePackageIntTypes: Set[Name] = Set(
907+
tpnme.Plus, tpnme.Minus, tpnme.Times, tpnme.Div, tpnme.Mod,
907908
tpnme.Lt, tpnme.Gt, tpnme.Ge, tpnme.Le,
908909
tpnme.Abs, tpnme.Negate, tpnme.Min, tpnme.Max, tpnme.ToString,
909-
tpnme.Not, tpnme.Xor, tpnme.And, tpnme.Or
910910
)
911-
private val compiletimePackageIntTypes: Set[Name] = Set(tpnme.Plus)
911+
private val compiletimePackageBooleanTypes: Set[Name] = Set(tpnme.Not, tpnme.Xor, tpnme.And, tpnme.Or)
912912
private val compiletimePackageStringTypes: Set[Name] = Set(tpnme.Plus)
913913

914914
final def isCompiletimeAppliedType(sym: Symbol)(implicit ctx: Context): Boolean = {
915915
def isOpsPackageObjectAppliedType: Boolean =
916916
sym.owner == CompiletimeOpsPackageObject.moduleClass && compiletimePackageTypes.contains(sym.name) ||
917917
sym.owner == CompiletimeOpsPackageObjectInt.moduleClass && compiletimePackageIntTypes.contains(sym.name) ||
918+
sym.owner == CompiletimeOpsPackageObjectBoolean.moduleClass && compiletimePackageBooleanTypes.contains(sym.name) ||
918919
sym.owner == CompiletimeOpsPackageObjectString.moduleClass && compiletimePackageStringTypes.contains(sym.name)
919920

920921
sym.isType && (isCompiletime_S(sym) || isOpsPackageObjectAppliedType)

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3636,41 +3636,49 @@ object Types {
36363636
} yield ConstantType(Constant(op(a, b)))
36373637

36383638
trace(i"compiletime constant fold $this", typr, show = true) {
3639-
val constantType = if (args.length == 1) tycon.symbol.name match {
3640-
case tpnme.S => constantFold1(natValue, _ + 1)
3641-
case tpnme.Abs => constantFold1(intValue, _.abs)
3642-
case tpnme.Negate => constantFold1(intValue, x => -x)
3643-
case tpnme.Not => constantFold1(boolValue, x => !x)
3644-
case tpnme.ToString => constantFold1(intValue, _.toString)
3645-
case _ => None
3646-
} else if (args.length == 2) tycon.symbol.name match {
3647-
case tpnme.Equals => constantFold2(constValue, _ == _)
3648-
case tpnme.NotEquals => constantFold2(constValue, _ != _)
3649-
case tpnme.Plus if tycon.symbol.owner == defn.CompiletimeOpsPackageObjectInt.moduleClass =>
3650-
constantFold2(intValue, _ + _)
3651-
case tpnme.Plus if tycon.symbol.owner == defn.CompiletimeOpsPackageObjectString.moduleClass =>
3652-
constantFold2(stringValue, _ + _)
3653-
case tpnme.Minus => constantFold2(intValue, _ - _)
3654-
case tpnme.Times => constantFold2(intValue, _ * _)
3655-
case tpnme.Div => constantFold2(intValue, {
3656-
case (_, 0) => throw new TypeError("Division by 0")
3657-
case (a, b) => a / b
3658-
})
3659-
case tpnme.Mod => constantFold2(intValue, {
3660-
case (_, 0) => throw new TypeError("Modulo by 0")
3661-
case (a, b) => a % b
3662-
})
3663-
case tpnme.Lt => constantFold2(intValue, _ < _)
3664-
case tpnme.Gt => constantFold2(intValue, _ > _)
3665-
case tpnme.Ge => constantFold2(intValue, _ >= _)
3666-
case tpnme.Le => constantFold2(intValue, _ <= _)
3667-
case tpnme.Min => constantFold2(intValue, _ min _)
3668-
case tpnme.Max => constantFold2(intValue, _ max _)
3669-
case tpnme.And => constantFold2(boolValue, _ && _)
3670-
case tpnme.Or => constantFold2(boolValue, _ || _)
3671-
case tpnme.Xor => constantFold2(boolValue, _ ^ _)
3672-
case _ => None
3673-
} else None
3639+
val name = tycon.symbol.name
3640+
val owner = tycon.symbol.owner
3641+
val nArgs = args.length
3642+
val constantType =
3643+
if (owner == defn.CompiletimePackageObject.moduleClass) name match {
3644+
case tpnme.S if nArgs == 1 => constantFold1(natValue, _ + 1)
3645+
case _ => None
3646+
} else if (owner == defn.CompiletimeOpsPackageObject.moduleClass) name match {
3647+
case tpnme.Equals if nArgs == 2 => constantFold2(constValue, _ == _)
3648+
case tpnme.NotEquals if nArgs == 2 => constantFold2(constValue, _ != _)
3649+
case _ => None
3650+
} else if (owner == defn.CompiletimeOpsPackageObjectInt.moduleClass) name match {
3651+
case tpnme.Abs if nArgs == 1 => constantFold1(intValue, _.abs)
3652+
case tpnme.Negate if nArgs == 1 => constantFold1(intValue, x => -x)
3653+
case tpnme.ToString if nArgs == 1 => constantFold1(intValue, _.toString)
3654+
case tpnme.Plus if nArgs == 2 => constantFold2(intValue, _ + _)
3655+
case tpnme.Minus if nArgs == 2 => constantFold2(intValue, _ - _)
3656+
case tpnme.Times if nArgs == 2 => constantFold2(intValue, _ * _)
3657+
case tpnme.Div if nArgs == 2 => constantFold2(intValue, {
3658+
case (_, 0) => throw new TypeError("Division by 0")
3659+
case (a, b) => a / b
3660+
})
3661+
case tpnme.Mod if nArgs == 2 => constantFold2(intValue, {
3662+
case (_, 0) => throw new TypeError("Modulo by 0")
3663+
case (a, b) => a % b
3664+
})
3665+
case tpnme.Lt if nArgs == 2 => constantFold2(intValue, _ < _)
3666+
case tpnme.Gt if nArgs == 2 => constantFold2(intValue, _ > _)
3667+
case tpnme.Ge if nArgs == 2 => constantFold2(intValue, _ >= _)
3668+
case tpnme.Le if nArgs == 2 => constantFold2(intValue, _ <= _)
3669+
case tpnme.Min if nArgs == 2 => constantFold2(intValue, _ min _)
3670+
case tpnme.Max if nArgs == 2 => constantFold2(intValue, _ max _)
3671+
case _ => None
3672+
} else if (owner == defn.CompiletimeOpsPackageObjectString.moduleClass) name match {
3673+
case tpnme.Plus if nArgs == 2 => constantFold2(stringValue, _ + _)
3674+
case _ => None
3675+
} else if (owner == defn.CompiletimeOpsPackageObjectBoolean.moduleClass) name match {
3676+
case tpnme.Not if nArgs == 1 => constantFold1(boolValue, x => !x)
3677+
case tpnme.And if nArgs == 2 => constantFold2(boolValue, _ && _)
3678+
case tpnme.Or if nArgs == 2 => constantFold2(boolValue, _ || _)
3679+
case tpnme.Xor if nArgs == 2 => constantFold2(boolValue, _ ^ _)
3680+
case _ => None
3681+
} else None
36743682

36753683
constantType.getOrElse(NoType)
36763684
}

library/src/scala/compiletime/ops/package.scala

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,33 @@ package object ops {
66
@infix type ==[X <: AnyVal, Y <: AnyVal] <: Boolean
77
@infix type !=[X <: AnyVal, Y <: AnyVal] <: Boolean
88

9-
@infix type +[X <: Int | String, Y <: Int | String] = (X, Y) match {
10-
case (Int, Int) => int.+[X, Y]
11-
case (String, String) => string.+[X, Y]
12-
case (String, Int) => string.+[X, ToString[Y]]
13-
case (Int, String) => string.+[ToString[X], Y]
9+
object string {
10+
@infix type +[X <: String, Y <: String] <: String
1411
}
1512

16-
@infix type -[X <: Int, Y <: Int] <: Int
17-
@infix type *[X <: Int, Y <: Int] <: Int
18-
@infix type /[X <: Int, Y <: Int] <: Int
19-
@infix type %[X <: Int, Y <: Int] <: Int
20-
21-
@infix type <[X <: Int, Y <: Int] <: Boolean
22-
@infix type >[X <: Int, Y <: Int] <: Boolean
23-
@infix type >=[X <: Int, Y <: Int] <: Boolean
24-
@infix type <=[X <: Int, Y <: Int] <: Boolean
25-
26-
type Abs[X <: Int] <: Int
27-
type Negate[X <: Int] <: Int
28-
type Min[X <: Int, Y <: Int] <: Int
29-
type Max[X <: Int, Y <: Int] <: Int
30-
type ToString[X <: Int] <: String
31-
32-
type ![X <: Boolean] <: Boolean
33-
@infix type ^[X <: Boolean, Y <: Boolean] <: Boolean
34-
@infix type &&[X <: Boolean, Y <: Boolean] <: Boolean
35-
@infix type ||[X <: Boolean, Y <: Boolean] <: Boolean
36-
37-
private object string {
38-
type +[X <: String, Y <: String] <: String
13+
object int {
14+
@infix type +[X <: Int, Y <: Int] <: Int
15+
@infix type -[X <: Int, Y <: Int] <: Int
16+
@infix type *[X <: Int, Y <: Int] <: Int
17+
@infix type /[X <: Int, Y <: Int] <: Int
18+
@infix type %[X <: Int, Y <: Int] <: Int
19+
20+
@infix type <[X <: Int, Y <: Int] <: Boolean
21+
@infix type >[X <: Int, Y <: Int] <: Boolean
22+
@infix type >=[X <: Int, Y <: Int] <: Boolean
23+
@infix type <=[X <: Int, Y <: Int] <: Boolean
24+
25+
type Abs[X <: Int] <: Int
26+
type Negate[X <: Int] <: Int
27+
type Min[X <: Int, Y <: Int] <: Int
28+
type Max[X <: Int, Y <: Int] <: Int
29+
type ToString[X <: Int] <: String
3930
}
4031

41-
private object int {
42-
type +[X <: Int, Y <: Int] <: Int
32+
object boolean {
33+
type ![X <: Boolean] <: Boolean
34+
@infix type ^[X <: Boolean, Y <: Boolean] <: Boolean
35+
@infix type &&[X <: Boolean, Y <: Boolean] <: Boolean
36+
@infix type ||[X <: Boolean, Y <: Boolean] <: Boolean
4337
}
4438
}

tests/neg/singleton-ops-boolean.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import scala.compiletime.ops.boolean._
2+
3+
object Test {
4+
val t0: ![true] = false
5+
val t1: ![false] = true
6+
val t2: ![true] = true // error
7+
val t3: ![false] = false // error
8+
9+
val t4: true && true = true
10+
val t5: true && false = false
11+
val t6: false && true = true // error
12+
val t7: false && false = true // error
13+
14+
val t8: true ^ true = false
15+
val t9: false ^ true = true
16+
val t10: false ^ false = true // error
17+
val t11: true ^ false = false // error
18+
19+
val t12: true || true = true
20+
val t13: true || false = true
21+
val t14 false || true = false // error
22+
val t15: false || false = true // error
23+
}

tests/neg/singleton-ops-int.scala

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import scala.compiletime.ops.int._
2+
3+
object Test {
4+
summon[2 + 3 =:= 6 - 1]
5+
summon[1763 =:= 41 * 43]
6+
summon[2 + 2 =:= 3] // error
7+
summon[29 * 31 =:= 900] // error
8+
summon[Int <:< Int + 1] // error
9+
summon[1 + Int <:< Int]
10+
11+
val t0: 2 + 3 = 5
12+
val t1: 2 + 2 = 5 // error
13+
val t2: -1 + 1 = 0
14+
val t3: -5 + -5 = -11 // error
15+
16+
val t4: 10 * 20 = 200
17+
val t5: 30 * 10 = 400 // error
18+
val t6: -10 * 2 = -20
19+
val t7: -2 * -2 = 4
20+
21+
val t8: 10 / 2 = 5
22+
val t9: 11 / -2 = -5 // Integer division
23+
val t10: 2 / 4 = 2 // error
24+
val t11: -1 / 0 = 1 // error
25+
26+
val t12: 10 % 3 = 1
27+
val t13: 12 % 2 = 1 // error
28+
val t14: 1 % -3 = 1
29+
val t15: -3 % 0 = 0 // error
30+
31+
val t16: 1 < 0 = false
32+
val t17: 0 < 1 = true
33+
val t18: 10 < 5 = true // error
34+
val t19: 5 < 10 = false // error
35+
36+
val t20: 1 <= 0 = false
37+
val t21: 1 <= 1 = true
38+
val t22: 10 <= 5 = true // error
39+
val t23: 5 <= 10 = false // error
40+
41+
val t24: 1 > 0 = true
42+
val t25: 0 > 1 = false
43+
val t26: 10 > 5 = false // error
44+
val t27: 5 > 10 = true // error
45+
46+
val t28: 1 >= 1 = true
47+
val t29: 0 >= 1 = false
48+
val t30: 10 >= 5 = false // error
49+
val t31: 5 >= 10 = true // error
50+
51+
val t32: Abs[0] = 0
52+
val t33: Abs[-1] = 1
53+
val t34: Abs[-1] = -1 // error
54+
val t35: Abs[1] = -1 // error
55+
56+
val t36: Negate[-10] = 10
57+
val t37: Negate[10] = -10
58+
val t38: Negate[1] = 1 // error
59+
val t39: Negate[-1] = -1 // error
60+
61+
val t40: Max[-1, 10] = 10
62+
val t41: Max[4, 2] = 4
63+
val t42: Max[2, 2] = 1 // error
64+
val t43: Max[-1, -1] = 0 // error
65+
66+
val t44: Min[-1, 10] = -1
67+
val t45: Min[4, 2] = 2
68+
val t46: Min[2, 2] = 1 // error
69+
val t47: Min[-1, -1] = 0 // error
70+
71+
val t48: ToString[213] = "213"
72+
val t49: ToString[-1] = "-1"
73+
val t50: ToString[0] = "-0" // error
74+
val t51: ToString[200] = "100" // error
75+
}

tests/neg/singleton-ops-match-type-scrutinee.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import scala.compiletime.ops._
1+
import scala.compiletime.ops.int._
22

33
object Test {
44
type Max2[A <: Int, B <: Int] <: Int = (A < B) match {

tests/neg/singleton-ops-recursive-match-type.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import scala.compiletime.ops._
1+
import scala.compiletime.ops.int._
22

33
object Test {
44
type GCD[A <: Int, B <: Int] <: Int = B match {

tests/neg/singleton-ops-string.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.compiletime.ops.string._
2+
3+
object Test {
4+
val t0: "Hello " + "world" = "Hello world"
5+
val t1: "" + "" = ""
6+
val t2: "3" + "" = "33" // error
7+
val t3: "Hello " + "world" = "error" // error
8+
}

tests/neg/singleton-ops-type-alias.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import scala.compiletime.ops._
1+
import scala.compiletime.ops.boolean._
22

33
object Test {
44
type Xor[A <: Boolean, B <: Boolean] = (A && ![B]) || (![A] && B)

0 commit comments

Comments
 (0)