Skip to content
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

Scala 3: What should be done #300

Closed
lloydmeta opened this issue Feb 3, 2021 · 24 comments
Closed

Scala 3: What should be done #300

lloydmeta opened this issue Feb 3, 2021 · 24 comments

Comments

@lloydmeta
Copy link
Owner

lloydmeta commented Feb 3, 2021

Looking at Scala 3, the enumerations built into the language look pretty good.

The questions are

  1. What should enumeratum look like in a Scala 3 world?
    • What problems are we trying to solve? The problems with Scala 2 enums were pretty obvious, which led to the core of this lib
@catostrophe
Copy link

catostrophe commented Feb 3, 2021

The first step towards Scala 3 for any real-world project is cross-compilation between Scala 2.13/Scala 3. Most of the OSS libs added Scala 3 to their cross-builds.

IMO, this will be a status quo for the next few years. Only then we'll drop Scala 2 support and migrate to the new enums.

Thus, the Enumeratum macros should be ported.

@hmemcpy
Copy link

hmemcpy commented Feb 3, 2021

My 2c: we use Enumeratum mostly to model things that map to postgres enums, with a nice benefit of being able to have safe withName* overloads for decoding.

I'm not sure if Scala 3 enums has support for this...

@lloydmeta
Copy link
Owner Author

The first step towards Scala 3 for any real-world project is cross-compilation between Scala 2.13/Scala 3. Most of the OSS libs added Scala 3 to their cross-builds.

IMO, this will be a status quo for the next few years. Only then we'll drop Scala 2 support and migrate to the new enums.

Thus, the Enumeratum macros should be ported.

Fair point; have you checked that the macro(s) in its current form can be ported? A PR would be welcome to start concrete discussions as well.

@lloydmeta
Copy link
Owner Author

lloydmeta commented Feb 3, 2021

My 2c: we use Enumeratum mostly to model things that map to postgres enums, with a nice benefit of being able to have safe withName* overloads for decoding.

I'm not sure if Scala 3 enums has support for this...

I think there is a way of having custom names, based on this doc https://github.com/dotty-staging/dotty/blob/master/docs/docs/reference/enums/desugarEnums.md#translation-of-enums-with-singleton-cases

Nevermind... looks like support for custom enumLabel was added, then later removed, and I can't figure out how to get it working (Scastie attempts)

@note
Copy link

note commented Apr 30, 2021

@lloydmeta I think you were looking for valueOf: scastie session

@lloydmeta
Copy link
Owner Author

@note Hmmm I think that's almost it, but how do we make the second print resolve Red from the custom name "red"?

@note
Copy link

note commented May 3, 2021

@lloydmeta Ah, I missed the point that it's about custom names as I mostly looked at scastie session using outdated ofValue. I am not sure about support for custom names in Scala 3 as well

@jtjeferreira
Copy link
Contributor

Thus, the Enumeratum macros should be ported.

any plan to have the macros ported?

@lloydmeta
Copy link
Owner Author

Thus, the Enumeratum macros should be ported.

any plan to have the macros ported?

The plan is:

  1. Come up with a working prototype for the macro, either on Scastie or a PR
    • In particular, the enclosingModule function is basically gone with no replacement in Scala 3 AFAIK, so we can't explore inside a "scope" for findValues
      private[enumeratum] def enclosedSubClassTreesInModule(c: Context)(
      typeSymbol: c.universe.Symbol,
      enclosingModule: c.universe.ModuleDef
      ): List[c.universe.ModuleDef] = {
      import c.universe._
      enclosingModule.impl.body.flatMap(_ match {
      case m: ModuleDef
      if m.symbol.isModule &&
      m.symbol.asModule.moduleClass.asClass.baseClasses.contains(typeSymbol) =>
      m :: enclosedSubClassTreesInModule(c)(typeSymbol, m)
      case m: ModuleDef =>
      enclosedSubClassTreesInModule(c)(typeSymbol, m)
      case _ => List.empty
      })
      }
  2. Implement the prototype.

Currently stuck on (1).

@jtjeferreira
Copy link
Contributor

jtjeferreira commented Jul 2, 2021

In particular, the enclosingModule function is basically gone with no replacement in Scala 3 AFAIK

There is some dotty/scala3 issue about that?

Alternatively, could we change the signature of findValues so we can pass that enclosingModule, like:

sealed abstract class Light extends EnumEntry
case object Light extends Enum[Light] {
  val values = findValues[Light]
  case object Red   extends Light
  case object Blue  extends Light
  case object Green extends Light
}

Sorry if this, does not make sense at all, because I am not very familiar with macros...

@lloydmeta
Copy link
Owner Author

lloydmeta commented Jul 4, 2021

In particular, the enclosingModule function is basically gone with no replacement in Scala 3 AFAIK

There is some dotty/scala3 issue about that?

Nope. I think it was a conscious decision/redesign around the Scala 3 macro system.

Alternatively, could we change the signature of findValues so we can pass that enclosingModule, like:

sealed abstract class Light extends EnumEntry
case object Light extends Enum[Light] {
  val values = findValues[Light]
  case object Red   extends Light
  case object Blue  extends Light
  case object Green extends Light
}

It's not so much a problem of having to provide the type param (the compiler can figure that out in the same way I think), just AFAICT, the functionality to explore the object scope is gone.

I think one way might be to change it into something that works on a defined scope like so, but it's not backwards-source compatible.

sealed abstract class Light extends EnumEntry
@findEnumValues // or find members at this point
case object Light extends Enum[Light] {
   case object Red   extends Light
   case object Blue  extends Light
   case object Green extends Light
}

To be clear: the only helpful next step in this issue is for someone to come forth with a working prototype.

@marq
Copy link

marq commented Jul 20, 2021

Actually it's possible to access "enclosingModule" via Symbol.spliceOwner.owner...owner.
Here is a prototype: Scastie

@lloydmeta
Copy link
Owner Author

@marq Thanks so much for that (even including a Scastie!). I'm very unfamiliar with the new Scala 3 macros (and it seems like the documentation may not be the most complete ?) so I'm having a hard time and would really appreciate the help of those who are such as yourself 🙏🏼

To provide a clean sandbox to hack around, I created a separate repo to explore porting macros to Scala 3 some more, and split it into 3 issues, one for each main macro method:

https://github.com/lloydmeta/mune/issues

Since you've done most of the work, lloydmeta/mune#1 is basically done, but I'm running into some errors on lloydmeta/mune#2 (materialising the companion object of an enum entry type); wondering you can spot anything wrong there. lloydmeta/mune#3 is also a TODO.

@mauhiz
Copy link
Contributor

mauhiz commented Dec 6, 2021

Hi @lloydmeta ! It seems that enumeratum still provides stuff that native scala3 enums don't, does it? Would be great to have I guess.
Are you actively working on that?

@lloydmeta
Copy link
Owner Author

Hi @lloydmeta ! It seems that enumeratum still provides stuff that native scala3 enums don't, does it? Would be great to have I guess.
Are you actively working on that?

Hey there @mauhiz , yeah, it turns out there are some things missing from the official Scala 3 enums that is supported here. I agree it would be nice to have, but I got stuck in my attempt to port the macro to Scala3 (see #300 (comment)). I'm not actively working on it and any help would be appreciated :)

@lloydmeta
Copy link
Owner Author

Thanks to @tpunder, the Scala 3 port is mostly complete.

The remaining bit is lloydmeta/mune#3, which involves porting over this bit

private[this] def findValueEntriesImpl[
ValueEntryType: c.WeakTypeTag,
ValueType: ClassTag,
ProcessedValue
](c: Context)(
processFoundValues: ValueType => ProcessedValue
): c.Expr[IndexedSeq[ValueEntryType]] = {
import c.universe._
val typeSymbol = weakTypeOf[ValueEntryType].typeSymbol
EnumMacros.validateType(c)(typeSymbol)
// Find the trees in the enclosing object that match the given ValueEntryType
val subclassTrees = EnumMacros.enclosedSubClassTrees(c)(typeSymbol)
// Find the parameters for the constructors of ValueEntryType
val valueEntryTypeConstructorsParams =
findConstructorParamsLists[ValueEntryType](c)
// Identify the value:ValueType implementations for each of the trees we found and process them if required
val treeWithVals = findValuesForSubclassTrees[ValueType, ProcessedValue](c)(
valueEntryTypeConstructorsParams,
subclassTrees,
processFoundValues
)
if (weakTypeOf[ValueEntryType] <:< c.typeOf[AllowAlias]) {
// Skip the uniqueness check
} else {
// Make sure the processed found value implementations are unique
ensureUnique[ProcessedValue](c)(treeWithVals)
}
// Finish by building our Sequence
val subclassSymbols = treeWithVals.map(_.tree.symbol)
EnumMacros.buildSeqExpr[ValueEntryType](c)(subclassSymbols)
}

I think the main tricky bit is finding how to destructure the different ways to declare value in a Scala 3 world.

@luksow
Copy link
Contributor

luksow commented May 17, 2022

@lloydmeta this might be helpful: https://github.com/theiterators/kebs/blob/master/macro-utils/src/main/scala-3/pl/iterators/kebs/macros/enums/EnumEntryMacros.scala

@coreywoodfield
Copy link
Contributor

@lloydmeta any update on this? Thanks

@lloydmeta
Copy link
Owner Author

There's a PR open (#349) that I've slowly been reviewing over iterations.

@lloydmeta
Copy link
Owner Author

Done.

@coreywoodfield
Copy link
Contributor

First off, thank you.

Second, I notice that there are two separate libraries: one for scala 3 and one for scala 2. Is there any plan or desire to have a mixed library that can be used in scala 3 and in scala 2? This would be useful for large projects that use enumeratum that want to migrate to scala 3 gradually. I think the alternatives would be either migrating all at once, or depending on both libraries, and I don't know how well depending on both libraries would work out in practice (I imagine not well)

@lloydmeta
Copy link
Owner Author

Is there any plan or desire to have a mixed library that can be used in scala 3 and in scala 2

There's no plan, but it does sound desirable; do you want to give it a stab ?

@coreywoodfield
Copy link
Contributor

Sure, I'll take a stab at it

@coreywoodfield
Copy link
Contributor

I have something that I think would work, but there's a bug in the macro mixing functionality that is triggered by using type parameters from the class level in a scala 2 macro call. So I don't think any further progress can be made until that's fixed.

branch:
master...lucidsoftware:enumeratum:mix-scala-2-and-3-macros
scala bug:
scala/scala3#16630

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants