From 2d3a465941a5ebbc2c6e34cbc8db9aed4c87731f Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Fri, 9 Oct 2026 15:52:42 +0100 Subject: [PATCH] fix: resolve LogSource for getClass results on Scala 3 Motivation: On Scala 3, `Logging(system, getClass)` fails to compile with "Cannot find LogSource for Class[? <: Foo]". `getClass` returns a wildcard class type, which Scala 3 does not match against `LogSource.fromAnyClass[T]: LogSource[Class[T]]`. Scala 2.13 accepts it. Modification: - Make `object LogSource` extend a new `LogSourceCompat` trait. - The Scala 3 `LogSourceCompat` adds a lower priority `fromAnySubClass[T]: LogSource[Class[? <: T]]` implicit; the Scala 2.13 version is empty because Scala 2 reports that implicit as ambiguous with `fromAnyClass`. - `fromAnyClass` is unchanged; changing its signature instead fails MiMa with an IncompatibleSignatureProblem. - Add `LogSourceSpec`. Result: `Logging(system, getClass)` and `LogSource(getClass, system)` compile on Scala 3. Tests: - `actor-tests/Test/testOnly org.apache.pekko.event.LogSourceSpec` on Scala 3.3.8 without the fix: compile error "Cannot find LogSource for Class[? <: ...LogsWithGetClass]" - `actor-tests/Test/testOnly org.apache.pekko.event.LogSourceSpec org.apache.pekko.event.LoggerSpec` with the fix: passed on 2.13.18 and 3.3.8 - `actor/mimaReportBinaryIssues`: passed on 2.13.18 and 3.3.8 - scalafmt run on changed files References: Fixes #1538 --- .../apache/pekko/event/LogSourceSpec.scala | 56 +++++++++++++++++++ .../apache/pekko/event/LogSourceCompat.scala | 28 ++++++++++ .../apache/pekko/event/LogSourceCompat.scala | 33 +++++++++++ .../org/apache/pekko/event/Logging.scala | 2 +- 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 actor-tests/src/test/scala/org/apache/pekko/event/LogSourceSpec.scala create mode 100644 actor/src/main/scala-2.13/org/apache/pekko/event/LogSourceCompat.scala create mode 100644 actor/src/main/scala-3/org/apache/pekko/event/LogSourceCompat.scala diff --git a/actor-tests/src/test/scala/org/apache/pekko/event/LogSourceSpec.scala b/actor-tests/src/test/scala/org/apache/pekko/event/LogSourceSpec.scala new file mode 100644 index 00000000000..39aff56ff18 --- /dev/null +++ b/actor-tests/src/test/scala/org/apache/pekko/event/LogSourceSpec.scala @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.pekko.event + +import org.apache.pekko +import pekko.testkit.PekkoSpec + +object LogSourceSpec { + class LogsWithGetClass(system: pekko.actor.ActorSystem) { + // `getClass` returns `Class[? <: LogsWithGetClass]`, which did not compile on Scala 3 + val log: LoggingAdapter = Logging(system, getClass) + val logSource: (String, Class[?]) = LogSource(getClass, system) + } +} + +class LogSourceSpec extends PekkoSpec { + import LogSourceSpec._ + + "LogSource" must { + + "resolve an implicit LogSource for the result of getClass" in { + val subject = new LogsWithGetClass(system) + subject.logSource._1 should ===(s"LogSourceSpec$$LogsWithGetClass($system)") + subject.logSource._2 should ===(classOf[LogsWithGetClass]) + subject.log.isInstanceOf[BusLogging] should ===(true) + } + + "resolve an implicit LogSource for a class literal" in { + val logSource: (String, Class[?]) = LogSource(classOf[LogsWithGetClass]) + logSource._1 should ===("LogSourceSpec$LogsWithGetClass") + logSource._2 should ===(classOf[LogsWithGetClass]) + } + + "resolve an implicit LogSource for a wildcard class" in { + val c: Class[?] = classOf[LogsWithGetClass] + val logSource: (String, Class[?]) = LogSource(c) + logSource._1 should ===("LogSourceSpec$LogsWithGetClass") + logSource._2 should ===(classOf[LogsWithGetClass]) + } + } +} diff --git a/actor/src/main/scala-2.13/org/apache/pekko/event/LogSourceCompat.scala b/actor/src/main/scala-2.13/org/apache/pekko/event/LogSourceCompat.scala new file mode 100644 index 00000000000..0d58b06dd7b --- /dev/null +++ b/actor/src/main/scala-2.13/org/apache/pekko/event/LogSourceCompat.scala @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.pekko.event + +import org.apache.pekko.annotation.InternalApi + +/** + * INTERNAL API + * + * Scala 2 resolves [[LogSource.fromAnyClass]] for `getClass` results, so no extra implicits are needed. + */ +@InternalApi +private[event] trait LogSourceCompat diff --git a/actor/src/main/scala-3/org/apache/pekko/event/LogSourceCompat.scala b/actor/src/main/scala-3/org/apache/pekko/event/LogSourceCompat.scala new file mode 100644 index 00000000000..0fdd6d91a0a --- /dev/null +++ b/actor/src/main/scala-3/org/apache/pekko/event/LogSourceCompat.scala @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.pekko.event + +import org.apache.pekko.annotation.InternalApi + +/** + * INTERNAL API + * + * Lower priority implicits for [[LogSource]]. Scala 3 does not match `getClass`, which returns + * `Class[? <: T]`, against [[LogSource.fromAnyClass]]. This is not in the shared sources because + * Scala 2 reports it as ambiguous with `fromAnyClass`. + */ +@InternalApi +private[event] trait LogSourceCompat { + implicit def fromAnySubClass[T]: LogSource[Class[? <: T]] = + LogSource.fromClass.asInstanceOf[LogSource[Class[? <: T]]] +} diff --git a/actor/src/main/scala/org/apache/pekko/event/Logging.scala b/actor/src/main/scala/org/apache/pekko/event/Logging.scala index 1c4b0f86d52..5d14018dc3b 100644 --- a/actor/src/main/scala/org/apache/pekko/event/Logging.scala +++ b/actor/src/main/scala/org/apache/pekko/event/Logging.scala @@ -324,7 +324,7 @@ class DummyClassForStringSources *
  • anything else gives compile error unless implicit [[pekko.event.LogSource]] is in scope for it
  • * */ -object LogSource { +object LogSource extends LogSourceCompat { implicit val fromString: LogSource[String] = new LogSource[String] { def genString(s: String) = s override def genString(s: String, system: ActorSystem) = s + "(" + system + ")"