diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 794119cd7a79..bd35b606bb8e 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1122,7 +1122,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.Plus, tpnme.Length, tpnme.Substring, tpnme.Matches, tpnme.CharAt ) private val compiletimePackageOpTypes: Set[Name] = Set(tpnme.S) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index f8c70176482c..45f521544c14 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -235,6 +235,7 @@ object StdNames { final val Plus: N = "+" final val S: N = "S" final val Substring: N = "Substring" + final val CharAt: N = "CharAt" final val Times: N = "*" final val ToInt: N = "ToInt" final val ToLong: N = "ToLong" diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0f7e87213d08..d4ab09d14317 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4423,6 +4423,8 @@ object Types { case tpnme.Matches => constantFold2(stringValue, _ matches _) case tpnme.Substring => constantFold3(stringValue, intValue, intValue, (s, b, e) => s.substring(b, e)) + case tpnme.CharAt => + constantFold2AB(stringValue, intValue, _ charAt _) case _ => None } else if (owner == defn.CompiletimeOpsBooleanModuleClass) 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 6214f983db32..9efea026bcc0 100644 --- a/library/src/scala/compiletime/ops/string.scala +++ b/library/src/scala/compiletime/ops/string.scala @@ -43,3 +43,14 @@ object string: */ @experimental type Matches[S <: String, Regex <: String] <: Boolean + + /** Returns the Char type at the specified index. + * An index ranges from 0 to Length[S] - 1. The first Char of + * the sequence is at index 0, the next at index 1, and so on. + * ```scala + * val c: CharAt["hello", 0] = 'h' + * ``` + * @syntax markdown + */ + @experimental + type CharAt[S <: String, Idx <: Int] <: Char diff --git a/tests/neg/singleton-ops-string.scala b/tests/neg/singleton-ops-string.scala index d9cf2377564b..c24a9c0e64d1 100644 --- a/tests/neg/singleton-ops-string.scala +++ b/tests/neg/singleton-ops-string.scala @@ -15,4 +15,23 @@ object Test { val t9: Matches["hamburger", "ham.*"] = true val t10: Matches["hamburger", "ham.*"] = false // error + + val t11: CharAt["String", 0] = 'S' + val t12: CharAt["String", 1] = 't' + val t13: CharAt["String", 2] = '!' // error + // ^^^ + // Found: ('!' : Char) + // Required: ('r' : Char) + val t14: CharAt["String", 3] = '!' // error + // ^^^ + // Found: ('!' : Char) + // Required: ('i' : Char) + val t15: CharAt["String", 4] = 'n' + val t16: CharAt["String", 5] = 'g' + val t17: CharAt["String", 6] = '!' // error + // ^ + // String index out of range: 6 + val t18: CharAt["String", -1] = '?' // error + // ^ + // String index out of range: -1 }