diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 8d305eef16e1..5684dd5a082b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1430,7 +1430,7 @@ class Definitions { ) private val compiletimePackageBooleanTypes: Set[Name] = Set(tpnme.Not, tpnme.Xor, tpnme.And, tpnme.Or) private val compiletimePackageStringTypes: Set[Name] = Set( - tpnme.Plus, tpnme.Length, tpnme.Substring, tpnme.Matches, tpnme.CharAt + tpnme.Plus, tpnme.Length, tpnme.Substring, tpnme.Matches, tpnme.CharAt, tpnme.LT, tpnme.GT, tpnme.LE, tpnme.GE ) private val compiletimePackageOpTypes: Set[Name] = Set(tpnme.S, tpnme.From) diff --git a/compiler/src/dotty/tools/dotc/core/TypeEval.scala b/compiler/src/dotty/tools/dotc/core/TypeEval.scala index 98578f353b96..30f00bf2475a 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeEval.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeEval.scala @@ -261,6 +261,10 @@ object TypeEval: constantFold3(stringValue, intValue, intValue, (s, b, e) => s.substring(b, e)) case tpnme.CharAt => constantFold2AB(stringValue, intValue, _.charAt(_)) + case tpnme.LT => constantFold2(stringValue, _ < _) + case tpnme.GT => constantFold2(stringValue, _ > _) + case tpnme.LE => constantFold2(stringValue, _ <= _) + case tpnme.GE => constantFold2(stringValue, _ >= _) case _ => None else if owner == defn.CompiletimeOpsBooleanModuleClass then name match case tpnme.Not => constantFold1(boolValue, x => !x) diff --git a/library/src/scala/compiletime/ops/string.scala b/library/src/scala/compiletime/ops/string.scala index 9ef5a3fb7b79..daf08138b10b 100644 --- a/library/src/scala/compiletime/ops/string.scala +++ b/library/src/scala/compiletime/ops/string.scala @@ -15,6 +15,50 @@ object string: */ infix type +[X <: String, Y <: String] <: String + /** Lexicographic comparison of two `String` singleton types. + * ```scala + * //{ + * import compiletime.ops.string.* + * //} + * val hello: "hello " < "world" = true + * ``` + * @syntax markdown + */ + infix type <[S1 <: String, S2 <: String] <: Boolean + + /** Lexicographic comparison of two `String` singleton types. + * ```scala + * //{ + * import compiletime.ops.string.* + * //} + * val hello: "hello " > "world" = false + * ``` + * @syntax markdown + */ + infix type >[S1 <: String, S2 <: String] <: Boolean + + /** Lexicographic comparison of two `String` singleton types. + * ```scala + * //{ + * import compiletime.ops.string.* + * //} + * val hello: "hello " >= "world" = false + * ``` + * @syntax markdown + */ + infix type >=[S1 <: String, S2 <: String] <: Boolean + + /** Lexicographic comparison of two `String` singleton types. + * ```scala + * //{ + * import compiletime.ops.string.* + * //} + * val hello: "hello " <= "world" = true + * ``` + * @syntax markdown + */ + infix type <=[S1 <: String, S2 <: String] <: Boolean + /** Length of a `String` singleton type. * ```scala * //{ diff --git a/tests/neg/singleton-ops-string.scala b/tests/neg/singleton-ops-string.scala index c24a9c0e64d1..9b05216ee172 100644 --- a/tests/neg/singleton-ops-string.scala +++ b/tests/neg/singleton-ops-string.scala @@ -34,4 +34,66 @@ object Test { val t18: CharAt["String", -1] = '?' // error // ^ // String index out of range: -1 + + val t19: "hello" < "world" = true + val t20: "hello" < "world" = false // error + + val t21: "hello" > "world" = false + val t22: "hello" > "world" = true // error + + val t23: "hello" <= "world" = true + val t24: "hello" <= "world" = false // error + val t25: "hello" <= "hello" = true + val t26: "hello" <= "hello" = false // error + + val t27: "hello" >= "world" = false + val t28: "hello" >= "world" = true // error + val t29: "hello" >= "hello" = true + val t30: "hello" >= "hello" = false // error + + import scala.compiletime.ops.int./ + + type If[C <: Boolean, Then, Else] = C match + case true => Then + case false => Else + + type ZipTuples[First, Second] = (First, Second) match + case (first *: EmptyTuple, second *: EmptyTuple) => (first, second) *: EmptyTuple + case (first *: firstRest, second *: secondRest) => (first, second) *: ZipTuples[firstRest, secondRest] + + type TuplePairs[InputTuple] = InputTuple match + case NamedTuple.NamedTuple[names, values] => ZipTuples[names, values] + + type PrependTuplePairs[First, Second, Pairs] = Pairs match + case EmptyTuple => (First *: EmptyTuple) *: (Second *: EmptyTuple) *: EmptyTuple + case left *: right *: EmptyTuple => (First *: left) *: (Second *: right) *: EmptyTuple + + type UnzipTuples[Input] = Input match + case (first *: second *: EmptyTuple) *: rest => PrependTuplePairs[first, second, UnzipTuples[rest]] + case EmptyTuple => EmptyTuple + + type NamedTupleFromPairs[Input] = UnzipTuples[Input] match + case EmptyTuple => NamedTuple.NamedTuple[EmptyTuple, EmptyTuple] + case left *: right *: EmptyTuple => NamedTuple.NamedTuple[left, right] + + type Merge[P1 <: Tuple, P2 <: Tuple] <: Tuple = (P1, P2) match + case (EmptyTuple, h *: t) => h *: t + case (h *: t, EmptyTuple) => h *: t + case ((k1, v1) *: t1, (k2, v2) *: t2) => If[k1 <= k2, + (k1, v1) *: Merge[t1, (k2, v2) *: t2], + (k2, v2) *: Merge[(k1, v1) *: t1, t2] + ] + + type SortPairs[P <: Tuple] <: Tuple = P match + case EmptyTuple => EmptyTuple + case h *: EmptyTuple => h *: EmptyTuple + case _ => Merge[ + SortPairs[Tuple.Take[P, Tuple.Size[P] / 2]], + SortPairs[Tuple.Drop[P, Tuple.Size[P] / 2]] + ] + + type SortNamedTuple[NT <: NamedTuple.AnyNamedTuple] = NamedTupleFromPairs[SortPairs[TuplePairs[NT]]] + + val t31: SortNamedTuple[(foo: 45, bar: "x", baz: 6.0, quux: false)] = (bar = "x", baz = 6.0, foo = 45, quux = false) + val t32: SortNamedTuple[(foo: 45, bar: "x", baz: 6.0, quux: false)] = (foo = 45, bar = "x", baz = 6.0, quux = false) // error }