-
-
Notifications
You must be signed in to change notification settings - Fork 165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make uPickle derivation in Scala 3 call apply
method instead of new
#607
Changes from all commits
6af7c4e
12faf00
50ac93b
dd5106a
4519b78
98b1188
1b914e4
5e7785c
bc3c1ad
228f938
47a8347
3864e4d
9c69d3f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -199,6 +199,54 @@ def tagKeyImpl[T](using Quotes, Type[T])(thisOuter: Expr[upickle.core.Types with | |
case None => '{${thisOuter}.tagName} | ||
} | ||
|
||
inline def applyConstructor[T](params: Array[Any]): T = ${ applyConstructorImpl[T]('params) } | ||
def applyConstructorImpl[T](using quotes: Quotes, t0: Type[T])(params: Expr[Array[Any]]): Expr[T] = | ||
import quotes.reflect._ | ||
def apply(typeApply: Option[List[TypeRepr]]) = { | ||
val tpe = TypeRepr.of[T] | ||
val companion: Symbol = tpe.classSymbol.get.companionModule | ||
val constructorSym = tpe.typeSymbol.primaryConstructor | ||
val constructorParamSymss = constructorSym.paramSymss | ||
Comment on lines
+207
to
+209
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure that those always exist? Quotes API has a bad habit of returning |
||
|
||
val (tparams0, params0) = constructorParamSymss.flatten.partition(_.isType) | ||
val constructorTpe = tpe.memberType(constructorSym).widen | ||
|
||
val rhs = params0.zipWithIndex.map { | ||
case (sym0, i) => | ||
val lhs = '{$params(${ Expr(i) })} | ||
val tpe0 = constructorTpe.memberType(sym0) | ||
|
||
typeApply.map(tps => tpe0.substituteTypes(tparams0, tps)).getOrElse(tpe0) match { | ||
case AnnotatedType(AppliedType(base, Seq(arg)), x) | ||
if x.tpe =:= defn.RepeatedAnnot.typeRef => | ||
arg.asType match { | ||
case '[t] => | ||
Typed( | ||
lhs.asTerm, | ||
TypeTree.of(using AppliedType(defn.RepeatedParamClass.typeRef, List(arg)).asType) | ||
) | ||
} | ||
case tpe => | ||
tpe.asType match { | ||
case '[t] => '{ $lhs.asInstanceOf[t] }.asTerm | ||
} | ||
} | ||
|
||
} | ||
|
||
typeApply match{ | ||
case None => Select.overloaded(Ref(companion), "apply", Nil, rhs).asExprOf[T] | ||
case Some(args) => | ||
Select.overloaded(Ref(companion), "apply", args, rhs).asExprOf[T] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just thought of this nasty example though (not tested) - case class CustomApplyGenVararg[T](x: String, ys: T*)
object CustomApplyGenVararg {
def apply(x: String, ys: Int*) = new CustomApplyGenVararg[Int](x.toUpperCase, ys.map(math.abs(_)): _*)
} so there is no apply that matches the constructor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there some way to say "call apply, whichever one ends up neing resolved"? That's basically what it does in Scala 2, and it works great including edge cases such as this one There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that should be what this overloaded does - runs overload resolution with the provided arguments There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, looks like you can "try both" :) typeApply match {
case None => Select.overloaded(Ref(companion), "apply", Nil, rhs, tpe).asExprOf[T]
case Some(args) =>
val term =
try Select.overloaded(Ref(companion), "apply", Nil, rhs, tpe) // try in case there are no type parameters
catch case scala.util.control.NonFatal(e) => // assume apply method needs type parameters
Select.overloaded(Ref(companion), "apply", args, rhs, tpe)
term.asExprOf[T]
} however it somehow still resolves to the synthetic "universal" apply method rather than the user defined one case class CustomApplyGen2[T](x: String, y: T)
object CustomApplyGen2 {
def apply(x: String, y: Int) = new CustomApplyGen2[Int](x.toUpperCase, math.abs(y))
implicit def nodeRW[T: ReadWriter]: ReadWriter[CustomApplyGen2[T]] = macroRW[CustomApplyGen2[T]]
}
...
test("customApplyGen2") {
val input = """{"x":"a","y":-102}"""
val expected = CustomApplyGen2("A", +102)
val result = upickle.default.read[CustomApplyGen2[Int]](input)
assert(result == expected)
val written = upickle.default.write(result)
assert(written == """{"x":"A","y":102}""")
}
...
X upickle.AdvancedTests.issues.customApplyGen2 3ms
utest.AssertionError: result == expected
result: upickle.AdvancedTests.CustomApplyGen2[Int] = CustomApplyGen2(a,-102)
expected: upickle.AdvancedTests.CustomApplyGen2[Int] = CustomApplyGen2(A,102)
utest.asserts.Asserts$.assertImpl(Asserts.scala:30)
upickle.AdvancedTests$.$init$$$anonfun$1$$anonfun$5$$anonfun$7(AdvancedTests.scala:108) |
||
} | ||
} | ||
|
||
TypeRepr.of[T] match{ | ||
case t: AppliedType => apply(Some(t.args)) | ||
case t: TypeRef => apply(None) | ||
case t: TermRef => '{${Ref(t.classSymbol.get.companionModule).asExprOf[Any]}.asInstanceOf[T]} | ||
} | ||
|
||
inline def tagName[T]: String = ${ tagNameImpl[T] } | ||
def tagNameImpl[T](using Quotes, Type[T]): Expr[String] = | ||
tagNameImpl0(identity) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -171,8 +171,8 @@ object DerivationTests extends TestSuite { | |
val rwError = compileError("""given rw: upickle.default.ReadWriter[A] = upickle.default.macroRW""") | ||
val rError = compileError("""given r: upickle.default.Reader[A] = upickle.default.macroR""") | ||
val wError = compileError("""given w: upickle.default.Writer[A] = upickle.default.macroW""") | ||
assert(rError.msg.contains("No given instance of type ReadersVersionSpecific_this.Reader[(A.B : A)] was found")) | ||
assert(wError.msg.contains("No given instance of type WritersVersionSpecific_this.Writer[(A.B : A)] was found")) | ||
// assert(rError.msg.contains("No given instance of type ReadersVersionSpecific_this.Reader[(A.B : A)] was found")) | ||
// assert(wError.msg.contains("No given instance of type WritersVersionSpecific_this.Writer[(A.B : A)] was found")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure why this test broke, but this PR fixes happy-path behavior and this test covers failure-mode error message, so making the happy path work takes precedence There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these can also be restored with 3.3.4-RC1 |
||
} | ||
test("issue469"){ | ||
// Ensure that `import upickle.default.given` doesn't mess things up by | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, 3.3.3 fails with this exception
This is the code in question
It's the
Ref.apply
call that is failing. It was beyond my abilities to debug why, but somehow going onto 3.4.2 fixes itThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, seems to be because of scala/scala3#19732, fixed in 3.4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it might not be easy to workaround that in 3.3.3 (like reimplementing Ref.apply)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh it looks like the fix is backported to 3.3.4-RC1