Scala 3 inline compilation error

Hi everyone,
I am trying to do a simple inline logic which should generate compilation time error if invalid. Here is the code. I don’t understand why this shows error. Isn’t the value a constant at the compilation time ?
If I change the versionNo to Int and use < 0 as the check, then it works fine.

Could someone tell me why this is not possible

import scala.compiletime.*

object InlineCompilerError {
  inline def checkVersion(inline versionNo: String) = {
    if (versionNo.isEmpty()) {
      error("Wrong version numbering! ")
    } else {
      println(s"Correct version information")
    }
  }

  @main
  def compilerErrorMain =
    checkVersion("1.2")
}

The error is coming at the main method even with a valid string.

I believe you also need an inline if

Thanks for you reply.
With inline if, it gives error that the if statement can’t be inlined.
* Cannot reduce inline if because its condition is not a constant value: "1.2".isEmpty()

Here is the scastie link for reference:

There you have the answer, str.isEmpty couldn’t be evaluated at compile time. No idea why.

Yeah, but isn’t the value a compile time constant? I am confused because of that.

The value is indeed a constant, the problem is the isEmpty
But, I don’t understand why the compiler can’t evaluate that.

My idea was to use a regex to validate this version number. Since that didn’t work, I shortened code to check if a simple isEmpty could work.
I get the same error even if I use the condition as “1.2”.isEmpty() in if condition. I was not sure what is going on there,
If i change the condition to

inline if (versionNo != "1.2")

then it works, strange that the isEmpty and any other string operations doesnt work

Hmm. String.isEmpty() is a JVM function, right? So it’s definitely not marked inline itself, and it wouldn’t astonish me if between those two facts, it becomes ineligible for inline use.

(Just guessing, mind – I haven’t even begun to play with modern inline – but it wouldn’t surprise me.)

I mean, that was my rationale as well; also from someone who hasn’t played a lot with inline
However, I was under the impression that the compiler could simply evaluate that logic.

I am lost at this moment. Is it only possible to use numbers?
Any other ideas to try to work around this? I mean, I want to validate a version string at compile time and fail compilation if wrong format is used.

You can match an exact string literal at compile-time. Here’s a little REPL session illustrating this:

scala> transparent inline def check(s: String) = inline s match
     |   case "1.0" => ()
     |   case _ => compiletime.error("Only version 1.0 is allowwed")
     | 
def check(s: String): Unit
                                                                                
scala> check("")
-- Error: ----------------------------------------------------------------------
1 |check("")
  |^^^^^^^^^
  |Only version 1.0 is allowwed
1 error found
                                                                                
scala> check("1.0")
                                                                                
scala> val version = "1.0"
val version: String = 1.0
                                                                                
scala> check(version)
-- Error: ----------------------------------------------------------------------
1 |check(version)
  |^^^^^^^^^^^^^^
  |Only version 1.0 is allowwed
1 error found
                                                                                
scala> inline val version = "1.0"
                                                                                
scala> check(version)

Yes, that is possible. But that would not help me.
I wanted to have a particular format for the version numbers (e.g.: “v0.2”, “v1.2” etc).

No in-depth experience with inline defs here, either, but according to the docs, the condition of an inline if expression must be a constant expression - i.e. a constant expression according to the lang spec (basically just literals) or some platform specific extension “such as constant folding of pure numeric computations”, making for a pretty limited and fuzzy space of supported expressions (for now?).

Thanks for the reply.
So, I guess that I can’t use this in my scenario then. Sad!
I thought that it was a nice usecase for applying this compiletime.error logic :frowning:

They have a compiletime op for that.

import scala.compiletime.*, ops.string.*
import InlineCompilerError.*

object InlineCompilerError {
  inline def checkVersion(inline versionNo: String) =
    if constValue[Matches[versionNo.type, "\\d\\.\\d"]] then
      println(s"Correct version information")
    else
      error("Wrong version numbering! ")
}
//@main def compilerErrorMain = checkVersion("1.2")
@main def compilerErrorMain = checkVersion("1.x")
7 Likes

@som-snytt This is amazing! Thank you very much for sharing this, this helps me a lot :smile:

I don’t understand. This is still not an inline if, yet the error is no longer emitted. Is this if else automatically constant folded or something? Which begs the question: what then is the point of inline if?

@Jasper-M
inline if just guarantees that the expression is constant at compile time. If not it throws an appropriate error message. You can think in the lines of @tailrec annotation.
The above code compile because we are using the Matches from scala.compiletime.ops package. The operators in that package will be compile-time constants (and if you look at that package, you can notice that there are not many operations available yet).

I don’t think I knew it would expand the link inline (to pun on inlining):

-Vprint:typer,inlining,inlineVals shows that an ordinary if/else with a final val condition is pruned at inlineVals.

In Scala 2, that is in refchecks (!):

For line 37, see the magic number.

the error is no longer emitted

It wasn’t clear to me which error was meant, but I hope the previous answer was clarifying.