-
Notifications
You must be signed in to change notification settings - Fork 76
Port sourcecode to Dotty #80
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
Changes from all commits
57e0518
109fc9b
7ed1a5a
b62b1e9
0359aea
5820d0e
1a36bc6
347c6a2
3be0218
6d01f18
c556b0f
68e029f
77a35e9
86affd4
340c5f2
698d2f1
7818a3f
a8874ed
7cb30fb
a3cfdb9
f480cc1
affced6
a18d9db
9d8a4b1
49c20bd
7a84bb9
26e2d95
a9ceadf
db11447
719ebfe
71b875c
7177d37
6ecc0c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
#!/usr/bin/env sh | ||
|
||
# This is a wrapper script, that automatically download mill from GitHub release pages | ||
# You can give the required mill version with MILL_VERSION env variable | ||
# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION | ||
DEFAULT_MILL_VERSION=0.5.0 | ||
|
||
set -e | ||
|
||
if [ -z "$MILL_VERSION" ] ; then | ||
if [ -f ".mill-version" ] ; then | ||
MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" | ||
elif [ -f "mill" ] && [ "$BASH_SOURCE" != "mill" ] ; then | ||
MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2) | ||
else | ||
MILL_VERSION=$DEFAULT_MILL_VERSION | ||
fi | ||
fi | ||
|
||
MILL_DOWNLOAD_PATH="$HOME/.mill/download" | ||
MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/$MILL_VERSION" | ||
|
||
if [ ! -x "$MILL_EXEC_PATH" ] ; then | ||
mkdir -p $MILL_DOWNLOAD_PATH | ||
DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download | ||
MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION%%-*}/$MILL_VERSION-assembly" | ||
curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL" | ||
chmod +x "$DOWNLOAD_FILE" | ||
mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH" | ||
unset DOWNLOAD_FILE | ||
unset MILL_DOWNLOAD_URL | ||
fi | ||
|
||
unset MILL_DOWNLOAD_PATH | ||
unset MILL_VERSION | ||
|
||
exec $MILL_EXEC_PATH "$@" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package sourcecode | ||
|
||
object TestUtil { | ||
|
||
val isDotty = false | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package sourcecode | ||
|
||
object TestUtil { | ||
|
||
// FIXME In dotty, scala.util.Properties.versionNumberString is still like 2.12.x | ||
lazy val isDotty = { | ||
val cl: ClassLoader = Thread.currentThread().getContextClassLoader | ||
try { | ||
cl.loadClass("dotty.DottyPredef") | ||
true | ||
} catch { | ||
case _: ClassNotFoundException => | ||
false | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package sourcecode | ||
|
||
object TestUtil { | ||
|
||
val isDotty = false | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
package sourcecode | ||
|
||
import scala.language.implicitConversions | ||
import scala.quoted._ | ||
import scala.tasty.Reflection | ||
|
||
trait NameMacros { | ||
inline implicit def generate: Name = | ||
${ Macros.nameImpl } | ||
} | ||
|
||
trait NameMachineMacros { | ||
inline implicit def generate: Name.Machine = | ||
${ Macros.nameMachineImpl } | ||
} | ||
|
||
trait FullNameMacros { | ||
inline implicit def generate: FullName = | ||
${ Macros.fullNameImpl } | ||
} | ||
|
||
trait FullNameMachineMacros { | ||
inline implicit def generate: FullName.Machine = | ||
${ Macros.fullNameMachineImpl } | ||
} | ||
|
||
trait FileMacros { | ||
inline implicit def generate: sourcecode.File = | ||
${ Macros.fileImpl } | ||
} | ||
|
||
trait FileNameMacros { | ||
inline implicit def generate: sourcecode.FileName = | ||
${ Macros.fileNameImpl } | ||
} | ||
|
||
trait LineMacros { | ||
inline implicit def generate: sourcecode.Line = | ||
${ Macros.lineImpl } | ||
} | ||
|
||
trait EnclosingMacros { | ||
inline implicit def generate: Enclosing = | ||
${ Macros.enclosingImpl } | ||
} | ||
|
||
trait EnclosingMachineMacros { | ||
inline implicit def generate: Enclosing.Machine = | ||
${ Macros.enclosingMachineImpl } | ||
} | ||
|
||
trait PkgMacros { | ||
inline implicit def generate: Pkg = | ||
${ Macros.pkgImpl } | ||
} | ||
|
||
trait TextMacros { | ||
inline implicit def generate[T](v: => T): Text[T] = ${ Macros.text('v) } | ||
inline def apply[T](v: => T): Text[T] = ${ Macros.text('v) } | ||
} | ||
|
||
trait ArgsMacros { | ||
inline implicit def generate: Args = | ||
${ Macros.argsImpl } | ||
} | ||
|
||
object Util{ | ||
def isSynthetic(c: Reflection)(s: c.Symbol) = isSyntheticName(getName(c)(s)) | ||
def isSyntheticName(name: String) = { | ||
name == "<init>" || (name.startsWith("<local ") && name.endsWith(">")) | ||
} | ||
def getName(c: Reflection)(s: c.Symbol) = { | ||
import c.given | ||
s.name.trim | ||
.stripSuffix("$") // meh | ||
} | ||
} | ||
|
||
object Macros { | ||
|
||
def actualOwner(c: Reflection)(owner: c.Symbol): c.Symbol = { | ||
import c.given | ||
var owner0 = owner | ||
// second condition is meh | ||
while(Util.isSynthetic(c)(owner0) || Util.getName(c)(owner0) == "ev") { | ||
anatoliykmetyuk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
owner0 = owner0.owner | ||
} | ||
owner0 | ||
} | ||
|
||
def nameImpl(given ctx: QuoteContext): Expr[Name] = { | ||
import ctx.tasty.given | ||
val owner = actualOwner(ctx.tasty)(ctx.tasty.rootContext.owner) | ||
val simpleName = Util.getName(ctx.tasty)(owner) | ||
'{Name(${simpleName.toExpr})} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the reference to free terms like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as my understanding of hygiene in Scala 2 macros goes, in Scala 3 it is fine. Scala 3 quotes are compiled on definition site wheres Scala 2 quasiquotes are compiled on expansion site. So in Scala 2, if e.g. you use |
||
} | ||
|
||
private def adjustName(s: String): String = | ||
// Required to get the same name from dotty | ||
if (s.startsWith("<local ") && s.endsWith("$>")) | ||
s.stripSuffix("$>") + ">" | ||
else | ||
s | ||
|
||
def nameMachineImpl(given ctx: QuoteContext): Expr[Name.Machine] = { | ||
import ctx.tasty.given | ||
val owner = ctx.tasty.rootContext.owner | ||
val simpleName = adjustName(Util.getName(ctx.tasty)(owner)) | ||
'{Name.Machine(${simpleName.toExpr})} | ||
} | ||
|
||
def fullNameImpl(given ctx: QuoteContext): Expr[FullName] = { | ||
import ctx.tasty.given | ||
@annotation.tailrec def cleanChunk(chunk: String): String = | ||
val refined = chunk.stripPrefix("_$").stripSuffix("$") | ||
if chunk != refined then cleanChunk(refined) else refined | ||
|
||
val owner = actualOwner(ctx.tasty)(ctx.tasty.rootContext.owner) | ||
val fullName = | ||
owner.fullName.trim | ||
.split("\\.", -1) | ||
.filterNot(Util.isSyntheticName) | ||
.map(cleanChunk) | ||
.mkString(".") | ||
'{FullName(${fullName.toExpr})} | ||
} | ||
|
||
def fullNameMachineImpl(given ctx: QuoteContext): Expr[FullName.Machine] = { | ||
import ctx.tasty.given | ||
val owner = ctx.tasty.rootContext.owner | ||
val fullName = owner.fullName.trim | ||
.split("\\.", -1) | ||
.map(_.stripPrefix("_$").stripSuffix("$")) // meh | ||
.map(adjustName) | ||
.mkString(".") | ||
'{FullName.Machine(${fullName.toExpr})} | ||
} | ||
|
||
def fileImpl(given ctx: QuoteContext): Expr[sourcecode.File] = { | ||
import ctx.tasty.given | ||
val file = ctx.tasty.rootPosition.sourceFile.jpath.toAbsolutePath.toString | ||
'{sourcecode.File(${file.toExpr})} | ||
} | ||
|
||
def fileNameImpl(given ctx: QuoteContext): Expr[sourcecode.FileName] = { | ||
import ctx.tasty.given | ||
val name = ctx.tasty.rootPosition.sourceFile.jpath.getFileName.toString | ||
'{sourcecode.FileName(${Expr(name)})} | ||
} | ||
|
||
def lineImpl(given ctx: QuoteContext): Expr[sourcecode.Line] = { | ||
import ctx.tasty.given | ||
val line = ctx.tasty.rootPosition.startLine + 1 | ||
'{sourcecode.Line(${line.toExpr})} | ||
} | ||
|
||
def enclosingImpl(given ctx: QuoteContext): Expr[Enclosing] = { | ||
val path = enclosing(ctx.tasty)( | ||
!Util.isSynthetic(ctx.tasty)(_) | ||
) | ||
|
||
'{Enclosing(${path.toExpr})} | ||
} | ||
|
||
def enclosingMachineImpl(given ctx: QuoteContext): Expr[Enclosing.Machine] = { | ||
val path = enclosing(ctx.tasty, machine = true)(_ => true) | ||
'{Enclosing.Machine(${path.toExpr})} | ||
} | ||
|
||
def pkgImpl(given ctx: QuoteContext): Expr[Pkg] = { | ||
import ctx.tasty.given | ||
val path = enclosing(ctx.tasty) { | ||
case s if s.isPackageDef => true | ||
case _ => false | ||
} | ||
|
||
'{Pkg(${path.toExpr})} | ||
} | ||
|
||
def argsImpl(given ctx: QuoteContext): Expr[Args] = { | ||
import ctx.tasty.{ _, given } | ||
|
||
val param: List[List[ctx.tasty.ValDef]] = { | ||
def nearestEnclosingMethod(owner: ctx.tasty.Symbol): List[List[ctx.tasty.ValDef]] = | ||
owner match { | ||
case defSym if defSym.isDefDef => | ||
defSym.tree.asInstanceOf[DefDef].paramss | ||
case classSym if classSym.isClassDef => | ||
classSym.tree.asInstanceOf[ClassDef].constructor.paramss | ||
case _ => | ||
nearestEnclosingMethod(owner.owner) | ||
} | ||
|
||
nearestEnclosingMethod(ctx.tasty.rootContext.owner) | ||
} | ||
|
||
val texts0 = param.map(_.foldRight('{List.empty[Text[_]]}) { | ||
case (vd @ ValDef(nme, _, optV), l) => | ||
'{Text(${optV.fold('None)(_.seal)}, ${nme.toExpr}) :: $l} | ||
}) | ||
val texts = texts0.foldRight('{List.empty[List[Text[_]]]}) { | ||
case (l, acc) => | ||
'{$l :: $acc} | ||
} | ||
|
||
'{Args($texts)} | ||
} | ||
|
||
|
||
def text[T: Type](v: Expr[T])(given ctx: QuoteContext): Expr[sourcecode.Text[T]] = { | ||
import ctx.tasty.given | ||
val txt = v.unseal.pos.sourceCode | ||
'{sourcecode.Text[T]($v, ${txt.toExpr})} | ||
} | ||
|
||
sealed trait Chunk | ||
object Chunk{ | ||
case class PkgObj(name: String) extends Chunk | ||
case class ClsTrt(name: String) extends Chunk | ||
case class ValVarLzyDef(name: String) extends Chunk | ||
|
||
} | ||
|
||
def enclosing(c: Reflection, machine: Boolean = false)(filter: c.Symbol => Boolean): String = { | ||
import c.{ _, given } | ||
|
||
var current = c.rootContext.owner | ||
if (!machine) | ||
current = actualOwner(c)(current) | ||
var path = List.empty[Chunk] | ||
while(current != Symbol.noSymbol && current != defn.RootPackage && current != defn.RootClass){ | ||
if (filter(current)) { | ||
|
||
val chunk = current match { | ||
case sym if | ||
sym.isValDef || sym.isDefDef => Chunk.ValVarLzyDef | ||
case sym if | ||
sym.isPackageDef || | ||
sym.moduleClass != Symbol.noSymbol => Chunk.PkgObj | ||
case sym if sym.isClassDef => Chunk.ClsTrt | ||
case _ => Chunk.PkgObj | ||
} | ||
|
||
path = chunk(Util.getName(c)(current).stripSuffix("$")) :: path | ||
} | ||
current = current.owner | ||
} | ||
path.map{ | ||
case Chunk.PkgObj(s) => adjustName(s) + "." | ||
case Chunk.ClsTrt(s) => adjustName(s) + "#" | ||
case Chunk.ValVarLzyDef(s) => adjustName(s) + " " | ||
}.mkString.dropRight(1) | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.