Skip to content

Conversation

@mbovel
Copy link
Member

@mbovel mbovel commented Nov 13, 2025

When should the result of calling a non-transparent inline method be visible?

Right now, it becomes visible when we reduce the condition of an inline if:

inline def g1(): Boolean =
  true

inline def g2(): Long =
  inline if g1() then 1L else 2L

@main def Test: Unit =
  assert(g2() == 1L)

It is also visible for operations that can be constant-folded:

inline def f1(): Long =
  1L

inline def f2(): Long =
  inline val x = f1() + 1L
  x

@main def Test: Unit =
  assert(f2() == 2L)

But it is not visible for a simple call:

inline def f1(): Long =
  1L

inline def f3(): Long =
  inline val x = f1()
  x

@main def Test: Unit =
  assert(f3() == 1L)
$ scala -S 3.7.4 test3.scala
Compiling project (Scala 3.7.4, JVM (17))
[error] ./test3.scala:9:10
[error] inline value must have a literal constant type
[error]   assert(f3() == 1L)
[error]          ^^^^
Compilation failed

The first two examples work because they rely on ConstantValue, which always strips Typed:

object ConstantValue {
def unapply(tree: Tree)(using Context): Option[Any] =
tree match
case Typed(expr, _) => unapply(expr)

This PR makes the third example work as well by also calling ConstantValue when typing the right-hand side of an inline val inside an inline def.

val rhs =
typed(vdef.rhs) match
case ConstantValue(v) => Literal(Constant(v))
case rhs => rhs
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we directly report an error here?

@sjrd
Copy link
Member

sjrd commented Nov 14, 2025

It is also visible for operations that can be constant-folded:

This is wrong. It breaks separate compilation/tasty compatibility if you later change the body of f1(). We must prevent that.

inline if is fine because it's reduced by the inliner, not by typer/constant folding.

@mbovel
Copy link
Member Author

mbovel commented Nov 14, 2025

This is wrong. It breaks separate compilation/tasty compatibility if you later change the body of f1().

But that only works here because we are inside an inline def and f() has already been inlined, doesn't it?

Inside an inline def, I would expect the right hand-side of an inline val to be constant-folded similarly to the condition of an inline if, shouldn't it be the case?

@sjrd
Copy link
Member

sjrd commented Nov 14, 2025

No, inline val is weird. It doesn't behave like the other inlines. It needs to be a constant expression (spec concept), which is required already during the initial typing.

@mbovel mbovel linked an issue Nov 14, 2025 that may be closed by this pull request
@som-snytt
Copy link
Contributor

Grateful I'm not trying to get something past sjrd right now.

@mbovel
Copy link
Member Author

mbovel commented Nov 14, 2025

No, inline val is weird. It doesn't behave like the other inlines. It needs to be a constant expression (spec concept), which is required already during the initial typing.

Why is it important that this is at typer already?

That doesn't seem aligned with the implementation, which only checks that during inlineVals.

And is this implied by the specification? Do spec definitions imply during typer?

@mbovel
Copy link
Member Author

mbovel commented Nov 14, 2025

Follow-up on the constant-folding part, independently from inlining: #24432.

@mbovel
Copy link
Member Author

mbovel commented Nov 14, 2025

@mbovel
Copy link
Member Author

mbovel commented Nov 14, 2025

We discussed with @sjrd in person. Accepting inline vals that are inside inline defs and that are constant only after inlining might be okay 🥳

Closing this PR in favor of #24425, which should be more general (but requires some more work).

@mbovel mbovel closed this Nov 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

inline val not inlined

3 participants