diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index e66c71731b4f..85cfc8ff2528 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -33,6 +33,8 @@ object desugar { */ val DerivingCompanion: Property.Key[SourcePosition] = Property.Key() + val DerivingName: Property.Key[TermName] = Property.Key() + /** An attachment for match expressions generated from a PatDef or GenFrom. * Value of key == one of IrrefutablePatDef, IrrefutableGenFrom */ diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index cd727ba8ac72..5bf0c77e6556 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -34,6 +34,7 @@ import config.Feature.{sourceVersion, migrateTo3} import config.SourceVersion.* import config.SourceVersion import dotty.tools.dotc.config.MigrationVersion +import dotty.tools.dotc.ast.desugar object Parsers { @@ -4453,7 +4454,13 @@ object Parsers { val derived = if (isIdent(nme.derives)) { in.nextToken() - commaSeparated(() => convertToTypeId(qualId())) + commaSeparated { () => + val qual = convertToTypeId(qualId()) + if isIdent(nme.as) then + in.nextToken() + qual.putAttachment(desugar.DerivingName, ident()) + qual + } } else Nil possibleTemplateStart() diff --git a/compiler/src/dotty/tools/dotc/typer/Deriving.scala b/compiler/src/dotty/tools/dotc/typer/Deriving.scala index 60148319a61c..9a3a58fcf92c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Deriving.scala +++ b/compiler/src/dotty/tools/dotc/typer/Deriving.scala @@ -41,8 +41,8 @@ trait Deriving { * an instance with the same name does not exist already. * @param reportErrors Report an error if an instance with the same name exists already */ - private def addDerivedInstance(clsName: Name, info: Type, pos: SrcPos): Unit = { - val instanceName = "derived$".concat(clsName) + private def addDerivedInstance(clsName: Name, info: Type, pos: SrcPos, name: Option[TermName]): Unit = { + val instanceName = name.getOrElse("derived$".concat(clsName)) if (ctx.denotNamed(instanceName).exists) report.error(em"duplicate type class derivation for $clsName", pos) else @@ -100,7 +100,7 @@ trait Deriving { val derivedInfo = if derivedParams.isEmpty then monoInfo else PolyType.fromParams(derivedParams, monoInfo) - addDerivedInstance(originalTypeClassType.typeSymbol.name, derivedInfo, derived.srcPos) + addDerivedInstance(originalTypeClassType.typeSymbol.name, derivedInfo, derived.srcPos, derived.removeAttachment(desugar.DerivingName)) } def deriveSingleParameter: Unit = { diff --git a/tests/pos/as-in-derives/Test.scala b/tests/pos/as-in-derives/Test.scala new file mode 100644 index 000000000000..ca004020cfb0 --- /dev/null +++ b/tests/pos/as-in-derives/Test.scala @@ -0,0 +1,73 @@ +import scala.deriving.* + +import scala.compiletime.* + +// =============== Typeclass definition +trait YesNoEnum[A] { + extension (self: A) def toBoolean: Boolean + + def apply(bool: Boolean): A +} + +object YesNoEnum { + + inline def derived[A <: scala.reflect.Enum](using + A: Mirror.SumOf[A], + ev1: A.MirroredElemLabels <:< ("Yes", "No") + ): YesNoEnum[A] = + new { + private val instances = summonInstances[A.MirroredElemTypes, A] + private val yes = instances(0) + private val no = instances(1) + + extension (self: A) def toBoolean: Boolean = A.ordinal(self) == 0 + + def apply(bool: Boolean): A = + if bool then yes else no + + } + + private inline def summonInstances[A <: Tuple, Parent <: scala.reflect.Enum]: List[Parent] = + inline erasedValue[A] match { + case _: (h *: t) => + inline valueOf[h] match { + case parent: Parent => parent :: summonInstances[t, Parent] + } + + case _: EmptyTuple => Nil + } +} + + +// =============== usage (current) =============== + +enum DecisionCurrent derives YesNoEnum { + case Yes, No +} + +object DecisionCurrent { + export derived$YesNoEnum.{apply as fromBoolean} +} + + +// =============== usage (new) =============== + +enum Decision derives YesNoEnum as fromBoolean { + case Yes, No +} + +enum DecisionAlternate derives YesNoEnum as yesNoEnum { + case Yes, No +} + +object DecisionAlternate { + export yesNoEnum.{apply as fromBoolean} +} + +@main def test = { + Decision.fromBoolean(true) // Yes + Decision.fromBoolean(false) // No + + DecisionAlternate.fromBoolean(true) // Yes + DecisionAlternate.fromBoolean(false) // No +}