Indirect access to `inline val` defined in `private object` causes `getter (...) was not inlined` error
Opened this issue · 8 comments
Compiler version
3.3.3
3.4.1
Minimized code
// object Konst:
private object Konst:
inline val K = 1
// inline def K = 1
object Use:
// OK
def a: Int = Konst.K
// OK
inline def b: Int = Konst.K
// Error
def c: Int = b
https://scastie.scala-lang.org/3bXUpYzlQ1eMnQOrLAOLuA
Output
[error] -- Error: /home/me/Bug.scala:17:15
[error] 17 | def c: Int = b
[error] | ^
[error] | getter K is declared as `inline`, but was not inlined
[error] |
[error] | Try increasing `-Xmax-inlines` above 32
[error] |----------------------------------------------------------------------------
[error] |Inline stack trace
[error] |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[error] |This location contains code that was inlined from Bug.scala:14
[error] 14 | inline def b: Int = Konst.K
[error] | ^^^^^^^
[error] ----------------------------------------------------------------------------
[error] one error found
We can mask the error by either:
- removing
inline
s - changing
object Konst
fromprivate
to public - changing
val K
todef K
Originally, Konst
and Use
were defined in separate files.
Expectation
Compile without error & apply inlining
This issue was picked for the Scala Issue Spree of tomorrow, June 11th. @hamzaremmal, @SethTisue and @rochala will be working on it. If you have any further insights into the issue or guidance on how to fix it, please leave it here.
-Vprint:all
shows
[[syntax trees at end of MegaPhase{pruneErasedDefs, uninitialized, inlinePatterns, vcInlineMethods, seqLiterals, intercepted, getters, specializeFunctions, specializeTuples, collectNullableFields, elimOuterSelect, resolveSuper, functionXXLForwarders, paramForwarding, genericTuples, letOverApply, arrayConstructors}]]
...
@SourceFile("S.scala") final module class Use() extends Object() {
...
private inline def b: Int = scala.compiletime.erasedValue[Int]
def c: Int = Use.inline$Konst.K:Int
def inline$Konst: Konst = Konst
}
}
I don't have much experience with inlining, but (UPDATE: yes the forwarder is interfering, but because it's not stable; see below)def inline$Konst: Konst = Konst
seems odd to me. it seems like the idea is to evade the private
-ness of Konst
with a forwarder, but it isn't accomplishing that because the type is Konst
anyway?
I guess let's enable tracing in inlining to get it talking about what it's doing? it might be useful to view the traces for the versions that work, as well as the trace for the version that goes wrong (UPDATE: this doesn't tell us anything additional that doesn't already show up in -Vprint:all
, we can already see what is being inlined or not)
Not 100% confident this is accurate, but the following picture seems to be emerging (in discussion with Hamza and Jędrzej):
Both inlining itself and a separate constant-folding mechanism are in play here. The separate mechanism is the constant-folding that happens after inlining, in FirstTransform
, which calls TreeInfo#constToLiteral
. But def c: Int = Use.inline$Konst.K:Int
cannot be constant-folded because inline$Konst
isn't stable.
But we don't yet have an idea for a fix...
Recall that the semantics of inline val
are very restrictive; inline vals must have constant literal types and be pure.
Fix idea: maybe instead of waiting for FirstTransform
's constant-folding to go wrong later, after inlining, we can make the inliner actually inline K
? (I think this dawned on all three of us at about the same moment...)
When you look at Inliner
you see lots of code that seems intended to handle val
and def
the same (lots of ValOrDefDef
), so we're puzzled why this plays out differently with def
.
Jędrzej noticed that Typer
has:
if sym.isInlineMethod then rhsCtx.addMode(Mode.InlineableBody)
perhaps that's in play here? But we don't see where InlineableBody
actually has any effect downstream?
(Is there any other such pre-analysis happening in Typer
that's relevant?)
I think the issue is not caused by the bridges added due to the private
modifier, but about selecting an inline val on the result of a non-inline method.
Minimisation:
class Foo:
inline val F = 1
def foo(): Foo = ???
val _ = foo().F
Indeed, the issue looks very similar to what we have been seeing during the spree. I will have to investigate it further.