What does the error "inline value must be pure" mean?

import scala.compiletime.constValue
import scala.compiletime.ops.int.`*`

transparent inline def s1[n <: Int]: Int = constValue[n] * constValue[n]

transparent inline def s2[n <: Int]: Int = constValue[n * n]

transparent inline def s3[n <: Int]: n * n = constValue[n * n]

inline def s4[n <: Int]: n * n = constValue[n * n]

inline val x1: 1 = s1[1]
inline val x2: 1 = s2[1]
inline val x3: 1 = s3[1]
inline val x4: 1 = s4[1]  // inline value must be pure

The x1, x2 and x3 is OK, but the x4 with error message “inline value must be pure“。

https://scastie.scala-lang.org/fcTGvsBCTdqIDPzbofnPJg

well s4 is not transparent, so therefore it cant ‘see through’ to its result beign a constant

If we have inline def s4[n <: Int]: n * n = ??? and we take n to be the literal type 1, the return type becomes 1 * 1, which is the literal type 1.

So the original code should typecheck, right?

The Scala compiler’s error message inline value must be pure is confusing when applied to a non-transparent inline method. Should the compiler provide a more specific hint, such as, Perhaps you need to add the transparent modifier?

1 Like

The error is not due to type mismatch - rather the “pureness” criteria is not a formal part of the type system, but like an ad-hoc rule specific for inline values. Ignoring the type, the RHS expression must only be a literal value, or a “pure expression” that results in such a literal value.

this would be a helpful addition - so upon this error, try to capture more information about why the purity check fails so it can be reported

Thank you very much for your helpful explanation!

So IIUC the inline-value-specific rule you mention does not take the type of the RHS expression into account to determine if it represents a literal value.

Because you describe the rule as kind of “ad-hoc”, I wonder if this is just a limitation of how the rule is currently implemented, or is there a more fundamental reason, for example that the precise type information is not available yet when the inline val is processed?

I think we could move the purity test past the inlining phase. That would make the example compile.

3 Likes

It would make the example compile, but it would also break the semantic abstraction that non-transparent inline provides. In particular, it breaks the invariant that “if it typechecks with inline, then it would also typecheck without it and will behave in the same way”.

If you rely on the specific shape of the body, you should add transparent. That modifier is meant to break that abstraction barrier to expose the inside of the method, besides its interface contract.

Well, it’s not exactly type-checking that is at play here. The example type-checks with the same (constant-)type for both regular and transparent inline methods. The failure comes from an additional test that checks that the right-hand side of an inline value must be an expression that is known to be side-effect free. That’s a very crude check that just declares some kinds of trees to be OK and otherwise errs on the side of caution. It might be reasonable to generalize that check so that it sees through inline expansions.

1 Like

Type checking or checking doesn’t really matter. I mean “is your program valid?” overall. From the restrictions/guarantees of inline def of today, as a library author, you get the freedom to change the body of your inline def as long as it abides by its contract, without ever breaking user code. “Constraints liberate.” If we relax the check, we restrict the freedoms of the library author. It’s not a win-win change.

3 Likes

That’s a fair point. We can still improve the error message, but the base behavior should probably stay as it is.

3 Likes