Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/makkarpov/explicits

A tiny library to have more control over Scala 3 implicit resolution in macros
https://github.com/makkarpov/explicits

scala scala-3 scala-macros

Last synced: 6 days ago
JSON representation

A tiny library to have more control over Scala 3 implicit resolution in macros

Awesome Lists containing this project

README

        

explicits
=========

A tiny library which gives macro authors much more control over Scala 3 implicit resolution process.

```scala
libraryDependencies += "mx.m-k" %% "explicits" % "0.1"
```

Implicit resolution
-------------------

Vanilla `Implicits.search` method provided by the compiler has several major drawbacks:

* It resolves implicits strictly in the scope of a macro application: you cannot inject any additional imports or givens even if you know where to look,
* and it either resolves the implicit completely or completely fails. You cannot get half-resolved result like "I can do that, but you need to provide X and Y for me".

Second drawback is critical if you are writing a macros to derive typeclasses recursively. Consider the following example:

```scala
trait CanMeow[T] { /* ... */ }

given seqCanMeow[T](using CanMeow[T]): CanMeow[Seq[T]] = ???

case class Foo(/* ... */)
case class Bar(foos: Seq[Foo]) derives CanMeow
```

In this case, you will never resolve `seqCanMeow` implicit with default API. This library could provide you a half-completed result saying "give me a `CanMeow[Foo]` and I will build `CanMeow[Seq[Foo]]` for you".

Usage example
-------------

All magic starts with `ImplicitSearch.builder` method:

```scala
import mx.mk.explicits.{ImplicitSearch, Symbol}

def deriveMeow[T](using Type[T], Quotes): Expr[CanMeow[T]] = {
val r = ImplicitSearch.builder[T]
// inject additional imports:
.extraLocations(Symbol.forModule("com.example.MeowModule"))
// provide explicit givens:
.give[CanMeow[Foobar]]('{ ??? })
// setup the assisted resolution:
.assist {
case '[ CanMeow[t] ] => true // sure, everything can meow with me
case _ => false // can't assist with anything else
}
.search() // get the final result
.toSuccess // aborts the macro if search failed

// inspect and derive what was missing:
val meows: Seq[Expr[?]] = success
.missingTypes
.map {
case '[ CanMeow[t] ] =>
'{ new CanMeow[t] { /* teach `t` to meow here */ } }
}

// construct the final expression:
success.construct(meows)
}
```

If you don't use the `.assist()` method, `ImplicitSearch.Success` will always have `missingTypes` field empty, and simple `.construct(Nil)` is sufficient to get the final expression. Any missing implicit will fail the overall resolution process if assisted resolution is disabled.

If you want to assist the compiler with implicit resolution:

1. `.assist()` method takes a type filter predicate (`Type[?] => Boolean`). This predicate should test whether a value for the type could be generated by your code. Search will fail if it encounters a missing value which fails the test.
2. You should inspect `.missingTypes` field of the search result and provide expressions for all types listed there. Final expression is then constructed from the provided parts by the `.construct()` method.

Compatibility
-------------

Since this library heavily depends on the compiler internals, it could easily break even on slightest compiler change. To ensure seamless operation across a wide range of compiler versions, this library internally packs multiple backend implementations and selects a correct one at runtime.

Currently, this library is tested to work on all released compiler versions from **3.2.0** up to **3.4.2**.

License
-------

This library is licensed under Apache 2.0 license.