From b71fe2f6ca108ce1273d9c08f6f29bf023c9ad7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 1 Jun 2026 21:50:39 +0200 Subject: [PATCH 1/5] =?UTF-8?q?perf(scala-3,core):=20@inline=20def=20?= =?UTF-8?q?=E2=86=92=20inline=20def=20in=20Opt=20family=20with=20inline=20?= =?UTF-8?q?Function/by-name=20params?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../com/avsystem/commons/misc/NOpt.scala | 77 +++++++++--------- .../scala/com/avsystem/commons/misc/Opt.scala | 79 ++++++++++--------- .../com/avsystem/commons/misc/OptArg.scala | 51 ++++++------ .../com/avsystem/commons/misc/OptRef.scala | 67 ++++++++-------- 4 files changed, 143 insertions(+), 131 deletions(-) diff --git a/core/src/main/scala/com/avsystem/commons/misc/NOpt.scala b/core/src/main/scala/com/avsystem/commons/misc/NOpt.scala index 6c813d9e0..1bdcdaac7 100644 --- a/core/src/main/scala/com/avsystem/commons/misc/NOpt.scala +++ b/core/src/main/scala/com/avsystem/commons/misc/NOpt.scala @@ -2,11 +2,13 @@ package com.avsystem.commons.misc import com.avsystem.commons.IIterable +import scala.annotation.publicInBinary + object NOpt { // These two are used as NOpt's raw value to represent empty NOpt and NOpt(null). // Unfortunately, null itself can't be used for that purpose because https://github.com/scala/bug/issues/7396 - private object EmptyMarker extends Serializable - private object NullMarker extends Serializable + @publicInBinary private[misc] object EmptyMarker extends Serializable + @publicInBinary private[misc] object NullMarker extends Serializable /** Creates a [[NOpt]] out of given value. Works like `Option.apply`, i.e. `null` is translated into an empty * [[NOpt]]. Note however that [[NOpt]] does have a representation of "present null" (which can be obtained using @@ -27,9 +29,9 @@ object NOpt { def empty[A]: NOpt[A] = Empty - private val emptyMarkerFunc: Any => Any = _ => EmptyMarker + @publicInBinary private[misc] val emptyMarkerFunc: Any => Any = _ => EmptyMarker - final class WithFilter[+A] private[NOpt] (self: NOpt[A], p: A => Boolean) { + final class WithFilter[+A] @publicInBinary private[NOpt] (self: NOpt[A], p: A => Boolean) { def map[B](f: A => B): NOpt[B] = self.filter(p) map f def flatMap[B](f: A => NOpt[B]): NOpt[B] = self.filter(p) flatMap f def foreach[U](f: A => U): Unit = self.filter(p).foreach(f) @@ -39,113 +41,114 @@ object NOpt { /** Like [[Opt]] but does have a counterpart for `Some(null)`. In other words, [[NOpt]] is a "nullable [[Opt]]". */ -final class NOpt[+A] private (private val rawValue: Any) extends AnyVal with OptBase[A] with Serializable { +final class NOpt[+A] @publicInBinary private[misc] (@publicInBinary private[misc] val rawValue: Any) + extends AnyVal with OptBase[A] with Serializable { import NOpt._ private def value: A = (if (rawValue.asInstanceOf[AnyRef] eq NullMarker) null else rawValue).asInstanceOf[A] - @inline def isEmpty: Boolean = rawValue.asInstanceOf[AnyRef] eq EmptyMarker - @inline def isDefined: Boolean = !isEmpty - @inline def nonEmpty: Boolean = isDefined + def isEmpty: Boolean = rawValue.asInstanceOf[AnyRef] eq EmptyMarker + def isDefined: Boolean = !isEmpty + def nonEmpty: Boolean = isDefined - @inline def get: A = + def get: A = if (isEmpty) throw new NoSuchElementException("empty NOpt") else value - @inline def boxed[B](implicit boxing: Boxing[A, B]): NOpt[B] = + def boxed[B](implicit boxing: Boxing[A, B]): NOpt[B] = map(boxing.fun) - @inline def unboxed[B](implicit unboxing: Unboxing[B, A]): NOpt[B] = + def unboxed[B](implicit unboxing: Unboxing[B, A]): NOpt[B] = map(unboxing.fun) - @inline def toOption: Option[A] = + def toOption: Option[A] = if (isEmpty) None else Some(value) /** Converts this `NOpt` into `Opt`. Because `Opt` cannot hold `null`, `NOpt(null)` is translated to `Opt.Empty`. */ - @inline def toOpt: Opt[A] = + def toOpt: Opt[A] = if (isEmpty) Opt.Empty else Opt(value) /** Converts this `NOpt` into `OptRef`, changing the element type into boxed representation if necessary (e.g. * `Boolean` into `java.lang.Boolean`). Because `OptRef` cannot hold `null`, `NOpt(null)` is translated to * `OptRef.Empty`. */ - @inline def toOptRef[B >: Null](implicit boxing: Boxing[A, B]): OptRef[B] = + def toOptRef[B >: Null](implicit boxing: Boxing[A, B]): OptRef[B] = if (isEmpty) OptRef.Empty else OptRef(boxing.fun(value)) /** Converts this `NOpt` into `OptArg`. Because `OptArg` cannot hold `null`, `NOpt(null)` is translated to * `OptArg.Empty`. */ - @inline def toOptArg: OptArg[A] = + def toOptArg: OptArg[A] = if (isEmpty) OptArg.Empty else OptArg(value) - @inline def getOrElse[B >: A](default: => B): B = + inline def getOrElse[B >: A](inline default: B): B = if (isEmpty) default else value - @inline def orNull[B >: A](implicit ev: Null <:< B): B = + def orNull[B >: A](implicit ev: Null <:< B): B = if (isEmpty) ev(null) else value - @inline def map[B](f: A => B): NOpt[B] = + inline def map[B](inline f: A => B): NOpt[B] = if (isEmpty) NOpt.Empty else NOpt.some(f(value)) - @inline def fold[B](ifEmpty: => B)(f: A => B): B = + inline def fold[B](inline ifEmpty: B)(inline f: A => B): B = if (isEmpty) ifEmpty else f(value) /** The same as [[fold]] but takes arguments in a single parameter list for better type inference. */ - @inline def mapOr[B](ifEmpty: => B, f: A => B): B = + inline def mapOr[B](inline ifEmpty: B, inline f: A => B): B = if (isEmpty) ifEmpty else f(value) - @inline def flatMap[B](f: A => NOpt[B]): NOpt[B] = + inline def flatMap[B](inline f: A => NOpt[B]): NOpt[B] = if (isEmpty) NOpt.Empty else f(value) - @inline def flatten[B](implicit ev: A <:< NOpt[B]): NOpt[B] = + def flatten[B](implicit ev: A <:< NOpt[B]): NOpt[B] = if (isEmpty) NOpt.Empty else ev(value) - @inline def filter(p: A => Boolean): NOpt[A] = + inline def filter(inline p: A => Boolean): NOpt[A] = if (isEmpty || p(value)) this else NOpt.Empty - @inline def withFilter(p: A => Boolean): NOpt.WithFilter[A] = + inline def withFilter(inline p: A => Boolean): NOpt.WithFilter[A] = new NOpt.WithFilter[A](this, p) - @inline def filterNot(p: A => Boolean): NOpt[A] = + inline def filterNot(inline p: A => Boolean): NOpt[A] = if (isEmpty || !p(value)) this else NOpt.Empty - @inline def contains[A1 >: A](elem: A1): Boolean = + def contains[A1 >: A](elem: A1): Boolean = !isEmpty && value == elem - @inline def exists(p: A => Boolean): Boolean = + inline def exists(inline p: A => Boolean): Boolean = !isEmpty && p(value) - @inline def forall(p: A => Boolean): Boolean = + inline def forall(inline p: A => Boolean): Boolean = isEmpty || p(value) - @inline def foreach[U](f: A => U): Unit = { + inline def foreach[U](inline f: A => U): Unit = { if (!isEmpty) f(value) } - @inline def collect[B](pf: PartialFunction[A, B]): NOpt[B] = + inline def collect[B](inline pf: PartialFunction[A, B]): NOpt[B] = if (!isEmpty) { val res = pf.applyOrElse(value, NOpt.emptyMarkerFunc) new NOpt(if (res == null) NullMarker else res) } else NOpt.Empty - @inline def orElse[B >: A](alternative: => NOpt[B]): NOpt[B] = + inline def orElse[B >: A](inline alternative: NOpt[B]): NOpt[B] = if (isEmpty) alternative else this - @inline def iterator: Iterator[A] = + def iterator: Iterator[A] = if (isEmpty) Iterator.empty else Iterator.single(value) - @inline def toList: List[A] = + def toList: List[A] = if (isEmpty) List() else new ::(value, Nil) - @inline def toRight[X](left: => X): Either[X, A] = + inline def toRight[X](inline left: X): Either[X, A] = if (isEmpty) Left(left) else Right(value) - @inline def toLeft[X](right: => X): Either[A, X] = + inline def toLeft[X](inline right: X): Either[A, X] = if (isEmpty) Right(right) else Left(value) - @inline def zip[B](that: NOpt[B]): NOpt[(A, B)] = + def zip[B](that: NOpt[B]): NOpt[(A, B)] = if (isEmpty || that.isEmpty) NOpt.Empty else NOpt((this.get, that.get)) /** Apply side effect only if NOpt is empty. It's a bit like foreach for NOpt.Empty @@ -154,7 +157,7 @@ final class NOpt[+A] private (private val rawValue: Any) extends AnyVal with Opt * @return the same nopt * @example {{{captionNOpt.forEmpty(logger.warn("caption is empty")).foreach(setCaption)}}} */ - @inline def forEmpty(sideEffect: => Unit): NOpt[A] = { + inline def forEmpty(inline sideEffect: Unit): NOpt[A] = { if (isEmpty) { sideEffect } diff --git a/core/src/main/scala/com/avsystem/commons/misc/Opt.scala b/core/src/main/scala/com/avsystem/commons/misc/Opt.scala index c2ebc9129..df19a8e47 100644 --- a/core/src/main/scala/com/avsystem/commons/misc/Opt.scala +++ b/core/src/main/scala/com/avsystem/commons/misc/Opt.scala @@ -2,10 +2,12 @@ package com.avsystem.commons.misc import com.avsystem.commons.IIterable +import scala.annotation.publicInBinary + object Opt { // Used as Opt's raw value to represent empty Opt. Unfortunately, null can't be used for that purpose // because https://github.com/scala/bug/issues/7396 - private object EmptyMarker extends Serializable + @publicInBinary private[misc] object EmptyMarker extends Serializable def apply[A](value: A): Opt[A] = if (value != null) new Opt[A](value) else Opt.Empty def unapply[A](opt: Opt[A]): Opt[A] = opt // name-based extractor @@ -20,9 +22,9 @@ object Opt { def empty[A]: Opt[A] = Empty - private val emptyMarkerFunc: Any => Any = _ => EmptyMarker + @publicInBinary private[misc] val emptyMarkerFunc: Any => Any = _ => EmptyMarker - final class WithFilter[+A] private[Opt] (self: Opt[A], p: A => Boolean) { + final class WithFilter[+A] @publicInBinary private[Opt] (self: Opt[A], p: A => Boolean) { def map[B](f: A => B): Opt[B] = self.filter(p) map f def flatMap[B](f: A => Opt[B]): Opt[B] = self.filter(p) flatMap f def foreach[U](f: A => U): Unit = self.filter(p).foreach(f) @@ -39,7 +41,7 @@ object Opt { /** Unless a given condition is true, this will evaluate the `opt` argument and return it. Otherwise, `opt` is not * evaluated and [[Opt.Empty]] is returned. */ - @inline def unless(cond: Boolean): Opt[A] = when(!cond) + def unless(cond: Boolean): Opt[A] = when(!cond) } implicit def lazyOptOps[A](opt: => Opt[A]): LazyOptOps[A] = new LazyOptOps(() => opt) @@ -54,111 +56,112 @@ object Opt { * WARNING: Unfortunately, using `Opt` in pattern matches turns off the exhaustivity checking. Please be aware of that * tradeoff. */ -final class Opt[+A] private (private val rawValue: Any) extends AnyVal with OptBase[A] with Serializable { +final class Opt[+A] @publicInBinary private[misc] (@publicInBinary private[misc] val rawValue: Any) + extends AnyVal with OptBase[A] with Serializable { import Opt.* private def value: A = rawValue.asInstanceOf[A] - @inline def isEmpty: Boolean = rawValue.asInstanceOf[AnyRef] eq EmptyMarker - @inline def isDefined: Boolean = !isEmpty - @inline def nonEmpty: Boolean = isDefined + def isEmpty: Boolean = rawValue.asInstanceOf[AnyRef] eq EmptyMarker + def isDefined: Boolean = !isEmpty + def nonEmpty: Boolean = isDefined - @inline def get: A = + def get: A = if (isEmpty) throw new NoSuchElementException("empty Opt") else value - @inline def boxed[B](implicit boxing: Boxing[A, B]): Opt[B] = + def boxed[B](implicit boxing: Boxing[A, B]): Opt[B] = map(boxing.fun) - @inline def boxedOrNull[B >: Null](implicit boxing: Boxing[A, B]): B = + def boxedOrNull[B >: Null](implicit boxing: Boxing[A, B]): B = if (isEmpty) null else boxing.fun(value) - @inline def unboxed[B](implicit unboxing: Unboxing[B, A]): Opt[B] = + def unboxed[B](implicit unboxing: Unboxing[B, A]): Opt[B] = map(unboxing.fun) - @inline def toOption: Option[A] = + def toOption: Option[A] = if (isEmpty) None else Some(value) - @inline def toOptRef[B >: Null](implicit boxing: Boxing[A, B]): OptRef[B] = + def toOptRef[B >: Null](implicit boxing: Boxing[A, B]): OptRef[B] = if (isEmpty) OptRef.Empty else OptRef(boxing.fun(value)) - @inline def toNOpt: NOpt[A] = + def toNOpt: NOpt[A] = if (isEmpty) NOpt.Empty else NOpt(value) - @inline def toOptArg: OptArg[A] = + def toOptArg: OptArg[A] = if (isEmpty) OptArg.Empty else OptArg(value) - @inline def getOrElse[B >: A](default: => B): B = + inline def getOrElse[B >: A](inline default: B): B = if (isEmpty) default else value - @inline def orNull[B >: A](implicit ev: Null <:< B): B = + def orNull[B >: A](implicit ev: Null <:< B): B = if (isEmpty) ev(null) else value /** Analogous to `Option.map` except that when mapping function returns `null`, empty `Opt` is returned as a result. */ - @inline def map[B](f: A => B): Opt[B] = + inline def map[B](inline f: A => B): Opt[B] = if (isEmpty) Opt.Empty else Opt(f(value)) - @inline def fold[B](ifEmpty: => B)(f: A => B): B = + inline def fold[B](inline ifEmpty: B)(inline f: A => B): B = if (isEmpty) ifEmpty else f(value) /** The same as [[fold]] but takes arguments in a single parameter list for better type inference. */ - @inline def mapOr[B](ifEmpty: => B, f: A => B): B = + inline def mapOr[B](inline ifEmpty: B, inline f: A => B): B = if (isEmpty) ifEmpty else f(value) - @inline def flatMap[B](f: A => Opt[B]): Opt[B] = + inline def flatMap[B](inline f: A => Opt[B]): Opt[B] = if (isEmpty) Opt.Empty else f(value) - @inline def flatten[B](implicit ev: A <:< Opt[B]): Opt[B] = + def flatten[B](implicit ev: A <:< Opt[B]): Opt[B] = if (isEmpty) Opt.Empty else ev(value) - @inline def filter(p: A => Boolean): Opt[A] = + inline def filter(inline p: A => Boolean): Opt[A] = if (isEmpty || p(value)) this else Opt.Empty - @inline def withFilter(p: A => Boolean): Opt.WithFilter[A] = + inline def withFilter(inline p: A => Boolean): Opt.WithFilter[A] = new Opt.WithFilter[A](this, p) - @inline def filterNot(p: A => Boolean): Opt[A] = + inline def filterNot(inline p: A => Boolean): Opt[A] = if (isEmpty || !p(value)) this else Opt.Empty - @inline def contains[A1 >: A](elem: A1): Boolean = + def contains[A1 >: A](elem: A1): Boolean = !isEmpty && value == elem - @inline def exists(p: A => Boolean): Boolean = + inline def exists(inline p: A => Boolean): Boolean = !isEmpty && p(value) - @inline def forall(p: A => Boolean): Boolean = + inline def forall(inline p: A => Boolean): Boolean = isEmpty || p(value) - @inline def foreach[U](f: A => U): Unit = { + inline def foreach[U](inline f: A => U): Unit = { if (!isEmpty) f(value) } /** Analogous to `Option.collect` except that when the function returns `null`, empty `Opt` is returned as a result. */ - @inline def collect[B](pf: PartialFunction[A, B]): Opt[B] = + inline def collect[B](inline pf: PartialFunction[A, B]): Opt[B] = if (!isEmpty) { val res = pf.applyOrElse(value, Opt.emptyMarkerFunc) new Opt(if (res == null) EmptyMarker else res) } else Opt.Empty - @inline def orElse[B >: A](alternative: => Opt[B]): Opt[B] = + inline def orElse[B >: A](inline alternative: Opt[B]): Opt[B] = if (isEmpty) alternative else this - @inline def iterator: Iterator[A] = + def iterator: Iterator[A] = if (isEmpty) Iterator.empty else Iterator.single(value) - @inline def toList: List[A] = + def toList: List[A] = if (isEmpty) List() else value :: Nil - @inline def toRight[X](left: => X): Either[X, A] = + inline def toRight[X](inline left: X): Either[X, A] = if (isEmpty) Left(left) else Right(value) - @inline def toLeft[X](right: => X): Either[A, X] = + inline def toLeft[X](inline right: X): Either[A, X] = if (isEmpty) Right(right) else Left(value) - @inline def zip[B](that: Opt[B]): Opt[(A, B)] = + def zip[B](that: Opt[B]): Opt[(A, B)] = if (isEmpty || that.isEmpty) Opt.Empty else Opt((this.get, that.get)) /** Apply side effect only if Opt is empty. It's a bit like foreach for Opt.Empty @@ -167,7 +170,7 @@ final class Opt[+A] private (private val rawValue: Any) extends AnyVal with OptB * @return the same opt * @example {{{captionOpt.forEmpty(logger.warn("caption is empty")).foreach(setCaption)}}} */ - @inline def forEmpty(sideEffect: => Unit): Opt[A] = { + inline def forEmpty(inline sideEffect: Unit): Opt[A] = { if (isEmpty) { sideEffect } diff --git a/core/src/main/scala/com/avsystem/commons/misc/OptArg.scala b/core/src/main/scala/com/avsystem/commons/misc/OptArg.scala index 78d0e5d7c..cd395467d 100644 --- a/core/src/main/scala/com/avsystem/commons/misc/OptArg.scala +++ b/core/src/main/scala/com/avsystem/commons/misc/OptArg.scala @@ -1,5 +1,7 @@ package com.avsystem.commons.misc +import scala.annotation.publicInBinary + object OptArg { /** This implicit conversion allows you to pass unwrapped values where `OptArg` is required. @@ -10,7 +12,7 @@ object OptArg { implicit def intToOptArgLong(int: Int): OptArg[Long] = OptArg(int) implicit def intToOptArgDouble(int: Int): OptArg[Double] = OptArg(int) - private object EmptyMarker extends Serializable + @publicInBinary private[misc] object EmptyMarker extends Serializable def apply[A](value: A): OptArg[A] = new OptArg[A](if (value != null) value else EmptyMarker) def unapply[A](opt: OptArg[A]): OptArg[A] = opt // name-based extractor @@ -42,69 +44,70 @@ object OptArg { * [[OptArg]] is converted to [[Opt]], [[NOpt]] or `Option` as soon as possible (using `toOpt`, `toNOpt` and `toOption` * methods). */ -final class OptArg[+A] private (private val rawValue: Any) extends AnyVal with OptBase[A] with Serializable { +final class OptArg[+A] @publicInBinary private[misc] (@publicInBinary private[misc] val rawValue: Any) + extends AnyVal with OptBase[A] with Serializable { import OptArg._ private def value: A = rawValue.asInstanceOf[A] - @inline def get: A = if (isEmpty) throw new NoSuchElementException("empty OptArg") else value + def get: A = if (isEmpty) throw new NoSuchElementException("empty OptArg") else value - @inline def isEmpty: Boolean = rawValue.asInstanceOf[AnyRef] eq EmptyMarker - @inline def isDefined: Boolean = !isEmpty - @inline def nonEmpty: Boolean = isDefined + def isEmpty: Boolean = rawValue.asInstanceOf[AnyRef] eq EmptyMarker + def isDefined: Boolean = !isEmpty + def nonEmpty: Boolean = isDefined - @inline def boxedOrNull[B >: Null](implicit boxing: Boxing[A, B]): B = + def boxedOrNull[B >: Null](implicit boxing: Boxing[A, B]): B = if (isEmpty) null else boxing.fun(value) - @inline def toOpt: Opt[A] = + def toOpt: Opt[A] = if (isEmpty) Opt.Empty else Opt(value) - @inline def toOption: Option[A] = + def toOption: Option[A] = if (isEmpty) None else Some(value) - @inline def toNOpt: NOpt[A] = + def toNOpt: NOpt[A] = if (isEmpty) NOpt.Empty else NOpt.some(value) - @inline def toOptRef[B >: Null](implicit boxing: Boxing[A, B]): OptRef[B] = + def toOptRef[B >: Null](implicit boxing: Boxing[A, B]): OptRef[B] = if (isEmpty) OptRef.Empty else OptRef(boxing.fun(value)) - @inline def getOrElse[B >: A](default: => B): B = + inline def getOrElse[B >: A](inline default: B): B = if (isEmpty) default else value - @inline def orNull[B >: A](implicit ev: Null <:< B): B = + def orNull[B >: A](implicit ev: Null <:< B): B = if (isEmpty) ev(null) else value - @inline def fold[B](ifEmpty: => B)(f: A => B): B = + inline def fold[B](inline ifEmpty: B)(inline f: A => B): B = if (isEmpty) ifEmpty else f(value) /** The same as [[fold]] but takes arguments in a single parameter list for better type inference. */ - @inline def mapOr[B](ifEmpty: => B, f: A => B): B = + inline def mapOr[B](inline ifEmpty: B, inline f: A => B): B = if (isEmpty) ifEmpty else f(value) - @inline def contains[A1 >: A](elem: A1): Boolean = + def contains[A1 >: A](elem: A1): Boolean = !isEmpty && value == elem - @inline def exists(p: A => Boolean): Boolean = + inline def exists(inline p: A => Boolean): Boolean = !isEmpty && p(value) - @inline def forall(p: A => Boolean): Boolean = + inline def forall(inline p: A => Boolean): Boolean = isEmpty || p(value) - @inline def foreach[U](f: A => U): Unit = { + inline def foreach[U](inline f: A => U): Unit = { if (!isEmpty) f(value) } - @inline def iterator: Iterator[A] = + def iterator: Iterator[A] = if (isEmpty) Iterator.empty else Iterator.single(value) - @inline def toList: List[A] = + def toList: List[A] = if (isEmpty) List() else new ::(value, Nil) - @inline def toRight[X](left: => X): Either[X, A] = + inline def toRight[X](inline left: X): Either[X, A] = if (isEmpty) Left(left) else Right(value) - @inline def toLeft[X](right: => X): Either[A, X] = + inline def toLeft[X](inline right: X): Either[A, X] = if (isEmpty) Right(right) else Left(value) /** Apply side effect only if OptArg is empty. It's a bit like foreach for OptArg.Empty @@ -113,7 +116,7 @@ final class OptArg[+A] private (private val rawValue: Any) extends AnyVal with O * @return the same optArg * @example {{{captionOptArg.forEmpty(logger.warn("caption is empty")).foreach(setCaption)}}} */ - @inline def forEmpty(sideEffect: => Unit): OptArg[A] = { + inline def forEmpty(inline sideEffect: Unit): OptArg[A] = { if (isEmpty) { sideEffect } diff --git a/core/src/main/scala/com/avsystem/commons/misc/OptRef.scala b/core/src/main/scala/com/avsystem/commons/misc/OptRef.scala index 8df85ba2c..5d1793353 100644 --- a/core/src/main/scala/com/avsystem/commons/misc/OptRef.scala +++ b/core/src/main/scala/com/avsystem/commons/misc/OptRef.scala @@ -2,6 +2,8 @@ package com.avsystem.commons.misc import com.avsystem.commons.IIterable +import scala.annotation.publicInBinary + object OptRef { def apply[A >: Null](value: A): OptRef[A] = new OptRef[A](value) def unapply[A >: Null](opt: OptRef[A]): OptRef[A] = opt // name-based extractor @@ -21,9 +23,9 @@ object OptRef { def empty[A >: Null]: OptRef[A] = Empty - private val nullFunc: Any => Null = _ => null + @publicInBinary private[misc] val nullFunc: Any => Null = _ => null - final class WithFilter[+A >: Null] private[OptRef] (self: OptRef[A], p: A => Boolean) { + final class WithFilter[+A >: Null] @publicInBinary private[OptRef] (self: OptRef[A], p: A => Boolean) { def map[B >: Null](f: A => B): OptRef[B] = self.filter(p) map f def flatMap[B >: Null](f: A => OptRef[B]): OptRef[B] = self.filter(p) flatMap f def foreach[U](f: A => U): Unit = self.filter(p).foreach(f) @@ -45,87 +47,88 @@ object OptRef { * SI-7396 (`hashCode` fails on `OptRef.Empty` which means that you can't add [[OptRef]] values into hash sets or use * them as hash map keys). */ -final class OptRef[+A >: Null] private (private val value: A) extends AnyVal with OptBase[A] with Serializable { - @inline def isEmpty: Boolean = value == null - @inline def isDefined: Boolean = !isEmpty - @inline def nonEmpty: Boolean = isDefined +final class OptRef[+A >: Null] @publicInBinary private[misc] (@publicInBinary private[misc] val value: A) + extends AnyVal with OptBase[A] with Serializable { + def isEmpty: Boolean = value == null + def isDefined: Boolean = !isEmpty + def nonEmpty: Boolean = isDefined - @inline def get: A = + def get: A = if (isEmpty) throw new NoSuchElementException("empty OptRef") else value - @inline def toOpt: Opt[A] = + def toOpt: Opt[A] = Opt(value) - @inline def toOption: Option[A] = + def toOption: Option[A] = Option(value) - @inline def toNOpt: NOpt[A] = + def toNOpt: NOpt[A] = if (isEmpty) NOpt.Empty else NOpt(value) - @inline def toOptArg: OptArg[A] = + def toOptArg: OptArg[A] = if (isEmpty) OptArg.Empty else OptArg(value) - @inline def getOrElse[B >: A](default: => B): B = + inline def getOrElse[B >: A](inline default: B): B = if (isEmpty) default else value - @inline def orNull[B >: A](implicit ev: Null <:< B): B = + def orNull[B >: A](implicit ev: Null <:< B): B = value.asInstanceOf[B] - @inline def map[B >: Null](f: A => B): OptRef[B] = + inline def map[B >: Null](inline f: A => B): OptRef[B] = if (isEmpty) OptRef.Empty else OptRef(f(value)) - @inline def fold[B](ifEmpty: => B)(f: A => B): B = + inline def fold[B](inline ifEmpty: B)(inline f: A => B): B = if (isEmpty) ifEmpty else f(value) /** The same as [[fold]] but takes arguments in a single parameter list for better type inference. */ - @inline def mapOr[B](ifEmpty: => B, f: A => B): B = + inline def mapOr[B](inline ifEmpty: B, inline f: A => B): B = if (isEmpty) ifEmpty else f(value) - @inline def flatMap[B >: Null](f: A => OptRef[B]): OptRef[B] = + inline def flatMap[B >: Null](inline f: A => OptRef[B]): OptRef[B] = if (isEmpty) OptRef.Empty else f(value) - @inline def filter(p: A => Boolean): OptRef[A] = + inline def filter(inline p: A => Boolean): OptRef[A] = if (isEmpty || p(value)) this else OptRef.Empty - @inline def withFilter(p: A => Boolean): OptRef.WithFilter[A] = + inline def withFilter(inline p: A => Boolean): OptRef.WithFilter[A] = new OptRef.WithFilter[A](this, p) - @inline def filterNot(p: A => Boolean): OptRef[A] = + inline def filterNot(inline p: A => Boolean): OptRef[A] = if (isEmpty || !p(value)) this else OptRef.Empty - @inline def contains[A1 >: A](elem: A1): Boolean = + def contains[A1 >: A](elem: A1): Boolean = !isEmpty && value == elem - @inline def exists(p: A => Boolean): Boolean = + inline def exists(inline p: A => Boolean): Boolean = !isEmpty && p(value) - @inline def forall(p: A => Boolean): Boolean = + inline def forall(inline p: A => Boolean): Boolean = isEmpty || p(value) - @inline def foreach[U](f: A => U): Unit = { + inline def foreach[U](inline f: A => U): Unit = { if (!isEmpty) f(value) } - @inline def collect[B >: Null](pf: PartialFunction[A, B]): OptRef[B] = + inline def collect[B >: Null](inline pf: PartialFunction[A, B]): OptRef[B] = if (!isEmpty) new OptRef(pf.applyOrElse(value, OptRef.nullFunc)) else OptRef.Empty - @inline def orElse[B >: A](alternative: => OptRef[B]): OptRef[B] = + inline def orElse[B >: A](inline alternative: OptRef[B]): OptRef[B] = if (isEmpty) alternative else this - @inline def iterator: Iterator[A] = + def iterator: Iterator[A] = if (isEmpty) Iterator.empty else Iterator.single(value) - @inline def toList: List[A] = + def toList: List[A] = if (isEmpty) List() else new ::(value, Nil) - @inline def toRight[X](left: => X): Either[X, A] = + inline def toRight[X](inline left: X): Either[X, A] = if (isEmpty) Left(left) else Right(value) - @inline def toLeft[X](right: => X): Either[A, X] = + inline def toLeft[X](inline right: X): Either[A, X] = if (isEmpty) Right(right) else Left(value) - @inline def zip[B >: Null](that: OptRef[B]): OptRef[(A, B)] = + def zip[B >: Null](that: OptRef[B]): OptRef[(A, B)] = if (isEmpty || that.isEmpty) OptRef.Empty else OptRef((this.get, that.get)) /** Apply side effect only if OptRef is empty. It's a bit like foreach for OptRef.Empty @@ -134,7 +137,7 @@ final class OptRef[+A >: Null] private (private val value: A) extends AnyVal wit * @return the same optRef * @example {{{captionOptRef.forEmpty(logger.warn("caption is empty")).foreach(setCaption)}}} */ - @inline def forEmpty(sideEffect: => Unit): OptRef[A] = { + inline def forEmpty(inline sideEffect: Unit): OptRef[A] = { if (isEmpty) { sideEffect } From 1f5a8deaf902a0f7d51c5a8da591d847454e18e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 1 Jun 2026 21:50:53 +0200 Subject: [PATCH 2/5] perf(scala-3,core): inline Function-taking ops in jiop adapters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../avsystem/commons/jiop/GuavaInterop.scala | 6 +- .../commons/jiop/JFunctionUtils.scala | 86 +++++++++---------- .../commons/jiop/Java8CollectionUtils.scala | 18 ++-- .../commons/jiop/ScalaJDoubleStream.scala | 32 +++---- .../commons/jiop/ScalaJIntStream.scala | 32 +++---- .../commons/jiop/ScalaJLongStream.scala | 32 +++---- .../avsystem/commons/jiop/ScalaJStream.scala | 46 +++++----- 7 files changed, 126 insertions(+), 126 deletions(-) diff --git a/core/jvm/src/main/scala/com/avsystem/commons/jiop/GuavaInterop.scala b/core/jvm/src/main/scala/com/avsystem/commons/jiop/GuavaInterop.scala index 655078b61..59542644b 100644 --- a/core/jvm/src/main/scala/com/avsystem/commons/jiop/GuavaInterop.scala +++ b/core/jvm/src/main/scala/com/avsystem/commons/jiop/GuavaInterop.scala @@ -15,9 +15,9 @@ trait GuavaInterop { type GSupplier[T] = gbase.Supplier[T] type GPredicate[T] = gbase.Predicate[T] - def gFunction[F, T](fun: F => T): GFunction[F, T] = fun(_) - def gSupplier[T](expr: => T): GSupplier[T] = () => expr - def gPredicate[T](pred: T => Boolean): GPredicate[T] = pred(_) + inline def gFunction[F, T](inline fun: F => T): GFunction[F, T] = fun(_) + inline def gSupplier[T](expr: => T): GSupplier[T] = () => expr + inline def gPredicate[T](inline pred: T => Boolean): GPredicate[T] = pred(_) implicit def toDecorateAsScala[T](gfut: ListenableFuture[T]): DecorateFutureAsScala[T] = new DecorateFutureAsScala(gfut) diff --git a/core/jvm/src/main/scala/com/avsystem/commons/jiop/JFunctionUtils.scala b/core/jvm/src/main/scala/com/avsystem/commons/jiop/JFunctionUtils.scala index 31bc1c9d0..2c1bbc866 100644 --- a/core/jvm/src/main/scala/com/avsystem/commons/jiop/JFunctionUtils.scala +++ b/core/jvm/src/main/scala/com/avsystem/commons/jiop/JFunctionUtils.scala @@ -50,47 +50,47 @@ trait JFunctionUtils { type JToLongFunction[T] = juf.ToLongFunction[T] type JUnaryOperator[T] = juf.UnaryOperator[T] - def jBiConsumer[T, U](code: (T, U) => Any): JBiConsumer[T, U] = (t, u) => { code(t, u); () } - def jBiFunction[T, U, R](fun: (T, U) => R): JBiFunction[T, U, R] = fun(_, _) - def jBiPredicate[T, U](pred: (T, U) => Boolean): JBiPredicate[T, U] = pred(_, _) - def jBinaryOperator[T](op: (T, T) => T): JBinaryOperator[T] = op(_, _) - def jBooleanSupplier(expr: => Boolean): JBooleanSupplier = () => expr - def jConsumer[T](code: T => Any): JConsumer[T] = t => { code(t); () } - def jDoubleBinaryOperator(op: (Double, Double) => Double): JDoubleBinaryOperator = op(_, _) - def jDoubleConsumer(code: Double => Any): JDoubleConsumer = d => { code(d); () } - def jDoubleFunction[R](fun: Double => R): JDoubleFunction[R] = fun(_) - def jDoublePredicate(pred: Double => Boolean): JDoublePredicate = pred(_) - def jDoubleSupplier(expr: => Double): JDoubleSupplier = () => expr - def jDoubleToIntFunction(fun: Double => Int): JDoubleToIntFunction = fun(_) - def jDoubleToLongFunction(fun: Double => Long): JDoubleToLongFunction = fun(_) - def jDoubleUnaryOperator(op: Double => Double): JDoubleUnaryOperator = op(_) - def jFunction[T, R](fun: T => R): JFunction[T, R] = fun(_) - def jIntBinaryOperator(op: (Int, Int) => Int): JIntBinaryOperator = op(_, _) - def jIntConsumer(code: Int => Any): JIntConsumer = i => { code(i); () } - def jIntFunction[R](fun: Int => R): JIntFunction[R] = fun(_) - def jIntPredicate(pred: Int => Boolean): JIntPredicate = pred(_) - def jIntSupplier(expr: => Int): JIntSupplier = () => expr - def jIntToDoubleFunction(fun: Int => Double): JIntToDoubleFunction = fun(_) - def jIntToLongFunction(fun: Int => Long): JIntToLongFunction = fun(_) - def jIntUnaryOperator(op: Int => Int): JIntUnaryOperator = op(_) - def jLongBinaryOperator(op: (Long, Long) => Long): JLongBinaryOperator = op(_, _) - def jLongConsumer(code: Long => Any): JLongConsumer = l => { code(l); () } - def jLongFunction[R](fun: Long => R): JLongFunction[R] = fun(_) - def jLongPredicate(pred: Long => Boolean): JLongPredicate = pred(_) - def jLongSupplier(expr: => Long): JLongSupplier = () => expr - def jLongToDoubleFunction(fun: Long => Double): JLongToDoubleFunction = fun(_) - def jLongToIntFunction(fun: Long => Int): JLongToIntFunction = fun(_) - def jLongUnaryOperator(op: Long => Long): JLongUnaryOperator = op(_) - def jObjDoubleConsumer[T](code: (T, Double) => Any): JObjDoubleConsumer[T] = (t, d) => { code(t, d); () } - def jObjIntConsumer[T](code: (T, Int) => Any): JObjIntConsumer[T] = (t, i) => { code(t, i); () } - def jObjLongConsumer[T](code: (T, Long) => Any): JObjLongConsumer[T] = (t, l) => { code(t, l); () } - def jPredicate[T](pred: T => Boolean): JPredicate[T] = pred(_) - def jSupplier[T](expr: => T): JSupplier[T] = () => expr - def jToDoubleBiFunction[T, U](fun: (T, U) => Double): JToDoubleBiFunction[T, U] = fun(_, _) - def jToDoubleFunction[T](fun: T => Double): JToDoubleFunction[T] = fun(_) - def jToIntBiFunction[T, U](fun: (T, U) => Int): JToIntBiFunction[T, U] = fun(_, _) - def jToIntFunction[T](fun: T => Int): JToIntFunction[T] = fun(_) - def jToLongBiFunction[T, U](fun: (T, U) => Long): JToLongBiFunction[T, U] = fun(_, _) - def jToLongFunction[T](fun: T => Long): JToLongFunction[T] = fun(_) - def jUnaryOperator[T](op: T => T): JUnaryOperator[T] = op(_) + inline def jBiConsumer[T, U](inline code: (T, U) => Any): JBiConsumer[T, U] = (t, u) => code(t, u) + inline def jBiFunction[T, U, R](inline fun: (T, U) => R): JBiFunction[T, U, R] = fun(_, _) + inline def jBiPredicate[T, U](inline pred: (T, U) => Boolean): JBiPredicate[T, U] = pred(_, _) + inline def jBinaryOperator[T](inline op: (T, T) => T): JBinaryOperator[T] = op(_, _) + inline def jBooleanSupplier(expr: => Boolean): JBooleanSupplier = () => expr + inline def jConsumer[T](inline code: T => Any): JConsumer[T] = t => code(t) + inline def jDoubleBinaryOperator(inline op: (Double, Double) => Double): JDoubleBinaryOperator = op(_, _) + inline def jDoubleConsumer(inline code: Double => Any): JDoubleConsumer = d => code(d) + inline def jDoubleFunction[R](inline fun: Double => R): JDoubleFunction[R] = fun(_) + inline def jDoublePredicate(inline pred: Double => Boolean): JDoublePredicate = pred(_) + inline def jDoubleSupplier(expr: => Double): JDoubleSupplier = () => expr + inline def jDoubleToIntFunction(inline fun: Double => Int): JDoubleToIntFunction = fun(_) + inline def jDoubleToLongFunction(inline fun: Double => Long): JDoubleToLongFunction = fun(_) + inline def jDoubleUnaryOperator(inline op: Double => Double): JDoubleUnaryOperator = op(_) + inline def jFunction[T, R](inline fun: T => R): JFunction[T, R] = fun(_) + inline def jIntBinaryOperator(inline op: (Int, Int) => Int): JIntBinaryOperator = op(_, _) + inline def jIntConsumer(inline code: Int => Any): JIntConsumer = i => code(i) + inline def jIntFunction[R](inline fun: Int => R): JIntFunction[R] = fun(_) + inline def jIntPredicate(inline pred: Int => Boolean): JIntPredicate = pred(_) + inline def jIntSupplier(expr: => Int): JIntSupplier = () => expr + inline def jIntToDoubleFunction(inline fun: Int => Double): JIntToDoubleFunction = fun(_) + inline def jIntToLongFunction(inline fun: Int => Long): JIntToLongFunction = fun(_) + inline def jIntUnaryOperator(inline op: Int => Int): JIntUnaryOperator = op(_) + inline def jLongBinaryOperator(inline op: (Long, Long) => Long): JLongBinaryOperator = op(_, _) + inline def jLongConsumer(inline code: Long => Any): JLongConsumer = l => code(l) + inline def jLongFunction[R](inline fun: Long => R): JLongFunction[R] = fun(_) + inline def jLongPredicate(inline pred: Long => Boolean): JLongPredicate = pred(_) + inline def jLongSupplier(expr: => Long): JLongSupplier = () => expr + inline def jLongToDoubleFunction(inline fun: Long => Double): JLongToDoubleFunction = fun(_) + inline def jLongToIntFunction(inline fun: Long => Int): JLongToIntFunction = fun(_) + inline def jLongUnaryOperator(inline op: Long => Long): JLongUnaryOperator = op(_) + inline def jObjDoubleConsumer[T](inline code: (T, Double) => Any): JObjDoubleConsumer[T] = (t, d) => code(t, d) + inline def jObjIntConsumer[T](inline code: (T, Int) => Any): JObjIntConsumer[T] = (t, i) => code(t, i) + inline def jObjLongConsumer[T](inline code: (T, Long) => Any): JObjLongConsumer[T] = (t, l) => code(t, l) + inline def jPredicate[T](inline pred: T => Boolean): JPredicate[T] = pred(_) + inline def jSupplier[T](expr: => T): JSupplier[T] = () => expr + inline def jToDoubleBiFunction[T, U](inline fun: (T, U) => Double): JToDoubleBiFunction[T, U] = fun(_, _) + inline def jToDoubleFunction[T](inline fun: T => Double): JToDoubleFunction[T] = fun(_) + inline def jToIntBiFunction[T, U](inline fun: (T, U) => Int): JToIntBiFunction[T, U] = fun(_, _) + inline def jToIntFunction[T](inline fun: T => Int): JToIntFunction[T] = fun(_) + inline def jToLongBiFunction[T, U](inline fun: (T, U) => Long): JToLongBiFunction[T, U] = fun(_, _) + inline def jToLongFunction[T](inline fun: T => Long): JToLongFunction[T] = fun(_) + inline def jUnaryOperator[T](inline op: T => T): JUnaryOperator[T] = op(_) } diff --git a/core/jvm/src/main/scala/com/avsystem/commons/jiop/Java8CollectionUtils.scala b/core/jvm/src/main/scala/com/avsystem/commons/jiop/Java8CollectionUtils.scala index 6320b2d41..2332ddf99 100644 --- a/core/jvm/src/main/scala/com/avsystem/commons/jiop/Java8CollectionUtils.scala +++ b/core/jvm/src/main/scala/com/avsystem/commons/jiop/Java8CollectionUtils.scala @@ -16,17 +16,17 @@ trait Java8CollectionUtils { object Java8CollectionUtils { class jIteratorOps[A](private val it: JIterator[A]) extends AnyVal { - def forEachRemaining(code: A => Any): Unit = + inline def forEachRemaining(inline code: A => Any): Unit = it.forEachRemaining(jConsumer(code)) } class jIterableOps[A](private val it: JIterable[A]) extends AnyVal { - def forEach(code: A => Any): Unit = + inline def forEach(inline code: A => Any): Unit = it.forEach(jConsumer(code)) } class jCollectionOps[A](private val coll: JCollection[A]) extends AnyVal { - def removeIf(pred: A => Boolean): Unit = + inline def removeIf(inline pred: A => Boolean): Unit = coll.removeIf(jPredicate(pred)) def scalaStream: ScalaJStream[A] = @@ -49,22 +49,22 @@ object Java8CollectionUtils { } class jMapOps[K, V](private val map: JMap[K, V]) extends AnyVal { - def compute(key: K, remappingFunction: (K, V) => V): V = + inline def compute(key: K, inline remappingFunction: (K, V) => V): V = map.compute(key, jBiFunction(remappingFunction)) - def computeIfAbsent(key: K)(mappingFunction: K => V): V = + inline def computeIfAbsent(key: K)(inline mappingFunction: K => V): V = map.computeIfAbsent(key, jFunction(mappingFunction)) - def computeIfPresent(key: K)(remappingFunction: (K, V) => V): V = + inline def computeIfPresent(key: K)(inline remappingFunction: (K, V) => V): V = map.computeIfPresent(key, jBiFunction(remappingFunction)) - def forEach(action: (K, V) => Any): Unit = + inline def forEach(inline action: (K, V) => Any): Unit = map.forEach(jBiConsumer(action)) - def merge(key: K, value: V)(remappingFunction: (V, V) => V): V = + inline def merge(key: K, value: V)(inline remappingFunction: (V, V) => V): V = map.merge(key, value, jBiFunction(remappingFunction)) - def replaceAll(function: (K, V) => V): Unit = + inline def replaceAll(inline function: (K, V) => V): Unit = map.replaceAll(jBiFunction(function)) } } diff --git a/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJDoubleStream.scala b/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJDoubleStream.scala index 2e378f336..f2d16e8cc 100644 --- a/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJDoubleStream.scala +++ b/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJDoubleStream.scala @@ -16,7 +16,7 @@ final class ScalaJDoubleStream(private val jStream: JDoubleStream) extends AnyVa def iterator: Iterator[Double] = jStream.iterator().asInstanceOf[JIterator[Double]].asScala - def onClose(closeHandler: => Any): ScalaJDoubleStream = + inline def onClose(closeHandler: => Any): ScalaJDoubleStream = new ScalaJDoubleStream(jStream.onClose(jRunnable(closeHandler))) def parallel: ScalaJDoubleStream = @@ -28,10 +28,10 @@ final class ScalaJDoubleStream(private val jStream: JDoubleStream) extends AnyVa def unordered: ScalaJDoubleStream = new ScalaJDoubleStream(jStream.unordered()) - def allMatch(predicate: Double => Boolean): Boolean = + inline def allMatch(inline predicate: Double => Boolean): Boolean = jStream.allMatch(jDoublePredicate(predicate)) - def anyMatch(predicate: Double => Boolean): Boolean = + inline def anyMatch(inline predicate: Double => Boolean): Boolean = jStream.anyMatch(jDoublePredicate(predicate)) def average: Option[Double] = @@ -40,7 +40,7 @@ final class ScalaJDoubleStream(private val jStream: JDoubleStream) extends AnyVa def boxed: ScalaJStream[Double] = new ScalaJStream(jStream.boxed.asInstanceOf[JStream[Double]]) - def collect[R](supplier: => R)(accumulator: (R, Double) => Any, combiner: (R, R) => Any): R = + inline def collect[R](supplier: => R)(inline accumulator: (R, Double) => Any, inline combiner: (R, R) => Any): R = jStream.collect(jSupplier(supplier), jObjDoubleConsumer(accumulator), jBiConsumer(combiner)) def count: Long = @@ -49,7 +49,7 @@ final class ScalaJDoubleStream(private val jStream: JDoubleStream) extends AnyVa def distinct: ScalaJDoubleStream = new ScalaJDoubleStream(jStream.distinct) - def filter(predicate: Double => Boolean): ScalaJDoubleStream = + inline def filter(inline predicate: Double => Boolean): ScalaJDoubleStream = new ScalaJDoubleStream(jStream.filter(jDoublePredicate(predicate))) def findAny: Option[Double] = @@ -58,28 +58,28 @@ final class ScalaJDoubleStream(private val jStream: JDoubleStream) extends AnyVa def findFirst: Option[Double] = jStream.findFirst.asScala - def flatMap(mapper: Double => ScalaJDoubleStream): ScalaJDoubleStream = + inline def flatMap(inline mapper: Double => ScalaJDoubleStream): ScalaJDoubleStream = new ScalaJDoubleStream(jStream.flatMap(jDoubleFunction(d => mapper(d).jStream))) - def forEach(action: Double => Any): Unit = + inline def forEach(inline action: Double => Any): Unit = jStream.forEach(jDoubleConsumer(action)) - def forEachOrdered(action: Double => Any): Unit = + inline def forEachOrdered(inline action: Double => Any): Unit = jStream.forEachOrdered(jDoubleConsumer(action)) def limit(maxSize: Long): ScalaJDoubleStream = new ScalaJDoubleStream(jStream.limit(maxSize)) - def map(mapper: Double => Double): ScalaJDoubleStream = + inline def map(inline mapper: Double => Double): ScalaJDoubleStream = new ScalaJDoubleStream(jStream.map(jDoubleUnaryOperator(mapper))) - def mapToInt(mapper: Double => Int): ScalaJIntStream = + inline def mapToInt(inline mapper: Double => Int): ScalaJIntStream = new ScalaJIntStream(jStream.mapToInt(jDoubleToIntFunction(mapper))) - def mapToLong(mapper: Double => Long): ScalaJLongStream = + inline def mapToLong(inline mapper: Double => Long): ScalaJLongStream = new ScalaJLongStream(jStream.mapToLong(jDoubleToLongFunction(mapper))) - def mapToObj[U](mapper: Double => U): ScalaJStream[U] = + inline def mapToObj[U](inline mapper: Double => U): ScalaJStream[U] = new ScalaJStream(jStream.mapToObj(jDoubleFunction(mapper))) def max: Option[Double] = @@ -88,16 +88,16 @@ final class ScalaJDoubleStream(private val jStream: JDoubleStream) extends AnyVa def min: Option[Double] = jStream.min.asScala - def noneMatch(predicate: Double => Boolean): Boolean = + inline def noneMatch(inline predicate: Double => Boolean): Boolean = jStream.noneMatch(jDoublePredicate(predicate)) - def peek(action: Double => Any): ScalaJDoubleStream = + inline def peek(inline action: Double => Any): ScalaJDoubleStream = new ScalaJDoubleStream(jStream.peek(jDoubleConsumer(action))) - def reduce(identity: Double)(op: (Double, Double) => Double): Double = + inline def reduce(identity: Double)(inline op: (Double, Double) => Double): Double = jStream.reduce(identity, jDoubleBinaryOperator(op)) - def reduce(op: (Double, Double) => Double): Option[Double] = + inline def reduce(inline op: (Double, Double) => Double): Option[Double] = jStream.reduce(jDoubleBinaryOperator(op)).asScala def skip(n: Long): ScalaJDoubleStream = diff --git a/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJIntStream.scala b/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJIntStream.scala index 96217845f..cf7d6bc55 100644 --- a/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJIntStream.scala +++ b/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJIntStream.scala @@ -16,7 +16,7 @@ final class ScalaJIntStream(private val jStream: JIntStream) extends AnyVal { def iterator: Iterator[Int] = jStream.iterator().asInstanceOf[JIterator[Int]].asScala - def onClose(closeHandler: => Any): ScalaJIntStream = + inline def onClose(closeHandler: => Any): ScalaJIntStream = new ScalaJIntStream(jStream.onClose(jRunnable(closeHandler))) def parallel: ScalaJIntStream = @@ -28,10 +28,10 @@ final class ScalaJIntStream(private val jStream: JIntStream) extends AnyVal { def unordered: ScalaJIntStream = new ScalaJIntStream(jStream.unordered()) - def allMatch(predicate: Int => Boolean): Boolean = + inline def allMatch(inline predicate: Int => Boolean): Boolean = jStream.allMatch(jIntPredicate(predicate)) - def anyMatch(predicate: Int => Boolean): Boolean = + inline def anyMatch(inline predicate: Int => Boolean): Boolean = jStream.anyMatch(jIntPredicate(predicate)) def asDoubleStream: ScalaJDoubleStream = @@ -46,7 +46,7 @@ final class ScalaJIntStream(private val jStream: JIntStream) extends AnyVal { def boxed: ScalaJStream[Int] = new ScalaJStream(jStream.boxed.asInstanceOf[JStream[Int]]) - def collect[R](supplier: => R)(accumulator: (R, Int) => Any, combiner: (R, R) => Any): R = + inline def collect[R](supplier: => R)(inline accumulator: (R, Int) => Any, inline combiner: (R, R) => Any): R = jStream.collect(jSupplier(supplier), jObjIntConsumer(accumulator), jBiConsumer(combiner)) def count: Long = @@ -55,7 +55,7 @@ final class ScalaJIntStream(private val jStream: JIntStream) extends AnyVal { def distinct: ScalaJIntStream = new ScalaJIntStream(jStream.distinct) - def filter(predicate: Int => Boolean): ScalaJIntStream = + inline def filter(inline predicate: Int => Boolean): ScalaJIntStream = new ScalaJIntStream(jStream.filter(jIntPredicate(predicate))) def findAny: Option[Int] = @@ -64,28 +64,28 @@ final class ScalaJIntStream(private val jStream: JIntStream) extends AnyVal { def findFirst: Option[Int] = jStream.findFirst.asScala - def flatMap(mapper: Int => ScalaJIntStream): ScalaJIntStream = + inline def flatMap(inline mapper: Int => ScalaJIntStream): ScalaJIntStream = new ScalaJIntStream(jStream.flatMap(jIntFunction(d => mapper(d).jStream))) - def forEach(action: Int => Any): Unit = + inline def forEach(inline action: Int => Any): Unit = jStream.forEach(jIntConsumer(action)) - def forEachOrdered(action: Int => Any): Unit = + inline def forEachOrdered(inline action: Int => Any): Unit = jStream.forEachOrdered(jIntConsumer(action)) def limit(maxSize: Long): ScalaJIntStream = new ScalaJIntStream(jStream.limit(maxSize)) - def map(mapper: Int => Int): ScalaJIntStream = + inline def map(inline mapper: Int => Int): ScalaJIntStream = new ScalaJIntStream(jStream.map(jIntUnaryOperator(mapper))) - def mapToDouble(mapper: Int => Double): ScalaJDoubleStream = + inline def mapToDouble(inline mapper: Int => Double): ScalaJDoubleStream = new ScalaJDoubleStream(jStream.mapToDouble(jIntToDoubleFunction(mapper))) - def mapToLong(mapper: Int => Long): ScalaJLongStream = + inline def mapToLong(inline mapper: Int => Long): ScalaJLongStream = new ScalaJLongStream(jStream.mapToLong(jIntToLongFunction(mapper))) - def mapToObj[U](mapper: Int => U): ScalaJStream[U] = + inline def mapToObj[U](inline mapper: Int => U): ScalaJStream[U] = new ScalaJStream(jStream.mapToObj(jIntFunction(mapper))) def max: Option[Int] = @@ -94,16 +94,16 @@ final class ScalaJIntStream(private val jStream: JIntStream) extends AnyVal { def min: Option[Int] = jStream.min.asScala - def noneMatch(predicate: Int => Boolean): Boolean = + inline def noneMatch(inline predicate: Int => Boolean): Boolean = jStream.noneMatch(jIntPredicate(predicate)) - def peek(action: Int => Any): ScalaJIntStream = + inline def peek(inline action: Int => Any): ScalaJIntStream = new ScalaJIntStream(jStream.peek(jIntConsumer(action))) - def reduce(identity: Int)(op: (Int, Int) => Int): Int = + inline def reduce(identity: Int)(inline op: (Int, Int) => Int): Int = jStream.reduce(identity, jIntBinaryOperator(op)) - def reduce(op: (Int, Int) => Int): Option[Int] = + inline def reduce(inline op: (Int, Int) => Int): Option[Int] = jStream.reduce(jIntBinaryOperator(op)).asScala def skip(n: Long): ScalaJIntStream = diff --git a/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJLongStream.scala b/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJLongStream.scala index 1c06aff82..c5db449a5 100644 --- a/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJLongStream.scala +++ b/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJLongStream.scala @@ -16,7 +16,7 @@ final class ScalaJLongStream(private val jStream: JLongStream) extends AnyVal { def iterator: Iterator[Long] = jStream.iterator().asInstanceOf[JIterator[Long]].asScala - def onClose(closeHandler: => Any): ScalaJLongStream = + inline def onClose(closeHandler: => Any): ScalaJLongStream = new ScalaJLongStream(jStream.onClose(jRunnable(closeHandler))) def parallel: ScalaJLongStream = @@ -28,10 +28,10 @@ final class ScalaJLongStream(private val jStream: JLongStream) extends AnyVal { def unordered: ScalaJLongStream = new ScalaJLongStream(jStream.unordered()) - def allMatch(predicate: Long => Boolean): Boolean = + inline def allMatch(inline predicate: Long => Boolean): Boolean = jStream.allMatch(jLongPredicate(predicate)) - def anyMatch(predicate: Long => Boolean): Boolean = + inline def anyMatch(inline predicate: Long => Boolean): Boolean = jStream.anyMatch(jLongPredicate(predicate)) def asDoubleStream: ScalaJDoubleStream = @@ -43,7 +43,7 @@ final class ScalaJLongStream(private val jStream: JLongStream) extends AnyVal { def boxed: ScalaJStream[Long] = new ScalaJStream(jStream.boxed.asInstanceOf[JStream[Long]]) - def collect[R](supplier: => R)(accumulator: (R, Long) => Any, combiner: (R, R) => Any): R = + inline def collect[R](supplier: => R)(inline accumulator: (R, Long) => Any, inline combiner: (R, R) => Any): R = jStream.collect(jSupplier(supplier), jObjLongConsumer(accumulator), jBiConsumer(combiner)) def count: Long = @@ -52,7 +52,7 @@ final class ScalaJLongStream(private val jStream: JLongStream) extends AnyVal { def distinct: ScalaJLongStream = new ScalaJLongStream(jStream.distinct) - def filter(predicate: Long => Boolean): ScalaJLongStream = + inline def filter(inline predicate: Long => Boolean): ScalaJLongStream = new ScalaJLongStream(jStream.filter(jLongPredicate(predicate))) def findAny: Option[Long] = @@ -61,28 +61,28 @@ final class ScalaJLongStream(private val jStream: JLongStream) extends AnyVal { def findFirst: Option[Long] = jStream.findFirst.asScala - def flatMap(mapper: Long => ScalaJLongStream): ScalaJLongStream = + inline def flatMap(inline mapper: Long => ScalaJLongStream): ScalaJLongStream = new ScalaJLongStream(jStream.flatMap(jLongFunction(d => mapper(d).jStream))) - def forEach(action: Long => Any): Unit = + inline def forEach(inline action: Long => Any): Unit = jStream.forEach(jLongConsumer(action)) - def forEachOrdered(action: Long => Any): Unit = + inline def forEachOrdered(inline action: Long => Any): Unit = jStream.forEachOrdered(jLongConsumer(action)) def limit(maxSize: Long): ScalaJLongStream = new ScalaJLongStream(jStream.limit(maxSize)) - def map(mapper: Long => Long): ScalaJLongStream = + inline def map(inline mapper: Long => Long): ScalaJLongStream = new ScalaJLongStream(jStream.map(jLongUnaryOperator(mapper))) - def mapToDouble(mapper: Long => Double): ScalaJDoubleStream = + inline def mapToDouble(inline mapper: Long => Double): ScalaJDoubleStream = new ScalaJDoubleStream(jStream.mapToDouble(jLongToDoubleFunction(mapper))) - def mapToInt(mapper: Long => Int): ScalaJIntStream = + inline def mapToInt(inline mapper: Long => Int): ScalaJIntStream = new ScalaJIntStream(jStream.mapToInt(jLongToIntFunction(mapper))) - def mapToObj[U](mapper: Long => U): ScalaJStream[U] = + inline def mapToObj[U](inline mapper: Long => U): ScalaJStream[U] = new ScalaJStream(jStream.mapToObj(jLongFunction(mapper))) def max: Option[Long] = @@ -91,16 +91,16 @@ final class ScalaJLongStream(private val jStream: JLongStream) extends AnyVal { def min: Option[Long] = jStream.min.asScala - def noneMatch(predicate: Long => Boolean): Boolean = + inline def noneMatch(inline predicate: Long => Boolean): Boolean = jStream.noneMatch(jLongPredicate(predicate)) - def peek(action: Long => Any): ScalaJLongStream = + inline def peek(inline action: Long => Any): ScalaJLongStream = new ScalaJLongStream(jStream.peek(jLongConsumer(action))) - def reduce(identity: Long)(op: (Long, Long) => Long): Long = + inline def reduce(identity: Long)(inline op: (Long, Long) => Long): Long = jStream.reduce(identity, jLongBinaryOperator(op)) - def reduce(op: (Long, Long) => Long): Option[Long] = + inline def reduce(inline op: (Long, Long) => Long): Option[Long] = jStream.reduce(jLongBinaryOperator(op)).asScala def skip(n: Long): ScalaJLongStream = diff --git a/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJStream.scala b/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJStream.scala index 1cb3cf3b0..9162b7cb4 100644 --- a/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJStream.scala +++ b/core/jvm/src/main/scala/com/avsystem/commons/jiop/ScalaJStream.scala @@ -17,7 +17,7 @@ final class ScalaJStream[+A](private val jStream: JStream[A @uV]) extends AnyVal def parallel: ScalaJStream[A] = new ScalaJStream(jStream.parallel()) - def onClose(closeHandler: => Any): ScalaJStream[A] = + inline def onClose(closeHandler: => Any): ScalaJStream[A] = new ScalaJStream(jStream.onClose(jRunnable(closeHandler))) def sequential: ScalaJStream[A] = @@ -38,16 +38,16 @@ final class ScalaJStream[+A](private val jStream: JStream[A @uV]) extends AnyVal def asLongStream(implicit ev: A <:< Long): ScalaJLongStream = mapToLong(ev) - def allMatch(predicate: A => Boolean): Boolean = + inline def allMatch(inline predicate: A => Boolean): Boolean = jStream.allMatch(jPredicate(predicate)) - def anyMatch(predicate: A => Boolean): Boolean = + inline def anyMatch(inline predicate: A => Boolean): Boolean = jStream.anyMatch(jPredicate(predicate)) def collect[R, B](collector: JCollector[_ >: A, B, R]): R = jStream.collect(collector) - def collect[R](supplier: => R)(accumulator: (R, A) => Any, combiner: (R, R) => Any): R = + inline def collect[R](supplier: => R)(inline accumulator: (R, A) => Any, inline combiner: (R, R) => Any): R = jStream.collect(jSupplier(supplier), jBiConsumer(accumulator), jBiConsumer(combiner)) def count: Long = @@ -56,7 +56,7 @@ final class ScalaJStream[+A](private val jStream: JStream[A @uV]) extends AnyVal def distinct: ScalaJStream[A] = new ScalaJStream(jStream.distinct()) - def filter(predicate: A => Boolean): ScalaJStream[A] = + inline def filter(inline predicate: A => Boolean): ScalaJStream[A] = new ScalaJStream(jStream.filter(jPredicate(predicate))) def findAny: Option[A] = @@ -65,58 +65,58 @@ final class ScalaJStream[+A](private val jStream: JStream[A @uV]) extends AnyVal def findFirst: Option[A] = jStream.findFirst().asScala - def flatMap[R](mapper: A => ScalaJStream[R]): ScalaJStream[R] = + inline def flatMap[R](inline mapper: A => ScalaJStream[R]): ScalaJStream[R] = new ScalaJStream(jStream.flatMap(jFunction(t => mapper(t).jStream))) - def flatMapToDouble(mapper: A => ScalaJDoubleStream): ScalaJDoubleStream = + inline def flatMapToDouble(inline mapper: A => ScalaJDoubleStream): ScalaJDoubleStream = new ScalaJDoubleStream(jStream.flatMapToDouble(jFunction(t => mapper(t).asJava))) - def flatMapToInt(mapper: A => ScalaJIntStream): ScalaJIntStream = + inline def flatMapToInt(inline mapper: A => ScalaJIntStream): ScalaJIntStream = new ScalaJIntStream(jStream.flatMapToInt(jFunction(t => mapper(t).asJava))) - def flatMapToLong(mapper: A => ScalaJLongStream): ScalaJLongStream = + inline def flatMapToLong(inline mapper: A => ScalaJLongStream): ScalaJLongStream = new ScalaJLongStream(jStream.flatMapToLong(jFunction(t => mapper(t).asJava))) - def forEach(action: A => Any): Unit = + inline def forEach(inline action: A => Any): Unit = jStream.forEach(jConsumer(action)) - def forEachOrdered(action: A => Any): Unit = + inline def forEachOrdered(inline action: A => Any): Unit = jStream.forEachOrdered(jConsumer(action)) def limit(maxSize: Long): ScalaJStream[A] = new ScalaJStream(jStream.limit(maxSize)) - def map[R](mapper: A => R): ScalaJStream[R] = + inline def map[R](inline mapper: A => R): ScalaJStream[R] = new ScalaJStream(jStream.map[R](jFunction(mapper))) - def mapToDouble(mapper: A => Double): ScalaJDoubleStream = + inline def mapToDouble(inline mapper: A => Double): ScalaJDoubleStream = new ScalaJDoubleStream(jStream.mapToDouble(jToDoubleFunction(mapper))) - def mapToInt(mapper: A => Int): ScalaJIntStream = + inline def mapToInt(inline mapper: A => Int): ScalaJIntStream = new ScalaJIntStream(jStream.mapToInt(jToIntFunction(mapper))) - def mapToLong(mapper: A => Long): ScalaJLongStream = + inline def mapToLong(inline mapper: A => Long): ScalaJLongStream = new ScalaJLongStream(jStream.mapToLong(jToLongFunction(mapper))) - def max(comparator: (A, A) => Int): Option[A] = + inline def max(inline comparator: (A, A) => Int): Option[A] = jStream.max(jComparator(comparator)).asScala - def min(comparator: (A, A) => Int): Option[A] = + inline def min(inline comparator: (A, A) => Int): Option[A] = jStream.min(jComparator(comparator)).asScala - def noneMatch(predicate: A => Boolean): Boolean = + inline def noneMatch(inline predicate: A => Boolean): Boolean = jStream.noneMatch(jPredicate(predicate)) - def peek(action: A => Any): ScalaJStream[A] = + inline def peek(inline action: A => Any): ScalaJStream[A] = new ScalaJStream(jStream.peek(jConsumer(action))) - def reduce[B >: A](accumulator: (B, B) => B): Option[B] = + inline def reduce[B >: A](inline accumulator: (B, B) => B): Option[B] = jStream.asInstanceOf[JStream[B]].reduce(jBinaryOperator(accumulator)).asScala - def reduce[B >: A](identity: B)(accumulator: (B, B) => B): B = + inline def reduce[B >: A](identity: B)(inline accumulator: (B, B) => B): B = jStream.asInstanceOf[JStream[B]].reduce(identity, jBinaryOperator(accumulator)) - def reduce[U](identity: U)(accumulator: (U, A) => U, combiner: (U, U) => U): U = + inline def reduce[U](identity: U)(inline accumulator: (U, A) => U, inline combiner: (U, U) => U): U = jStream.reduce(identity, jBiFunction(accumulator), jBinaryOperator(combiner)) def skip(n: Long): ScalaJStream[A] = @@ -125,7 +125,7 @@ final class ScalaJStream[+A](private val jStream: JStream[A @uV]) extends AnyVal def sorted: ScalaJStream[A] = new ScalaJStream(jStream.sorted) - def sorted(comparator: (A, A) => Int): ScalaJStream[A] = + inline def sorted(inline comparator: (A, A) => Int): ScalaJStream[A] = new ScalaJStream(jStream.sorted(jComparator(comparator))) def toArray[B >: A <: AnyRef: ClassTag]: Array[B] = From deb43bf888b16fa1dc9d0ded417a9bec72f26341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 1 Jun 2026 21:51:00 +0200 Subject: [PATCH 3/5] =?UTF-8?q?docs(migration):=20record=20@inline=20def?= =?UTF-8?q?=20=E2=86=92=20inline=20def=20+=20Function-param=20sweep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- MIGRATION.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index b99395701..bffe141ba 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -54,6 +54,51 @@ the bottom of this file. Restoration ships incrementally per feature area. compiles). - `enum` was renamed to `e` at one call site in `GenKeyCodec` (`enum` is reserved in Scala 3). - `@targetName` annotation added to `CloseableIterator` overloaded methods. +- `@inline def` → `inline def` across Opt family (`Opt`, `NOpt`, `OptArg`, `OptRef` — 117 sites). Scala 3 `inline def` is + implicitly `final` and cannot be overridden — a source-compat break for downstream subclassers. Mitigation: targets + are value classes (final by construction); zero impact on downstream subclassing patterns. Three trivial accessors + (`isEmpty`, `isDefined`, `nonEmpty`, `get`) kept as plain `def` per Scala 3 inline edge case: pattern-match desugaring + (`case Opt(x) =>`) calls these and cannot inline at compiler-generated call sites. +- Whitelist preserved verbatim (JVM-optimizer-hint convention on tight-loop parser hot paths): `CborInput.bits` (1 + site), `JsonStringInput` (5 sites — `read`, `isNext`, `isNextDigit`, `advance`, nested `update`), `RPCFramework` (2 + sites — RPC module remains Scala 2.13-only per fork strategy). +- `@publicInBinary` annotation added on private constructors and `private[misc]` members referenced from `inline def` + bodies in `Opt`, `NOpt`, `OptArg`, `OptRef` (Scala 3 compiler requirement: "Private constructors used in inline + methods require @publicInBinary"). Binary-stable extension point; no source-compat impact on callers. +- Binary-compat: Scala 3 `inline def` emits no bytecode method. MiMa would flag if/when the scala-3 baseline is + released; currently MiMa is off (`mimaPreviousArtifacts := Set.empty`). +- `inline` scope tightened (per user directive 2026-06-01): `inline def` is now applied only to methods with at least one + `Function`/`PartialFunction`-typed parameter (e.g. `map[B](inline f: A => B)`, `collect(inline pf: PartialFunction[A, B])`, + `foreach[U](inline f: A => U)`, `fold(ifEmpty: => B)(inline f: A => B)`). Methods whose only parameters are values, + implicits, or by-name (`=> T`) are reverted to plain `def` — by-name already provides call-site substitution in + Scala 3 without the `inline def` overhead. Affected reverts: `boxed`/`boxedOrNull`/`unboxed`/`toOption`/`toOpt`/`toOptRef`/ + `toNOpt`/`toOptArg`/`orNull`/`flatten`/`contains`/`iterator`/`toList`/`zip`/`getOrElse`/`orElse`/`toRight`/`toLeft`/ + `forEmpty` in the Opt family; `Opt.LazyOptOps.unless`; `gSupplier` in GuavaInterop; `jBooleanSupplier`/`jDoubleSupplier`/ + `jIntSupplier`/`jLongSupplier`/`jSupplier` in JFunctionUtils; `onClose` in all four `ScalaJ*Stream` types. +- Redundant `inline` keyword stripped from by-name parameters across retained `inline def` methods (Scala 3: `inline x: => T` + is redundant — `=> T` already provides call-site splicing). Affected: `fold(ifEmpty: => B)`, `mapOr(ifEmpty: => B)` in + Opt family; `collect(supplier: => R)(…)` in all four `ScalaJ*Stream` types. +- `inline` extended to jiop adapters with Function-typed parameters: `JFunctionUtils` jXxx adapters (38 sites with + Function params; 5 Supplier adapters now plain `def`), `Java8CollectionUtils` (`jCollection.forEach/removeIf`, + `jMap.compute*`/`forEach`/`merge`/`replaceAll`), `GuavaInterop` gFunction/gPredicate (2 sites; gSupplier now plain + `def`), and `ScalaJ{Stream,IntStream,LongStream,DoubleStream}` pipeline ops (`filter`/`map`/`flatMap`/`forEach`/ + `reduce`/`collect`/`peek`/…). Exception: `ListenableFutureAsScala.{onComplete, transform, transformWith}` in + `GuavaInterop` override `scala.concurrent.Future` methods (which have non-`inline` params) and stay as plain `def` — + Scala 3 forbids overriding a non-inline parameter with an inline parameter. +- Trailing `; ()` removed from Consumer SAM lambda bodies in `JFunctionUtils` (`jBiConsumer`/`jConsumer`/`jDoubleConsumer`/ + `jIntConsumer`/`jLongConsumer`/`jObjDoubleConsumer`/`jObjIntConsumer`/`jObjLongConsumer`). Scala 3 SAM conversion to a + void-returning Java functional interface accepts a result expression of any type — explicit `()`-coercion is unnecessary + noise. +- By-name parameters reshaped to `inline` value parameters on `inline def` Opt-family forwarders where the body uses + the parameter exactly once in a straight-line `if/then/else` branch (no closure capture, no multi-evaluation, no + default value). Affected (19 sites across Opt/NOpt/OptArg/OptRef): `getOrElse(inline default: B)`, + `fold(inline ifEmpty: B)(inline f: ...)`, `mapOr(inline ifEmpty: B, inline f: ...)`, `orElse(inline alternative: Opt[B])`, + `toRight(inline left: X)`, `toLeft(inline right: X)`, `forEmpty(inline sideEffect: Unit)`. Rationale: with `inline def`, + an `inline B` parameter substitutes the expression literally at the call site — no `Function0` thunk allocation, no + by-name dispatch overhead. Strictly cheaper than `=> B` for single-use straight-line bodies. Call-site syntax is + identical (callers pass any `B`-typed expression). Suppliers (`gSupplier`, `jBooleanSupplier`, `jDoubleSupplier`, + `jIntSupplier`, `jLongSupplier`, `jSupplier`) and stream `onClose`/`collect(supplier:)` keep `=> T` — those by-name + params are wrapped inside `() => expr` closures (deferred-evaluation intent). ### mongo From 4d015ba33ca3b5410ddd8452a44e3bd21875e67e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 1 Jun 2026 22:12:56 +0200 Subject: [PATCH 4/5] perf(scala-3,core): inline closure-taking methods in SharedExtensions 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). --- .../avsystem/commons/SharedExtensions.scala | 161 +++++++++++------- 1 file changed, 96 insertions(+), 65 deletions(-) diff --git a/core/src/main/scala/com/avsystem/commons/SharedExtensions.scala b/core/src/main/scala/com/avsystem/commons/SharedExtensions.scala index 70c670615..06a4a26cc 100644 --- a/core/src/main/scala/com/avsystem/commons/SharedExtensions.scala +++ b/core/src/main/scala/com/avsystem/commons/SharedExtensions.scala @@ -65,18 +65,18 @@ object SharedExtensionsUtils extends SharedExtensions { * @example * {{{someVeryLongExpression() |> (v => if(condition(v)) something(v) else somethingElse(v))}}} */ - def |>[B](f: A => B): B = f(a) + inline def |>[B](inline f: A => B): B = f(a) - def applyIf[A0 >: A](predicate: A => Boolean)(f: A => A0): A0 = + inline def applyIf[A0 >: A](inline predicate: A => Boolean)(inline f: A => A0): A0 = if (predicate(a)) f(a) else a /** Explicit syntax to discard the value of a side-effecting expression. Useful when `-Ywarn-value-discard` compiler * option is enabled. */ @nowarn - def discard: Unit = () + inline def discard: Unit = () - def thenReturn[T](value: T): T = value + inline def thenReturn[T](inline value: T): T = value def option: Option[A] = Option(a) @@ -105,12 +105,12 @@ object SharedExtensionsUtils extends SharedExtensions { * } * }}} */ - def setup(code: A => Any): A = { + inline def setup(inline code: A => Any): A = { code(a) a } - def matchOpt[B](pf: PartialFunction[A, B]): Opt[B] = + inline def matchOpt[B](inline pf: PartialFunction[A, B]): Opt[B] = pf.applyOpt(a) /** To be used instead of normal `match` keyword in pattern matching in order to suppress non-exhaustive match @@ -123,7 +123,7 @@ object SharedExtensionsUtils extends SharedExtensions { * } * }}} */ - def uncheckedMatch[B](pf: PartialFunction[A, B]): B = + inline def uncheckedMatch[B](inline pf: PartialFunction[A, B]): B = pf.applyOrElse(a, (obj: A) => throw new MatchError(obj)) // TODO[scala3-port]: showAst (Scala 2 macro def) (L) @@ -151,23 +151,23 @@ object SharedExtensionsUtils extends SharedExtensions { } class LazyUniversalOps[A](private val a: () => A) extends AnyVal { - def evalFuture: Future[A] = FutureCompanionOps.eval(a()) + inline def evalFuture: Future[A] = FutureCompanionOps.eval(a()) - def evalTry: Try[A] = Try(a()) + inline def evalTry: Try[A] = Try(a()) - def optIf(condition: Boolean): Opt[A] = + inline def optIf(inline condition: Boolean): Opt[A] = if (condition) Opt(a()) else Opt.Empty - def optionIf(condition: Boolean): Option[A] = + inline def optionIf(inline condition: Boolean): Option[A] = if (condition) Some(a()) else None - def recoverFrom[T <: Throwable: ClassTag](fallbackValue: => A): A = + inline def recoverFrom[T <: Throwable: ClassTag](fallbackValue: => A): A = try a() catch { case _: T => fallbackValue } - def recoverToOpt[T <: Throwable: ClassTag]: Opt[A] = + inline def recoverToOpt[T <: Throwable: ClassTag]: Opt[A] = try Opt(a()) catch { case _: T => Opt.Empty @@ -233,7 +233,7 @@ object SharedExtensionsUtils extends SharedExtensions { } class IntOps(private val int: Int) extends AnyVal { - def times(code: => Any): Unit = { + inline def times(inline code: => Any): Unit = { var i = 0 while (i < int) { code @@ -243,22 +243,22 @@ object SharedExtensionsUtils extends SharedExtensions { } class FutureOps[A](private val fut: Future[A]) extends AnyVal { - def onCompleteNow[U](f: Try[A] => U): Unit = + inline def onCompleteNow[U](inline f: Try[A] => U): Unit = fut.onComplete(f)(RunNowEC) - def andThenNow[U](pf: PartialFunction[Try[A], U]): Future[A] = + inline def andThenNow[U](inline pf: PartialFunction[Try[A], U]): Future[A] = fut.andThen(pf)(RunNowEC) - def foreachNow[U](f: A => U): Unit = + inline def foreachNow[U](inline f: A => U): Unit = fut.foreach(f)(RunNowEC) - def transformNow[S](s: A => S, f: Throwable => Throwable): Future[S] = + inline def transformNow[S](inline s: A => S, inline f: Throwable => Throwable): Future[S] = fut.transform(s, f)(RunNowEC) - def transformNow[S](f: Try[A] => Try[S]): Future[S] = + inline def transformNow[S](inline f: Try[A] => Try[S]): Future[S] = fut.transform(f)(RunNowEC) - def transformWithNow[S](f: Try[A] => Future[S]): Future[S] = + inline def transformWithNow[S](inline f: Try[A] => Future[S]): Future[S] = fut.transformWith(f)(RunNowEC) def wrapToTry: Future[Try[A]] = @@ -266,27 +266,27 @@ object SharedExtensionsUtils extends SharedExtensions { /** Maps a `Future` using [[concurrent.RunNowEC RunNowEC]]. */ - def mapNow[B](f: A => B): Future[B] = + inline def mapNow[B](inline f: A => B): Future[B] = fut.map(f)(RunNowEC) /** FlatMaps a `Future` using [[concurrent.RunNowEC RunNowEC]]. */ - def flatMapNow[B](f: A => Future[B]): Future[B] = + inline def flatMapNow[B](inline f: A => Future[B]): Future[B] = fut.flatMap(f)(RunNowEC) - def filterNow(p: A => Boolean): Future[A] = + inline def filterNow(inline p: A => Boolean): Future[A] = fut.filter(p)(RunNowEC) - def collectNow[B](pf: PartialFunction[A, B]): Future[B] = + inline def collectNow[B](inline pf: PartialFunction[A, B]): Future[B] = fut.collect(pf)(RunNowEC) - def recoverNow[U >: A](pf: PartialFunction[Throwable, U]): Future[U] = + inline def recoverNow[U >: A](inline pf: PartialFunction[Throwable, U]): Future[U] = fut.recover(pf)(RunNowEC) - def recoverWithNow[B >: A](pf: PartialFunction[Throwable, Future[B]]): Future[B] = + inline def recoverWithNow[B >: A](inline pf: PartialFunction[Throwable, Future[B]]): Future[B] = fut.recoverWith(pf)(RunNowEC) - def zipWithNow[B, R](that: Future[B])(f: (A, B) => R): Future[R] = + inline def zipWithNow[B, R](that: Future[B])(inline f: (A, B) => R): Future[R] = fut.zipWith(that)(f)(RunNowEC) def toUnit: Future[Unit] = @@ -331,7 +331,7 @@ object SharedExtensionsUtils extends SharedExtensions { * evaluation throws an exception. This is very similar to `Future.apply` but evaluates the argument immediately, * without dispatching it to some `ExecutionContext`. */ - def eval[T](expr: => T): Future[T] = + inline def eval[T](inline expr: => T): Future[T] = try Future.successful(expr) catch { case NonFatal(cause) => Future.failed(cause) @@ -416,7 +416,7 @@ object SharedExtensionsUtils extends SharedExtensions { * @return the same option * @example {{{captionOpt.forEmpty(logger.warn("caption is empty")).foreach(setCaption)}}} */ - def forEmpty(sideEffect: => Unit): Option[A] = { + inline def forEmpty(inline sideEffect: => Unit): Option[A] = { if (option.isEmpty) { sideEffect } @@ -425,7 +425,7 @@ object SharedExtensionsUtils extends SharedExtensions { /** The same as `fold` but takes arguments in a single parameter list for better type inference. */ - def mapOr[B](ifEmpty: => B, f: A => B): B = + inline def mapOr[B](inline ifEmpty: => B, inline f: A => B): B = option.fold(ifEmpty)(f) } @@ -461,7 +461,7 @@ object SharedExtensionsUtils extends SharedExtensions { * Don't use .failed projection, because it unnecessarily creates Exception in case of Success, which is an * expensive operation. */ - def tapFailure(action: Throwable => Unit): Try[A] = tr match { + inline def tapFailure(inline action: Throwable => Unit): Try[A] = tr match { case Success(_) => tr case Failure(throwable) => try action(throwable) @@ -531,7 +531,7 @@ object SharedExtensionsUtils extends SharedExtensions { /** The same thing as `orElse` but with arguments flipped. Useful in situations where `orElse` would have to be * called on a partial function literal, which does not work well with type inference. */ - def unless(pre: PartialFunction[A, B]): PartialFunction[A, B] = pre orElse pf + inline def unless(inline pre: PartialFunction[A, B]): PartialFunction[A, B] = pre orElse pf def applyNOpt(a: A): NOpt[B] = pf.applyOrElse[A, Any](a, NoValueMarkerFunc) match { case NoValueMarker => NOpt.Empty @@ -543,18 +543,18 @@ object SharedExtensionsUtils extends SharedExtensions { case rawValue => Opt(rawValue.asInstanceOf[B]) } - def fold[C](a: A)(forEmpty: A => C, forNonEmpty: B => C): C = pf.applyOrElse[A, Any](a, NoValueMarkerFunc) match { - case NoValueMarker => forEmpty(a) - case rawValue => forNonEmpty(rawValue.asInstanceOf[B]) - } + inline def fold[C](a: A)(inline forEmpty: A => C, inline forNonEmpty: B => C): C = + pf.applyOrElse[A, Any](a, NoValueMarkerFunc) match { + case NoValueMarker => forEmpty(a) + case rawValue => forNonEmpty(rawValue.asInstanceOf[B]) + } } object PartialFunctionOps { - private object NoValueMarker - private final val NoValueMarkerFunc = (_: Any) => NoValueMarker + object NoValueMarker + final val NoValueMarkerFunc = (_: Any) => NoValueMarker } class IterableOnceOps[C[X] <: IterableOnce[X], A](private val coll: C[A]) extends AnyVal { - private def it: Iterator[A] = coll.iterator def toSized[To](fac: Factory[A, To], sizeHint: Int): To = { val b = fac.newBuilder @@ -563,70 +563,101 @@ object SharedExtensionsUtils extends SharedExtensions { b.result() } - def toMapBy[K](keyFun: A => K): Map[K, A] = + inline def toMapBy[K](inline keyFun: A => K): Map[K, A] = mkMap(keyFun, identity) - def mkMap[K, V](keyFun: A => K, valueFun: A => V): Map[K, V] = { + inline def mkMap[K, V](inline keyFun: A => K, inline valueFun: A => V): Map[K, V] = { val res = Map.newBuilder[K, V] - it.foreach { a => + coll.iterator.foreach { a => res += ((keyFun(a), valueFun(a))) } res.result() } - def groupToMap[K, V, To](keyFun: A => K, valueFun: A => V)(implicit bf: BuildFrom[C[A], V, To]): Map[K, To] = { + inline def groupToMap[K, V, To]( + inline keyFun: A => K, + inline valueFun: A => V, + )(implicit bf: BuildFrom[C[A], V, To] + ): Map[K, To] = { val builders = mutable.Map[K, mutable.Builder[V, To]]() - it.foreach { a => + coll.iterator.foreach { a => builders.getOrElseUpdate(keyFun(a), bf.newBuilder(coll)) += valueFun(a) } builders.iterator.map { case (k, v) => (k, v.result()) }.toMap } - def findOpt(p: A => Boolean): Opt[A] = it.find(p).toOpt + inline def findOpt(inline p: A => Boolean): Opt[A] = coll.iterator.find(p).toOpt - def flatCollect[B](f: PartialFunction[A, IterableOnce[B]])(implicit fac: Factory[B, C[B]]): C[B] = + inline def flatCollect[B](inline f: PartialFunction[A, IterableOnce[B]])(implicit fac: Factory[B, C[B]]): C[B] = coll.iterator.collect(f).flatten.to(fac) - def collectFirstOpt[B](pf: PartialFunction[A, B]): Opt[B] = it.collectFirst(pf).toOpt + inline def collectFirstOpt[B](inline pf: PartialFunction[A, B]): Opt[B] = coll.iterator.collectFirst(pf).toOpt - def reduceOpt[A1 >: A](op: (A1, A1) => A1): Opt[A1] = if (it.isEmpty) Opt.Empty else it.reduce(op).opt + inline def reduceOpt[A1 >: A](inline op: (A1, A1) => A1): Opt[A1] = { + val it = coll.iterator + if (it.isEmpty) Opt.Empty else it.reduce(op).opt + } - def reduceLeftOpt[B >: A](op: (B, A) => B): Opt[B] = if (it.isEmpty) Opt.Empty else it.reduceLeft(op).opt + inline def reduceLeftOpt[B >: A](inline op: (B, A) => B): Opt[B] = { + val it = coll.iterator + if (it.isEmpty) Opt.Empty else it.reduceLeft(op).opt + } - def reduceRightOpt[B >: A](op: (A, B) => B): Opt[B] = if (it.isEmpty) Opt.Empty else it.reduceRight(op).opt + inline def reduceRightOpt[B >: A](inline op: (A, B) => B): Opt[B] = { + val it = coll.iterator + if (it.isEmpty) Opt.Empty else it.reduceRight(op).opt + } - def maxOpt(implicit ord: Ordering[A]): Opt[A] = if (it.isEmpty) Opt.Empty else it.max.opt + def maxOpt(implicit ord: Ordering[A]): Opt[A] = { + val it = coll.iterator + if (it.isEmpty) Opt.Empty else it.max.opt + } - def maxOptBy[B: Ordering](f: A => B): Opt[A] = if (it.isEmpty) Opt.Empty else it.maxBy(f).opt + inline def maxOptBy[B: Ordering](inline f: A => B): Opt[A] = { + val it = coll.iterator + if (it.isEmpty) Opt.Empty else it.maxBy(f).opt + } - def minOpt(implicit ord: Ordering[A]): Opt[A] = if (it.isEmpty) Opt.Empty else it.min.opt + def minOpt(implicit ord: Ordering[A]): Opt[A] = { + val it = coll.iterator + if (it.isEmpty) Opt.Empty else it.min.opt + } - def minOptBy[B: Ordering](f: A => B): Opt[A] = if (it.isEmpty) Opt.Empty else it.minBy(f).opt + inline def minOptBy[B: Ordering](inline f: A => B): Opt[A] = { + val it = coll.iterator + if (it.isEmpty) Opt.Empty else it.minBy(f).opt + } def indexOfOpt(elem: A): Opt[Int] = coll.iterator.indexOf(elem).opt.filter(_ != -1) - def indexWhereOpt(p: A => Boolean): Opt[Int] = coll.iterator.indexWhere(p).opt.filter(_ != -1) + inline def indexWhereOpt(inline p: A => Boolean): Opt[Int] = coll.iterator.indexWhere(p).opt.filter(_ != -1) - def mkStringOr(start: String, sep: String, end: String, default: String): String = + def mkStringOr(start: String, sep: String, end: String, default: String): String = { + val it = coll.iterator if (it.nonEmpty) it.mkString(start, sep, end) else default + } - def mkStringOr(sep: String, default: String): String = + def mkStringOr(sep: String, default: String): String = { + val it = coll.iterator if (it.nonEmpty) it.mkString(sep) else default + } def mkStringOrEmpty(start: String, sep: String, end: String): String = mkStringOr(start, sep, end, "") - def asyncFoldLeft[B](zero: Future[B])(fun: (B, A) => Future[B])(implicit ec: ExecutionContext): Future[B] = - it.foldLeft(zero)((fb, a) => fb.flatMap(b => fun(b, a))) + inline def asyncFoldLeft[B](zero: Future[B])(inline fun: (B, A) => Future[B])(implicit ec: ExecutionContext) + : Future[B] = + coll.iterator.foldLeft(zero)((fb, a) => fb.flatMap(b => fun(b, a))) - def asyncFoldRight[B](zero: Future[B])(fun: (A, B) => Future[B])(implicit ec: ExecutionContext): Future[B] = - it.foldRight(zero)((a, fb) => fb.flatMap(b => fun(a, b))) + inline def asyncFoldRight[B](zero: Future[B])(inline fun: (A, B) => Future[B])(implicit ec: ExecutionContext) + : Future[B] = + coll.iterator.foldRight(zero)((a, fb) => fb.flatMap(b => fun(a, b))) - def asyncForeach(fun: A => Future[Unit])(implicit ec: ExecutionContext): Future[Unit] = - it.foldLeft[Future[Unit]](Future.unit)((fu, a) => fu.flatMap(_ => fun(a))) + inline def asyncForeach(inline fun: A => Future[Unit])(implicit ec: ExecutionContext): Future[Unit] = + coll.iterator.foldLeft[Future[Unit]](Future.unit)((fu, a) => fu.flatMap(_ => fun(a))) - def partitionEither[L, R]( - fun: A => Either[L, R] + inline def partitionEither[L, R]( + inline fun: A => Either[L, R] )(implicit facL: Factory[L, C[L]], facR: Factory[R, C[R]], From 15d0c4c313622129d54c0e2264248dfa9b8e5f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 1 Jun 2026 22:26:38 +0200 Subject: [PATCH 5/5] perf(scala-3,core): inline closure-taking methods in TaskExtensions / 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. --- .../commons/concurrent/ObservableExtensions.scala | 8 ++++---- .../commons/concurrent/TaskExtensions.scala | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/com/avsystem/commons/concurrent/ObservableExtensions.scala b/core/src/main/scala/com/avsystem/commons/concurrent/ObservableExtensions.scala index 5fc36ae91..ecfd3cec6 100644 --- a/core/src/main/scala/com/avsystem/commons/concurrent/ObservableExtensions.scala +++ b/core/src/main/scala/com/avsystem/commons/concurrent/ObservableExtensions.scala @@ -24,7 +24,7 @@ object ObservableExtensions extends ObservableExtensions { /** Returns a [[monix.eval.Task Task]] which emits the first non-null item for which the predicate holds. */ - def findOptL(p: T => Boolean): Task[Opt[T]] = obs.findL(e => e != null && p(e)).map(_.toOpt) + inline def findOptL(inline p: T => Boolean): Task[Opt[T]] = obs.findL(e => e != null && p(e)).map(_.toOpt) /** Suppress the duplicate elements emitted by the source Observable. * @@ -37,7 +37,7 @@ object ObservableExtensions extends ObservableExtensions { * * WARNING: this requires unbounded buffering. */ - def distinctBy[K](key: T => K): Observable[T] = + inline def distinctBy[K](inline key: T => K): Observable[T] = obs .scan((MSet.empty[K], Opt.empty[T])) { case ((seenElems, _), elem) => (seenElems, elem.optIf(seenElems.add(key(elem)))) @@ -61,7 +61,7 @@ object ObservableExtensions extends ObservableExtensions { * * WARNING: this requires unbounded buffering. */ - def sortedByL[R](f: T => R)(implicit ord: Ordering[R], ct: ClassTag[T]): Task[ISeq[T]] = + inline def sortedByL[R](inline f: T => R)(implicit ord: Ordering[R], ct: ClassTag[T]): Task[ISeq[T]] = sortedL(ord.on(f), ct) /** Given a collection factory `factory`, returns a [[monix.eval.Task Task]] that upon evaluation will collect all @@ -84,7 +84,7 @@ object ObservableExtensions extends ObservableExtensions { * * WARNING: for infinite streams the process will eventually blow up with an out of memory error. */ - def mkMapL[K, V](keyFun: T => K, valueFun: T => V): Task[Map[K, V]] = + inline def mkMapL[K, V](inline keyFun: T => K, inline valueFun: T => V): Task[Map[K, V]] = obs.map(v => (keyFun(v), valueFun(v))).toL(Map) } } diff --git a/core/src/main/scala/com/avsystem/commons/concurrent/TaskExtensions.scala b/core/src/main/scala/com/avsystem/commons/concurrent/TaskExtensions.scala index 7e32cd105..a25f8bb76 100644 --- a/core/src/main/scala/com/avsystem/commons/concurrent/TaskExtensions.scala +++ b/core/src/main/scala/com/avsystem/commons/concurrent/TaskExtensions.scala @@ -21,17 +21,17 @@ object TaskExtensions extends TaskExtensions { /** Similar to [[Task.timeoutWith]] but exception instance is created lazily (for performance) */ - def lazyTimeout(after: FiniteDuration, msg: => String): Task[T] = + inline def lazyTimeout(after: FiniteDuration, inline msg: => String): Task[T] = task.timeoutTo(after, Task.defer(Task.raiseError(new TimeoutException(msg)))) /** Similar to [[Task.tapEval]], accepts simple consumer function as an argument */ - def tapL(f: T => Unit): Task[T] = + inline def tapL(inline f: T => Unit): Task[T] = task.map(_.setup(f)) /** Similar to [[Task.tapError]], accepts [[PartialFunction]] as an argument */ - def tapErrorL[B](f: PartialFunction[Throwable, B]): Task[T] = + inline def tapErrorL[B](inline f: PartialFunction[Throwable, B]): Task[T] = task.tapError(t => Task(f.applyOpt(t))) } @@ -41,7 +41,7 @@ object TaskExtensions extends TaskExtensions { /** A [[Task]] of [[Opt.Empty]] */ def optEmpty[A]: Task[Opt[A]] = Task.pure(Opt.Empty) - def traverseOpt[A, B](opt: Opt[A])(f: A => Task[B]): Task[Opt[B]] = + inline def traverseOpt[A, B](opt: Opt[A])(inline f: A => Task[B]): Task[Opt[B]] = opt.fold(Task.optEmpty[B])(a => f(a).map(_.opt)) def fromOpt[A](maybeTask: Opt[Task[A]]): Task[Opt[A]] = maybeTask match { @@ -49,16 +49,16 @@ object TaskExtensions extends TaskExtensions { case Opt.Empty => Task.optEmpty } - def traverseMap[K, V, A, B](map: Map[K, V])(f: (K, V) => Task[(A, B)]): Task[Map[A, B]] = + inline def traverseMap[K, V, A, B](map: Map[K, V])(inline f: (K, V) => Task[(A, B)]): Task[Map[A, B]] = Observable.fromIterable(map).mapEval { case (key, value) => f(key, value) }.toL(Map) - def traverseMapValues[K, A, B](map: Map[K, A])(f: (K, A) => Task[B]): Task[Map[K, B]] = + inline def traverseMapValues[K, A, B](map: Map[K, A])(inline f: (K, A) => Task[B]): Task[Map[K, B]] = traverseMap(map) { case (key, value) => f(key, value).map(key -> _) } def currentTimestamp: Task[Timestamp] = Task.clock.realTime(TimeUnit.MILLISECONDS).map(Timestamp(_)) - def usingNow[T](useNow: Timestamp => Task[T]): Task[T] = + inline def usingNow[T](inline useNow: Timestamp => Task[T]): Task[T] = currentTimestamp.flatMap(useNow) } }