Skip to content

Conversation

@Kalin-Rudnicki
Copy link
Contributor

@Kalin-Rudnicki Kalin-Rudnicki commented Dec 26, 2025

Context

I opened a draft MR a while back proposing something like this, and was told it would need some proving out. I have built a full blown auto-derived SQL codec + scala DSL + migration library on top of a wrapper I wrote with the same principles in this MR (except it wraps Quotes one level out instead of directly wrapping the compiler internals like this MR begins to do).

I am hoping to receive feedback of the receptiveness of such a change. If it will be received and used if working, I will happily see it through to a complete and working state.

Problem with Quotes.reflect.* Currently

This is a 5,500 that is extremely difficult for anyone to read, and feels nothing like any interface I have used in 5 years of Scala development, at least from a "how are things defined, and how can I find what I need" perspective. On top of this, the way the interface is defined is very confusing to the IDE, making an already very complex domain even harder, borderline impossible to get anything done. I have built an entire wrapper around this API, and have been diving into this domain for 6 months now, and I still can not get anything done in the few times I try to use the API directly without the wrapper.

Core Design

I commented out a few highlights, but the TLDR is you have a Example, Example.Module, ExampleImpl and ExampleImpl.Module. The only way to get an instance of Example.Module is via quotes.reflectV2.Example, although the canonical way to get that is via Example.quoted(using quotes).myAbc or Example.myAbc (via Example.moduleConversion).

Benefits

  1. API is more discoverable
  2. API is more readable
  3. You can split things into different files, so someone trying to look around is not thrown into a 5,500+ line file that looks nothing like they have ever seen before
  4. IDE understands what is going on and can go to definition + complete Example.myA (completes myAbc)
  5. You can actually define helpers for an API like this

@Kalin-Rudnicki Kalin-Rudnicki requested a review from a team as a code owner December 26, 2025 14:06
@Kalin-Rudnicki Kalin-Rudnicki changed the title WIP : started refactoring of Quotes.reflect into a top level package DRAFT (seeking feedback) : started refactoring of Quotes.reflect into a top level package Dec 26, 2025
Comment on lines +25 to +42
trait BooleanConstant private[compiletime] () extends Constant {

override def value: Boolean

}
object BooleanConstant {

def quoted(using quotes: Quotes): BooleanConstant.Module = quotes.reflectV2.BooleanConstant
given moduleConversion: (quotes: Quotes) => Conversion[BooleanConstant.type, BooleanConstant.Module] = _ => quotes.reflectV2.BooleanConstant

def unapply(x: BooleanConstant): Some[Boolean] = Some(x.value)

trait Module private[compiletime] () {
def apply(x: Boolean): BooleanConstant
def make(x: Boolean): BooleanConstant
}

}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public definition of BooleanConstant + BooleanConstant.Module

as long as you have a given Quotes, can be used at the top level as such:

def myFunction(bool: Boolean)(using Quotes): Nothing = {
  val ex1: BooleanConstant = BooleanConstant.quoted.make(bool)
  val ex2: Constant = BooleanConstant.make(bool)
  ???
}

Thanks to the Conversion, we can treat object BooleanConstant exactly like we would expect to treat a normal companion object, but we still achieve the desired obfuscation of the private API.

Comment on lines +32 to +41
type BooleanConstant = BooleanConstantImpl
final case class BooleanConstantImpl(value: Boolean) extends ConstantImpl, pub.BooleanConstant
object BooleanConstantImpl {

object Module extends pub.BooleanConstant.Module {
override def apply(x: Boolean): BooleanConstant = BooleanConstantImpl(x)
override def make(x: Boolean): BooleanConstant = BooleanConstantImpl(x)
}

}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we are able to implement BooleanConstant and BooleanConstant.Module with BooleanConstantImpl and BooleanConstantImpl.module. The impl of this one is dead simple, so its an object Module, but there are other such cases with final class Module(using val ctx: Context) extends pub.Ex.Module { /* ... */ }.

Comment on lines +5 to +9
trait Quotes {

lazy val reflectV2: pub.reflect.Module

}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quotes exposes top level defined reflect.Module

Comment on lines +5 to +19
object reflect {

trait Module {

lazy val Symbol: pub.Symbol.Module
lazy val Position: pub.Position.Module
lazy val SourceFile: pub.SourceFile.Module
lazy val Signature: pub.Signature.Module
lazy val Flags: pub.Flags.Module

lazy val Selector: pub.Selector.Module
lazy val SimpleSelector: pub.SimpleSelector.Module
lazy val RenameSelector: pub.RenameSelector.Module
lazy val OmitSelector: pub.OmitSelector.Module
lazy val GivenSelector: pub.GivenSelector.Module
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reflect.Module contains implementations of all the *.Module

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

Successfully merging this pull request may close these issues.

1 participant