From 0cb7ee51b0a807e386df4de3247dc8154cea1083 Mon Sep 17 00:00:00 2001 From: "Taro L. Saito" Date: Tue, 2 Sep 2025 21:40:37 -0700 Subject: [PATCH 1/2] Remove deprecated registerTraitFactory method in Scala 3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed the deprecated `registerTraitFactory[A]` method and related code that was causing deprecation warnings in Scala 3. The method was marked as deprecated since version 23.9.1 with the message "Instantiating trait with DI is still experimental in Scala 3". Changes: - Removed deprecated `registerTraitFactory[A]` method from package.scala - Removed unused `registerTraitFactoryImpl[A]` and `shouldGenerateTrait[A]` functions - Removed calls to `registerTraitFactory[A]` from DesignImpl and RouterBase - Removed TraitFactoryTest.scala test file that was testing the deprecated functionality The Scala 2 implementation and related cache infrastructure remain intact for backward compatibility. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../scala-3/wvlet/airframe/DesignImpl.scala | 1 - .../main/scala-3/wvlet/airframe/package.scala | 102 ------------------ .../wvlet/airframe/di/TraitFactoryTest.scala | 58 ---------- .../airframe/http/router/RouterBase.scala | 2 - 4 files changed, 163 deletions(-) delete mode 100644 airframe-di/src/test/scala-3/wvlet/airframe/di/TraitFactoryTest.scala diff --git a/airframe-di/src/main/scala-3/wvlet/airframe/DesignImpl.scala b/airframe-di/src/main/scala-3/wvlet/airframe/DesignImpl.scala index 892e6df179..800378aa5b 100644 --- a/airframe-di/src/main/scala-3/wvlet/airframe/DesignImpl.scala +++ b/airframe-di/src/main/scala-3/wvlet/airframe/DesignImpl.scala @@ -11,7 +11,6 @@ import wvlet.log.LogSupport private[airframe] trait DesignImpl extends LogSupport: self: Design => inline def bind[A]: Binder[A] = - registerTraitFactory[A] new Binder(self, Surface.of[A], SourceCode()).asInstanceOf[Binder[A]] inline def remove[A]: Design = diff --git a/airframe-di/src/main/scala-3/wvlet/airframe/package.scala b/airframe-di/src/main/scala-3/wvlet/airframe/package.scala index a790aaf7a8..f8e0bb3816 100644 --- a/airframe-di/src/main/scala-3/wvlet/airframe/package.scala +++ b/airframe-di/src/main/scala-3/wvlet/airframe/package.scala @@ -42,107 +42,5 @@ val traitFactoryCache = new ConcurrentHashMap[Surface, Session => Any].asScala def getOrElseUpdateTraitFactoryCache(s: Surface, factory: Session => Any): Session => Any = traitFactoryCache.getOrElseUpdate(s, factory) -@deprecated("Instantiating trait with DI is still experimental in Scala 3", "23.9.1") -inline def registerTraitFactory[A]: Unit = { - // registerTraitFactoryImpl[A] -} -import scala.quoted.* -private def shouldGenerateTrait[A](using - tpe: Type[A], - q: Quotes -): Boolean = - import quotes.* - import quotes.reflect.* - - val t = TypeRepr.of[A] - val a = t.typeSymbol - - // Find the public default constructor that has no arguments - val hasPublicDefaultConstructor: Boolean = - val pc = a.primaryConstructor - pc.paramSymss.size == 1 && pc.paramSymss(0).size == 0 - - val hasAbstractMethods: Boolean = - a.methodMembers.exists { x => - x.flags.is(Flags.Method) && - (x.flags.is(Flags.Abstract) || x.flags.is(Flags.Deferred)) - } - val isTaggedType = a.fullName.startsWith("wvlet.airframe.surface.tag.") - val isSealedType = a.flags.is(Flags.Sealed) - val isStatic = a.flags.is(Flags.JavaStatic) - val isLocal = a.flags.is(Flags.Local) - val isTrait = a.flags.is(Flags.Trait) - - val shouldInstantiateTrait = - if !isStatic then - // = Non static type - // If X is non static type (= local class or trait), - // we need to instantiate it first in order to populate its $outer variables - - // We cannot instantiate path-dependent types - if a.fullName.contains("#") then false - else !hasAbstractMethods && hasPublicDefaultConstructor - else if a.isAbstractType then - // = Abstract type - // We cannot build abstract type X that has abstract methods, so bind[X].to[ConcreteType] - // needs to be found in the design - - // If there is no abstract methods, it might be a trait without any method - !hasAbstractMethods - else - // We cannot instantiate any trait or class without the default constructor - // So binding needs to be found in the Design. - hasPublicDefaultConstructor - - // Tagged type or sealed class binding should be found in Design - val result = isTrait && !isTaggedType && !isSealedType && shouldInstantiateTrait - - // println(s"${a.flags.show}, isStatic: ${isStatic}, isAbstract: ${a.isAbstractType}, isSealed: ${isSealedType} ${a.fullName}, " + - // s"has pstr: ${hasPublicDefaultConstructor} is tagged: ${isTaggedType}, has abstract method: ${hasAbstractMethods}") - - result - -@experimental def registerTraitFactoryImpl[A](using - tpe: Type[A], - q: Quotes -): quoted.Expr[Unit] = - import quotes.* - import quotes.reflect.* - - if !shouldGenerateTrait[A] then '{} - else - val name = "$anon" - val parents = List(TypeTree.of[Object], TypeTree.of[A], TypeTree.of[DISupport]) - - def decls(cls: Symbol): List[Symbol] = - List(Symbol.newMethod(cls, "session", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Session]))) - - val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None) - val sessionMethodSym = cls.declaredMethod("session").head - def sessionMethodDef(s: Term) = DefDef(sessionMethodSym, argss => Some(s)) - def newCls(s: Term) = - val clsDef = ClassDef(cls, parents, body = List(sessionMethodDef(s))) - val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[A]) - Block(List(clsDef), newCls) - - // Generate code { (s: Session) => new A with DISupport { def sesion: Session = s } } - val body = Lambda( - owner = Symbol.spliceOwner, - tpe = MethodType(List("s"))(_ => List(TypeRepr.of[Session]), _ => TypeRepr.of[A]), - rhsFn = (sym: Symbol, paramRefs: List[Tree]) => - val s = paramRefs.head.asExprOf[Session].asTerm - val fn = newCls(s) - fn.changeOwner(sym) - ) - // Register trait factory - // { (s: Session) => new A with DISupport { def session = s } } - '{ - wvlet.airframe.getOrElseUpdateTraitFactoryCache( - Surface.of[A], - ${ - body.asExprOf[Session => Any] - } - ) - } diff --git a/airframe-di/src/test/scala-3/wvlet/airframe/di/TraitFactoryTest.scala b/airframe-di/src/test/scala-3/wvlet/airframe/di/TraitFactoryTest.scala deleted file mode 100644 index b129e8743e..0000000000 --- a/airframe-di/src/test/scala-3/wvlet/airframe/di/TraitFactoryTest.scala +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed 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 wvlet.airframe.di - -import wvlet.airspec.AirSpec -import scala.language.experimental -import wvlet.airframe.* -import wvlet.airframe.surface.Surface - -object TraitFactoryTest extends AirSpec: - - trait A - - test("register trait factory") { - if isScala3 then pending("In Scala 3.3.1, creating a new trait instance via macro is still experimental") - registerTraitFactory[A] - traitFactoryCache.get(Surface.of[A]) shouldBe defined - } - - trait B: - def hello: Unit - - test("do not create trait factory for abstract classes") { - registerTraitFactory[B] - // --- - traitFactoryCache.get(Surface.of[B]) shouldBe empty - } - -// test("do not create trait for a local type") { -// trait Local -// registerTraitFactory[Local] -// traitFactoryCache.get(Surface.of[Local]) shouldBe empty -// } - - class C - - test("should not register trait factory for a simple class") { - registerTraitFactory[C] - traitFactoryCache.get(Surface.of[B]) shouldBe empty - } - - class D(i: Int) - - test("do not creat trait factroy for a class without any public constructor") { - registerTraitFactory[D] - traitFactoryCache.get(Surface.of[B]) shouldBe empty - } diff --git a/airframe-http/.jvm/src/main/scala-3/wvlet/airframe/http/router/RouterBase.scala b/airframe-http/.jvm/src/main/scala-3/wvlet/airframe/http/router/RouterBase.scala index 0910dd7262..869b922a13 100644 --- a/airframe-http/.jvm/src/main/scala-3/wvlet/airframe/http/router/RouterBase.scala +++ b/airframe-http/.jvm/src/main/scala-3/wvlet/airframe/http/router/RouterBase.scala @@ -43,11 +43,9 @@ private[router] object RouterObjectMacros: if TypeRepr.of[Controller] <:< TypeRepr.of[HttpFilterType] then '{ - wvlet.airframe.registerTraitFactory[Controller] Router(filterSurface = Some(Surface.of[Controller])) } else '{ - wvlet.airframe.registerTraitFactory[Controller] Router.empty.add[Controller] } From f5c2912c0fa8d22b5d508743e991c7c066ec2319 Mon Sep 17 00:00:00 2001 From: "Taro L. Saito" Date: Tue, 2 Sep 2025 21:48:28 -0700 Subject: [PATCH 2/2] Apply scalafmt formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove trailing blank lines from package.scala 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- airframe-di/src/main/scala-3/wvlet/airframe/package.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/airframe-di/src/main/scala-3/wvlet/airframe/package.scala b/airframe-di/src/main/scala-3/wvlet/airframe/package.scala index f8e0bb3816..bd8f8889a4 100644 --- a/airframe-di/src/main/scala-3/wvlet/airframe/package.scala +++ b/airframe-di/src/main/scala-3/wvlet/airframe/package.scala @@ -41,6 +41,3 @@ val traitFactoryCache = new ConcurrentHashMap[Surface, Session => Any].asScala def getOrElseUpdateTraitFactoryCache(s: Surface, factory: Session => Any): Session => Any = traitFactoryCache.getOrElseUpdate(s, factory) - - -