Skip to content

[Scala 3] @inline def → inline def#871

Open
halotukozak wants to merge 5 commits into
AVSystem:scala-3from
halotukozak:03-04-at-inline-to-inline
Open

[Scala 3] @inline def → inline def#871
halotukozak wants to merge 5 commits into
AVSystem:scala-3from
halotukozak:03-04-at-inline-to-inline

Conversation

@halotukozak

@halotukozak halotukozak commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Rewrites Scala 2 @inline def (JVM optimizer hint) to Scala 3 inline def (mandatory compile-time AST splice) across the Opt family AND function-taking ops elsewhere. Closure-eliminating perf win: inline def + inline (f: A => B) parameter usage drops Function1 allocations at call sites.

Scope

Opt family (initial sweep, fork 5fafdbd7)

  • Opt, NOpt, OptArg, OptRef — all @inline definline def, with selected by-name params reshaped to inline x: T for one-use straight-line cases (getOrElse, orElse, fold(ifEmpty), mapOr(ifEmpty), toRight, toLeft, forEmpty). Trivial accessors (isEmpty/get/toOption/etc.) kept as plain definline def would re-trigger -Xmax-inlines bailout on pattern-match desugaring sites.
  • @publicInBinary added on private constructors + private[misc] members referenced from inline bodies (Scala 3 ABI requirement).

Broader Function-param sweep (fork a4ddad6b, 580625a9, 33a5b792, ad505679, ee0be95e)

  • SharedExtensions.scala: 49 inline def + 42 inline params (matches fork shape 49/43)
  • concurrent/TaskExtensions.scala, concurrent/ObservableExtensions.scala: 11 inline def + 11 inline params
  • core/jvm/jiop/{JFunctionUtils, GuavaInterop, Java8CollectionUtils}: closure-taking adapters inlined
  • core/jvm/jiop/{ScalaJStream, ScalaJIntStream, ScalaJLongStream, ScalaJDoubleStream}: closure ops inlined

Inline-scope rules applied

  1. inline def only when method has at least one Function-typed (A => B) OR by-name (=> T) param. Pure value-param methods stay plain def.
  2. inline keyword stripped from by-name params (inline x: => Tx: => T) — redundant in Scala 3 (=> T already enables call-site substitution).
  3. Future-override adapters in GuavaInterop kept as plain def (Scala 3 forbids overriding non-inline base method with inline).

Whitelist (preserved verbatim — fork keeps these as @inline)

  • CborInput.scala:56 (1 site — tight-loop parser hot path)
  • JsonStringInput.scala (5 sites)
  • RPCFramework.scala (2 sites — RPC dropped from scala-3 in fork)

OptArg.argToOptArg preservation honored per slice 3.3 rationale.

Acceptance

  • git grep '@inline' core/src/main/scala/ outside whitelist → 0 hits ✓
  • Whitelist counts intact (CborInput=1, JsonStringInput=5, RPCFramework=2) ✓
  • Fork-shape parity: SharedExtensions 49/43-ish, Opt-family inline-def counts match fork ✓
  • sbt compile + Test/compile + scalafmtCheckAll green ✓
  • No new @nowarn / -Wconf suppressions ✓ (one verbatim fork carve-out: @nowarn on inline def discard in SharedExtensions)

Source-compat impact

Scala 3 inline def is implicitly final → cannot be overridden. Downstream subclassers of Opt / NOpt / OptArg / OptRef would break, but targets are value classes (final by construction) — zero impact in practice.

SharedExtensions extension blocks have no inheritance — safe.

Translated from fork origin/master commits 5fafdbd7 (Opt family), a4ddad6b (SharedExtensions), 580625a9 (JFunctionUtils), 33a5b792/ad505679/ee0be95e (ScalaJ*Stream).

Slice: 3.4 of Phase 3 (Scala 3 syntax modernization)
Merge order: 3.1 → 3.2 → 3.3 → 3.4
Depends on: #870 (slice 3.3 — must merge first)
Base branch: upstream/scala-3 (not stacked on prior slice)

@halotukozak halotukozak added this to the Scala 3 milestone Jun 1, 2026
halotukozak and others added 3 commits June 1, 2026 21:50
…e Function/by-name params

Sweep Scala 2 @inline def JVM-optimizer hints to Scala 3 mandatory
inline def AST splice across the Opt family (Opt, NOpt, OptArg,
OptRef), and mark Function-typed (A => B, (A, B) => C, PartialFunction)
parameters as inline to eliminate Function* allocation at call sites.

Final shape:
- Methods with at least one Function-typed OR by-name parameter:
  `inline def` with inline-marked Function params.
- By-name params (=> T) themselves remain unmarked — `=> T` already
  provides call-site substitution in Scala 3; `inline (x: => T)` is
  redundant. Where the by-name was used exactly once in a straight-line
  if/then/else body, it is reshaped to `(inline x: B)` so the
  expression is substituted literally (no Function0 thunk allocation).
- Trivial accessors (isEmpty/isDefined/nonEmpty/get) kept as plain def
  to avoid -Xmax-inlines bailout in pattern-match desugaring
  (`case Opt(x) =>`).
- @publicInBinary added on private constructors and referenced private
  members per Scala 3 inline-body access requirement.
- OptArg.argToOptArg preserved as implicit def per slice 3.3
  erasure-bridge rationale (untouched).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Convert jXxx / gXxx adapters that wrap Scala lambdas into Java
functional interfaces to inline def + inline Function param across:

- JFunctionUtils (38/38 inline def + 38/38 inline params; 5 zero-arg
  Suppliers reverted via by-name → inline-value where applicable)
- Java8CollectionUtils (9/8)
- GuavaInterop (3/3 — see Future-override exception below)
- ScalaJStream / ScalaJIntStream / ScalaJLongStream / ScalaJDoubleStream
  (23/23 + 16/16 ×3)

Eliminates Function* allocation at every jiop call site by splicing the
SAM-conversion at the use site rather than allocating a wrapper.

Future-override exception:
GuavaInterop.ListenableFutureAsScala.{onComplete, transform,
transformWith} are kept as plain def (no inline keyword on def OR
params). They override scala.concurrent.Future methods whose parent
signatures have non-inline params; Scala 3 forbids overriding a
non-inline param with an inline param.

Style:
- Drop trailing `; ()` coercion from Consumer SAM lambdas in
  JFunctionUtils — Scala 3 SAM conversion of an Any-returning lambda
  to a void-returning Java functional interface accepts the result
  directly.
- scalafmt rewraps applied where post-inline insertion exceeded line
  width.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Document slice 3.4 outcomes in MIGRATION.md §3:
- @inline def → inline def conversion (Opt family + jiop adapters)
- Function-typed/by-name parameter inlining
- inline def is implicitly final (value-class scope mitigates)
- Future-override exception in GuavaInterop (non-inline override
  constraint)
- @publicInBinary applied on private members referenced from inline
  bodies

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@halotukozak halotukozak force-pushed the 03-04-at-inline-to-inline branch from 11ae468 to deb43bf Compare June 1, 2026 19:51
… XOps wrappers

Marks 49 inline def + 42 inline params across SharedExtensions XOps
wrappers, matching fork shape (49/43). Applies on the pre-PR#868 form
(class XOps extends AnyVal). The inline keywords will carry into the
post-rebase extension blocks since method signatures are preserved by
slice 3.1.

Methods touched per Ops class:
- UniversalOps: |>, applyIf, discard, thenReturn, setup, matchOpt, uncheckedMatch
- LazyUniversalOps: evalFuture, evalTry, optIf, optionIf, recoverFrom, recoverToOpt
- IntOps: times
- FutureOps: onCompleteNow, andThenNow, foreachNow, transformNow (2), transformWithNow, mapNow, flatMapNow, filterNow, collectNow, recoverNow, recoverWithNow, zipWithNow (13 total; wrapToTry/toUnit/toVoid/thenReturn/ignoreFailures kept plain per fork)
- OptionOps: forEmpty, mapOr
- TryOps: tapFailure
- PartialFunctionOps: unless, fold
- IterableOnceOps: toMapBy, mkMap, groupToMap, findOpt, flatCollect, collectFirstOpt, reduceOpt, reduceLeftOpt, reduceRightOpt, maxOptBy, minOptBy, indexWhereOpt, asyncFoldLeft, asyncFoldRight, asyncForeach, partitionEither
- FutureCompanionOps: eval

NoValueMarker / NoValueMarkerFunc unprivatized (referenced from inline
def fold body, matches fork). private def it dropped from IterableOnceOps
in favor of inline coll.iterator (avoids @publicInBinary on private fwd).
… ObservableExtensions

Marks inline def + inline Function/by-name params on AnyVal Ops wrappers
in concurrent extensions:

- TaskOps: lazyTimeout, tapL, tapErrorL
- TaskCompanionOps: traverseOpt, traverseMap, traverseMapValues, usingNow
- ObservableOps: findOptL, distinctBy, sortedByL, mkMapL

No fork counterpart (these files don't exist on origin/master; project-
local extensions). Inline marks applied per project-wide policy for
Function-taking and by-name-taking forwarders.
@halotukozak halotukozak marked this pull request as ready for review June 1, 2026 21:20
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.

1 participant