Skip to content

Commit

Permalink
Only override defaultArgument in mirrors of new case classes
Browse files Browse the repository at this point in the history
And test backwards compat
  • Loading branch information
EugeneFlesselle committed Feb 1, 2024
1 parent 3e1f445 commit 15c5e58
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 2 deletions.
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Annotations.Annotation
import Phases.*
import ast.tpd.Literal
import transform.Mixin
import dotty.tools.tasty.TastyVersion

import dotty.tools.dotc.transform.sjs.JSSymUtils.sjsNeedsField

Expand Down Expand Up @@ -115,6 +116,13 @@ class SymUtils:

def isGenericProduct(using Context): Boolean = whyNotGenericProduct.isEmpty

/** Is a case class for which mirrors support access to default arguments.
* see sbt-test/scala3-compat/defaultArgument-mirrors-3.3 for why this is needed
*/
def mirrorSupportsDefaultArguments(using Context): Boolean =
!(self.is(JavaDefined) || self.is(Scala2x)) && self.isClass && self.tastyInfo.forall:
case TastyInfo(TastyVersion(major, minor, exp), _) => major == 28 && minor >= 4

/** Is this an old style implicit conversion?
* @param directOnly only consider explicitly written methods
* @param forImplicitClassOnly only consider methods generated from implicit classes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
addParent(defn.Mirror_ProductClass.typeRef)
addMethod(nme.fromProduct, MethodType(defn.ProductClass.typeRef :: Nil, monoType.typeRef), cls,
fromProductBody(_, _, optInfo).ensureConforms(monoType.typeRef)) // t4758.scala or i3381.scala are examples where a cast is needed
if cls.primaryConstructor.hasDefaultParams then
if cls.primaryConstructor.hasDefaultParams && cls.mirrorSupportsDefaultArguments then
overrideMethod(nme.defaultArgument, MethodType(defn.IntType :: Nil, defn.AnyType), cls,
defaultArgumentBody(_, _, optInfo), isExperimental = true)
}
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,10 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
def makeProductMirror(pre: Type, cls: Symbol, tps: Option[List[Type]]): TreeWithErrors =
val accessors = cls.caseAccessors
val Seq(elemLabels, elemHasDefaults, elemTypes1) =
val supportsDefaults = cls.mirrorSupportsDefaultArguments
Seq(
accessors.map(acc => ConstantType(Constant(acc.name.toString))),
accessors.map(acc => ConstantType(Constant(acc.is(HasDefault)))),
accessors.map(acc => ConstantType(Constant(supportsDefaults && acc.is(HasDefault)))),
tps.getOrElse(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
).map(TypeOps.nestedPairs)
val (monoType, elemTypes) = mirroredType match
Expand Down
58 changes: 58 additions & 0 deletions sbt-test/scala3-compat/defaultArgument-mirrors-3.3/app/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import scala.deriving.Mirror

package lib {

case class NewFoo(x: Int = 1, y: Int)

object NewMirrors {
val mNewFoo = summon[Mirror.Of[NewFoo]]

val mOldFoo = summon[Mirror.Of[OldFoo]]
val mOldBar = summon[Mirror.Of[OldBar]]
}
}

package app {
import lib.*

object Main {

// defaultArgument implementation did not throw NoSuchElementException
def foundDefaultArgument(m: Mirror.Product): Boolean = try {
m.defaultArgument(0)
true
} catch {
case _: NoSuchElementException => false
}

def main(args: Array[String]): Unit = {

// NewFoo: normal case with support for default arguments

assert(NewMirrors.mNewFoo.defaultArgument(0) == 1)
summon[NewMirrors.mNewFoo.MirroredElemHasDefaults =:= (true, false)]

// OldFoo: does not override the defaultArgument implementation

assert(!foundDefaultArgument(NewMirrors.mOldFoo)) // Expected: since mirror of old case class
summon[NewMirrors.mOldFoo.MirroredElemHasDefaults =:= (false, false)] // Necessary: to be consistent with defaultArgument implementation

assert(!foundDefaultArgument(OldMirrors.mOldFoo)) // Expected: since mirror of old case class
summon[scala.util.NotGiven[OldMirrors.mOldFoo.MirroredElemHasDefaults <:< (Boolean, Boolean)]] // reference to old mirror doesn't have any refinement
summon[OldMirrors.mOldFoo.MirroredElemHasDefaults <:< Tuple] // but does inherit type member from Mirror trait

// OldBar: is anon mirror so could implement defaultArgument
// but we manually keep behaviour consistent with other mirrors of old case classes

assert(NewMirrors.mOldBar ne lib.OldBar)
assert(!foundDefaultArgument(NewMirrors.mOldBar))
summon[NewMirrors.mOldBar.MirroredElemHasDefaults =:= (false, false)] // Ok: should be consistent with above

assert(OldMirrors.mOldBar ne lib.OldBar)
assert(!foundDefaultArgument(OldMirrors.mOldBar))
summon[scala.util.NotGiven[OldMirrors.mOldBar.MirroredElemHasDefaults <:< (Boolean, Boolean)]]
summon[OldMirrors.mOldBar.MirroredElemHasDefaults <:< Tuple]

}
}
}
7 changes: 7 additions & 0 deletions sbt-test/scala3-compat/defaultArgument-mirrors-3.3/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
lazy val lib = project.in(file("lib"))
.settings(
scalaVersion := "3.3.0"
)

lazy val app = project.in(file("app"))
.dependsOn(lib)
13 changes: 13 additions & 0 deletions sbt-test/scala3-compat/defaultArgument-mirrors-3.3/lib/Foo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package lib

import deriving.Mirror

case class OldFoo(x: Int = 1, y: Int)

case class OldBar(x: Int = 1, y: Int)
case object OldBar

object OldMirrors {
val mOldFoo = summon[Mirror.ProductOf[OldFoo]]
val mOldBar = summon[Mirror.ProductOf[OldBar]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import sbt._
import Keys._

object DottyInjectedPlugin extends AutoPlugin {
override def requires = plugins.JvmPlugin
override def trigger = allRequirements

override val projectSettings = Seq(
scalaVersion := sys.props("plugin.scalaVersion")
)
}
1 change: 1 addition & 0 deletions sbt-test/scala3-compat/defaultArgument-mirrors-3.3/test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
> app/run
2 changes: 2 additions & 0 deletions sbt-test/source-dependencies/mirror-product/MyProduct.scala
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
case class MyProduct(x: Int)
case class WillGetDefault(x: Int)
case class WillChangeDefault(x: Int = 1)
2 changes: 2 additions & 0 deletions sbt-test/source-dependencies/mirror-product/Test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ transparent inline def foo[T](using m: Mirror.Of[T]): Int =

@main def Test =
assert(foo[MyProduct] == 2)
assert(summon[Mirror.Of[WillGetDefault]].defaultArgument(0) == 1)
assert(summon[Mirror.Of[WillChangeDefault]].defaultArgument(0) == 2)
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
case class MyProduct(x: Int, y: String)
case class WillGetDefault(x: Int = 1)
case class WillChangeDefault(x: Int = 2)

0 comments on commit 15c5e58

Please sign in to comment.