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 publishing plan #2485

Open
39 tasks
Tracked by #1316
keynmol opened this issue Sep 29, 2021 · 16 comments
Open
39 tasks
Tracked by #1316

Scala 3 publishing plan #2485

keynmol opened this issue Sep 29, 2021 · 16 comments

Comments

@keynmol
Copy link
Contributor

keynmol commented Sep 29, 2021

I did not find a tracking issue for this, but please point me to it if there is one.

For full transparency, I don't know if it's necessary to publish Scalameta for Scala 3. It would certainly be nice for end users (such as mdoc and its end users) not to propagate the 2.13 versions of certain libraries Scalameta uses.

Straight up publishing for Scala 3 is complicated by the heavy usage of Scala 2 macros, as such a very quick grep shows that at least this many macros/usages of macros will need to be re-written:

  • /trees/shared/src/main/scala/scala/meta/internal/trees/AstInfo.scala
  • /quasiquotes/shared/src/main/scala/scala/meta/internal/quasiquotes/ReificationMacros.scala
  • /quasiquotes/shared/src/main/scala/scala/meta/internal/quasiquotes/ConversionMacros.scala
  • /common/shared/src/main/scala/org/scalameta/adt/Liftables.scala
  • /common/shared/src/main/scala/org/scalameta/adt/Adt.scala
  • /common/shared/src/main/scala/org/scalameta/tests/TypecheckError.scala
  • /common/shared/src/main/scala/org/scalameta/internal/FreeLocalFinder.scala
  • /common/shared/src/main/scala/org/scalameta/internal/DebugFinder.scala
  • /common/shared/src/main/scala/org/scalameta/internal/ImplTransformers.scala
  • /common/shared/src/main/scala/org/scalameta/invariants/package.scala
  • /common/shared/src/main/scala/org/scalameta/explore/Macros.scala
  • /common/shared/src/main/scala/org/scalameta/package.scala
  • /common/shared/src/main/scala/org/scalameta/data/data.scala
  • /common/shared/src/main/scala/org/scalameta/data/Macros.scala
  • /common/shared/src/main/scala/scala/meta/internal/trees/TyperMacros.scala
  • /common/shared/src/main/scala/scala/meta/internal/trees/branch.scala
  • /common/shared/src/main/scala/scala/meta/internal/trees/Liftables.scala
  • /common/shared/src/main/scala/scala/meta/internal/trees/ast.scala
  • /common/shared/src/main/scala/scala/meta/internal/trees/quasiquote.scala
  • /common/shared/src/main/scala/scala/meta/internal/trees/root.scala
  • /common/shared/src/main/scala/scala/meta/internal/trees/registry.scala
  • /common/shared/src/main/scala/scala/meta/internal/prettyprinters/ShowMacros.scala
  • /common/shared/src/main/scala/scala/meta/internal/transversers/transformer.scala
  • /common/shared/src/main/scala/scala/meta/internal/transversers/traverser.scala
  • /common/shared/src/main/scala/scala/meta/internal/transversers/transverser.scala
  • /common/shared/src/main/scala/scala/meta/internal/classifiers/Macros.scala
  • /common/shared/src/main/scala/scala/meta/internal/tokens/root.scala
  • /common/shared/src/main/scala/scala/meta/internal/tokens/token.scala
  • /common/shared/src/main/scala-2.13/org/scalameta/internal/MacroCompat.scala
  • /dialects/shared/src/main/scala/scala/meta/dialects/package.scala
  • /tokens/shared/src/main/scala/scala/meta/internal/tokens/TokenInfo.scala
  • /src/test/scala-2.12/scala/meta/tests/metacp/MetacpErrorSuite.scala

And those macros are split into two major categories:

  1. Macro annotations - which are currently not supported at all in Scala 3, for example: https://github.com/scalameta/scalameta/blob/main/scalameta/common/shared/src/main/scala/scala/meta/internal/trees/ast.scala#L11
    Faced with the same problem, Cats replaced their macros with Scalafix codegeneration: https://github.com/typelevel/simulacrum-scalafix without breaking any APIs.

  2. Various other macros, whitebox and blackbox. Scala 3's macros are more restricted, but probably most things can be implemented, if may be using slightly different techniques.

Fastparse

Scalameta depends on Fastparse but not for the main parser, rather the auxiliary ones - XML and Scaladoc, e.g. https://github.com/scalameta/scalameta/blob/fb8bee548061d75397b5fea9fb5a3d3e1aabecd0/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScaladocParser.scala

Fastparse is a heavily macro-based project, and while some preparation work has started for Scala 3 port: com-lihaoyi/fastparse#252 I'm not sure it will be easy or will progress quickly.

Fast parser options are thin on the ground for Scala 3 atm, cats-parse being the only one published and comparable in performance (afaik). Perhaps a hand-rolled parser will be easier or a macro-less version of Fastparse for Scala 3...


Consideration: Scalameta is an active project, powering several very important tools in the ecosystem, and the macro work above is a multi-week/month invasive undertaking.

To make it possible, some preliminary work needs to happen:

  • Inline sourcecode dependency in the https://github.com/scalameta/fastparse fork.
  • Inline geny dependency in the https://github.com/scalameta/fastparse fork.
  • Modify build to add Scala 3 versions where it is necessary (i.e. ignore Native, ignore Scalac plugin, etc.)
  • Move macro definition and usage files into scala-2 folders in the common module
    - This allows the Scala 2 version to compile and operate without changes
    - The work on porting the macros can continue in parallel in separate sources, scala-3 folder
  • Keep as much code as possible in the src/main/scala, if it can be cross-compiled (I've done this exercise, there's not that many files, sadly)
  • Disable full cross-compilation on CI, set Scala 3 sources compilation/testing as a separate workflow which doesn't fail the build
  • Gradually build out the meta machinery needed to compile Scalameta's code.
@olafurpg
Copy link
Member

This looks like a great plan! Thank you for initiating this discussion.

What 2.13 dependencies cause most problems for downstream users? I’m curious if we can replace them somehow so scalameta_2.13 will be dependency free or only depend on Scala 3 libraries (using the 2.13 tasty reader).

@keynmol
Copy link
Contributor Author

keynmol commented Sep 30, 2021

└─ org.scalameta:scalameta_2.13:latest.stable -> 4.4.28 (possible incompatibility)
   ├─ org.scala-lang:scala-library:2.13.6
   ├─ org.scala-lang:scalap:2.13.6
   │  └─ org.scala-lang:scala-compiler:2.13.6
   │     ├─ net.java.dev.jna:jna:5.3.1
   │     ├─ org.jline:jline:3.19.0
   │     ├─ org.scala-lang:scala-library:2.13.6
   │     └─ org.scala-lang:scala-reflect:2.13.6
   │        └─ org.scala-lang:scala-library:2.13.6
   └─ org.scalameta:parsers_2.13:4.4.28
      ├─ org.scala-lang:scala-library:2.13.6
      └─ org.scalameta:trees_2.13:4.4.28
         ├─ org.scala-lang:scala-library:2.13.6
         ├─ org.scalameta:common_2.13:4.4.28
         │  ├─ com.lihaoyi:sourcecode_2.13:0.2.7
         │  ├─ com.thesamet.scalapb:scalapb-runtime_2.13:0.11.4
         │  │  ├─ com.google.protobuf:protobuf-java:3.15.8
         │  │  ├─ com.thesamet.scalapb:lenses_2.13:0.11.4
         │  │  │  ├─ org.scala-lang:scala-library:2.13.6
         │  │  │  └─ org.scala-lang.modules:scala-collection-compat_2.13:2.4.4
         │  │  │     └─ org.scala-lang:scala-library:2.13.5 -> 2.13.6
         │  │  ├─ org.scala-lang:scala-library:2.13.6
         │  │  └─ org.scala-lang.modules:scala-collection-compat_2.13:2.4.4
         │  │     └─ org.scala-lang:scala-library:2.13.5 -> 2.13.6
         │  └─ org.scala-lang:scala-library:2.13.6
         └─ org.scalameta:fastparse-v2_2.13:2.3.1
            ├─ com.lihaoyi:geny_2.13:0.6.5
            └─ com.lihaoyi:sourcecode_2.13:0.2.3 -> 0.2.7

The common annoyances are:

  1. geny (coming from shadowed fastparse)
  2. sourcecode (^ same)
  3. scala-collection-compat
  4. scala protobuf stuff

@keynmol
Copy link
Contributor Author

keynmol commented Sep 30, 2021

Given that fastparse is already shadowed, we can do the same with geny and sourcecode

@olafurpg
Copy link
Member

Is it possible to use the 2.13 tasty reader to upgrade those dependencies to Scala 3?

@keynmol
Copy link
Contributor Author

keynmol commented Sep 30, 2021

So you mean having fastparse_2.13 depend on geny_3 and sourcecode_3 instead?

I think this will lead to opposite problem in 2.13 artifacts downstream (i.e. mdoc_2.13), as they will now have _3 artifacts resolved and SBT will barf again.

Is this what you meant?

@olafurpg
Copy link
Member

You are right, that will cause problems for 2.13 artifacts. Can we inline those dependencies instead (excluding protobuf) to make them disappear? Both geny and sourcecode are tiny, and I suspect we're not using scala-collection-compat in many places.

@olafurpg
Copy link
Member

It would of course be ideal if we can build Scalameta against Scala 3, but the checklist above shows that it will require a lot of effort. There shouldn't be any technical problems for Scala 3 users to consume scalameta_2.13 if it doesn't bring transitive 2.13 dependencies.

@keynmol
Copy link
Contributor Author

keynmol commented Sep 30, 2021

Yep, I'm all for inlining at first, to remedy the current pain points.

I'm still eager to at least start on some of the macros, but may be in a separate sbt module. Will need a bit of rejigging to have at least some tests run against those, but I want to minimise the churn for the contributors to the main parts of scalameta.

@keynmol
Copy link
Contributor Author

keynmol commented Oct 4, 2021

Modified the plan to inline both sourcecode and geny first, as it's best risk/reward move at this stage.

@halfmatthalfcat
Copy link

@keynmol what's the status (beyond the comments in here) on this effort? I'm sure it's a big effort but it's forcing me into 2.13.10 vs 3.x since I need quasiquote support.

@tgodzik
Copy link
Collaborator

tgodzik commented Mar 28, 2023

@keynmol what's the status (beyond the comments in here) on this effort? I'm sure it's a big effort but it's forcing me into 2.13.10 vs 3.x since I need quasiquote support.

We discussed it recently during the Scala tooling summit and this is non-trivial migration. We might at some point pursue a simpler macro system for Scala 3, but we haven't decided on anything yet.

@smarter
Copy link
Contributor

smarter commented Apr 11, 2023

Here are my notes from the tooling summit, note that this is focused on porting all of scalameta which is why it mentions the core macro annotation, but we might instead prioritize porting the quasiquotes macros to make scalameta more usable from Scala 3, we didn't investigate those during the summit.


During our in-person discussion we mostly focused on the @ast macro annotation since this is the core macro annotation used to define the scalameta ast.

  • The annotation is defined in https://github.com/scalameta/scalameta/blob/main/scalameta/common/shared/src/main/scala/scala/meta/internal/trees/ast.scala, it is rather long but there's comments along the way describing each step of the form // step X: ...
  • https://github.com/scalameta/scalameta/blob/main/scalameta/common/shared/src/main/scala/scala/meta/internal/trees/ast.md shows an example expansion of the ast, although it isn't completely up-to-date anymore.
  • Several of the steps in the macro annotation do not require adding new public members and could be implemented using the new experimental Scala 3 macro annotation support.
  • ... but there's also quite a lot of public definitions added. These could be expanded using a scalafix rule.
  • A lot of the complexity of the macro is related to its efficient lazy copying mechanism (see
    // step 6: implement the unimplemented methods in InternalTree (part 1)
    // The purpose of privateCopy is to provide extremely cheap cloning
    // in the case when a tree changes its parent (because that happens often in our framework,
    // e.g. when we create a quasiquote and then insert it into a bigger quasiquote,
    // or when we parse something and build the trees from the ground up).
    // In such a situation, we copy all private state verbatim (tokens, denotations, etc)
    // and create lazy initializers that will take care of recursively copying the children.
    // Compare this with the `copy` method (described below), which additionally flushes the private state.
    // This method is private[meta] because the state that it's managing is not supposed to be touched
    // by the users of the framework.
    ). An initial version of a Scala 3 implementation could perhaps skip those.

@nafg
Copy link

nafg commented Jul 12, 2023

Any guess when this might happen?

@tgodzik
Copy link
Collaborator

tgodzik commented Jul 13, 2023

Not currently no, but you can use the Scalameta 2.13.x version in Scala 3 currently.

@henricook
Copy link

Would you mind giving me a dummy's explanation of how to use Scalameta 2.13.x in my Scala 3 project please?

@mkurz
Copy link

mkurz commented Dec 9, 2023

@henricook

libraryDependencies += ("org.scalameta" %% "scalameta" % "4.8.14").cross(CrossVersion.for3Use2_13),

We use parser like this in twirl: https://github.com/playframework/twirl/blob/382474d3ebc3761b37cb1af7138926fcb4364159/build.sbt#L119

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

8 participants