I am trying to validate whether the given String is a valid version, in this cases major.minor.patch
The following code works at the moment, but fails with 3.3.0-RC2. Is there an obvious replacement?
case class Version(major: Int, minor: Int, patch: Int):
def render: String = s"$major.$minor.$patch"
object Version:
import scala.compiletime.*
import scala.compiletime.ops.string.*
private val VersionPattern = raw"(\d+)\.(\d+)\.(\d+)".r
inline def apply(inline versionNo: String): Version =
inline if constValue[Matches[versionNo.type, "\\d+\\.\\d+.\\d+"]] then
val VersionPattern(major, minor, patch) = versionNo: @unchecked
Version(major.toInt, minor.toInt, patch.toInt)
else error("Not a valid version of the form <major>.<minor>.<patch>")
See this scastie scastie with the code that works as expected in 3.2.2.
(The change seems intentional, that’s why I’m hoping for an obvious replacement: see Github Issue 16804 )
Hmm, I found that this works:
inline def apply[V <: String]: Version =
inline if constValue[Matches[V, "\\d+\\.\\d+.\\d+"]] then
val VersionPattern(major, minor, patch) = constValue[V]: @unchecked
Version(major.toInt, minor.toInt, patch.toInt)
else error("Not valid")
But the call-site isn’t very intuitive anymore: Version[“1.2.0”] vs Version(“1.2.0”), so it’s more a workaround than a solution
1 Like
You can add a Singleton
upper bound:
inline def apply[V <: String & Singleton](v: V): Version
2 Likes
Thanks a lot, works perfectly for me!
For completeness sake: This is the full version I have now - I’m not sure if there is a more elegant way for reusing the ErrorMsg
(scastie)
case class Version(major: Int, minor: Int, patch: Int):
def render: String = s"$major.$minor.$patch"
object Version:
import scala.compiletime.*
import scala.compiletime.ops.string.*
private val VersionPattern = raw"(\d+)\.(\d+)\.(\d+)".r
private type Error = "Not a valid version of the form <major>.<minor>.<patch>"
private val ErrorMsg = constValue[Error]
inline def apply[V <: String & Singleton](versionNo: V): Version =
inline if constValue[Matches[V, "\\d+\\.\\d+.\\d+"]] then
val Right(value) = parse(versionNo): @unchecked
value
else error(constValue[Error])
def parse(v: String): Either[String, Version] = v match
case VersionPattern(major, minor, patch) =>
Right(Version(major.toInt, minor.toInt, patch.toInt))
case invalid =>
Left(s"$ErrorMsg: $invalid")
(edit: removed unnecessary inline
from versionNo
parameter)
private transparent inline def ErrorMsg = "Not a valid version of the form <major>.<minor>.<patch>"
For some reason it needs to be a transparent def. With only inline
I get this error, which is a bit silly IMHO.
A literal string is expected as an argument to `compiletime.error`. Got "Not a valid version of the form <major>.<minor>.<patch>":String
Oh, I had only tried the variant without transparent
and then given up. Thanks a lot!
With transparent
the compiler doesn’t add the upcast : String
when he inlines the method. That upcast keeps him from recognizing the expression as a literal.
1 Like