Skip to content

Commit 3f915ca

Browse files
Add dotty support
1 parent 23d11b3 commit 3f915ca

File tree

12 files changed

+333
-16
lines changed

12 files changed

+333
-16
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
- curl https://raw.githubusercontent.com/scala-native/scala-native/master/scripts/travis_setup.sh | bash -x
1717
- scala: 2.12.6
1818
- scala: 2.13.0-M5
19+
- scala: 0.10.0 # dotty
1920

2021
# Release stable release on tag push and snapshot on merge to master
2122
- stage: release

build.sbt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ val scala210 = "2.10.7"
55
val scala211 = "2.11.12"
66
val scala212 = "2.12.6"
77
val scala213 = "2.13.0-M5"
8+
val dotty = "0.10.0"
89

910
inThisBuild(List(
1011
organization := "com.lihaoyi",
@@ -37,7 +38,12 @@ def macroDependencies(version: String) =
3738

3839
lazy val sourcecode = crossProject(JSPlatform, JVMPlatform, NativePlatform)
3940
.settings(
40-
libraryDependencies ++= macroDependencies(scalaVersion.value),
41+
libraryDependencies ++= {
42+
CrossVersion.partialVersion(scalaVersion.value) match {
43+
case Some((2, _)) => macroDependencies(scalaVersion.value)
44+
case _ => Nil
45+
}
46+
},
4147
test in Test := (run in Test).toTask("").value,
4248
unmanagedSourceDirectories in Compile ++= {
4349
val crossVer = CrossVersion.partialVersion(scalaVersion.value)
@@ -49,14 +55,14 @@ lazy val sourcecode = crossProject(JSPlatform, JVMPlatform, NativePlatform)
4955
Seq()
5056
}
5157

52-
val scala2 = crossVer match {
58+
val scala2OrDotty = crossVer match {
5359
case Some((2, _)) =>
5460
Seq(baseDirectory.value / ".." / "shared" / "src" / "main" / "scala-2.x")
5561
case _ =>
56-
Seq()
62+
Seq(baseDirectory.value / ".." / "shared" / "src" / "main" / "dotty")
5763
}
5864

59-
scala211plus ++ scala2
65+
scala211plus ++ scala2OrDotty
6066
},
6167
// Osgi settings
6268
osgiSettings,
@@ -65,6 +71,9 @@ lazy val sourcecode = crossProject(JSPlatform, JVMPlatform, NativePlatform)
6571
dynamicImportPackage := Seq("*")
6672
)
6773
.enablePlugins(SbtOsgi)
74+
.jvmSettings(
75+
crossScalaVersions += dotty
76+
)
6877
.jsSettings(
6978
scalaJSUseMainModuleInitializer in Test := true // use JVM-style main.
7079
)

project/build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ val scalaJSVersion =
55
addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion)
66

77
addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.2.2")
8+
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.2.6")
89
addSbtPlugin("com.typesafe.sbt" % "sbt-osgi" % "0.9.4")
910
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.6.0")
1011
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.8")
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package sourcecode
2+
3+
object TestUtil {
4+
5+
val isDotty = false
6+
7+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package sourcecode
2+
3+
object TestUtil {
4+
5+
// FIXME In dotty, scala.util.Properties.versionNumberString is still like 2.12.x
6+
lazy val isDotty = {
7+
val cl: ClassLoader = Thread.currentThread().getContextClassLoader
8+
try {
9+
cl.loadClass("dotty.DottyPredef")
10+
true
11+
} catch {
12+
case _: ClassNotFoundException =>
13+
false
14+
}
15+
}
16+
17+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package sourcecode
2+
3+
object TestUtil {
4+
5+
val isDotty = false
6+
7+
}
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
package sourcecode
2+
3+
import scala.language.implicitConversions
4+
import scala.quoted.Exprs.TastyTreeExpr
5+
import scala.quoted.{Expr, LiftExprOps, Type}
6+
import scala.tasty.Tasty
7+
8+
trait NameMacros {
9+
inline implicit def generate: Name =
10+
~Macros.nameImpl
11+
}
12+
13+
trait NameMachineMacros {
14+
inline implicit def generate: Name.Machine =
15+
~Macros.nameMachineImpl
16+
}
17+
18+
trait FullNameMacros {
19+
inline implicit def generate: FullName =
20+
~Macros.fullNameImpl
21+
}
22+
23+
trait FullNameMachineMacros {
24+
inline implicit def generate: FullName.Machine =
25+
~Macros.fullNameMachineImpl
26+
}
27+
28+
trait FileMacros {
29+
inline implicit def generate: sourcecode.File =
30+
~Macros.fileImpl
31+
}
32+
33+
trait LineMacros {
34+
inline implicit def generate: sourcecode.Line =
35+
~Macros.lineImpl
36+
}
37+
38+
trait EnclosingMacros {
39+
inline implicit def generate: Enclosing =
40+
~Macros.enclosingImpl
41+
}
42+
43+
trait EnclosingMachineMacros {
44+
inline implicit def generate: Enclosing.Machine =
45+
~Macros.enclosingMachineImpl
46+
}
47+
48+
trait PkgMacros {
49+
inline implicit def generate: Pkg =
50+
~Macros.pkgImpl
51+
}
52+
53+
trait TextMacros {
54+
inline implicit def generate[T](v: => T): Text[T] = ~Macros.text('(v))
55+
inline def apply[T](v: => T): Text[T] = ~Macros.text('(v))
56+
}
57+
58+
trait ArgsMacros {
59+
inline implicit def generate: Args =
60+
~Macros.argsImpl
61+
}
62+
63+
object Util{
64+
def isSynthetic(c: Tasty)(s: c.Symbol) = isSyntheticName(getName(c)(s))
65+
def isSyntheticName(name: String) = {
66+
name == "<init>" || (name.startsWith("<local ") && name.endsWith(">"))
67+
}
68+
def getName(c: Tasty)(s: c.Symbol) = {
69+
import c._
70+
s.name.trim
71+
.stripSuffix("$") // meh
72+
}
73+
}
74+
75+
object Macros {
76+
77+
def actualOwner(c: Tasty)(owner: c.Symbol): c.Symbol = {
78+
import c._
79+
var owner0 = owner
80+
// second condition is meh
81+
while(Util.isSynthetic(c)(owner0) || Util.getName(c)(owner0) == "ev") {
82+
owner0 = owner0.owner
83+
}
84+
owner0
85+
}
86+
87+
def nameImpl(implicit c: Tasty): Expr[Name] = {
88+
import c._
89+
val owner = actualOwner(c)(c.rootContext.owner)
90+
val simpleName = Util.getName(c)(owner)
91+
'(Name(~simpleName.toExpr))
92+
}
93+
94+
private def adjustName(s: String): String =
95+
// Required to get the same name from dotty
96+
if (s.startsWith("<local ") && s.endsWith("$>"))
97+
s.stripSuffix("$>") + ">"
98+
else
99+
s
100+
101+
def nameMachineImpl(implicit c: Tasty): Expr[Name.Machine] = {
102+
import c._
103+
val owner = c.rootContext.owner
104+
val simpleName = adjustName(Util.getName(c)(owner))
105+
'(Name.Machine(~simpleName.toExpr))
106+
}
107+
108+
def fullNameImpl(implicit c: Tasty): Expr[FullName] = {
109+
import c._
110+
val owner = actualOwner(c)(c.rootContext.owner)
111+
val fullName =
112+
owner.fullName.trim
113+
.split("\\.", -1)
114+
.filterNot(Util.isSyntheticName)
115+
.map(_.stripPrefix("_$").stripSuffix("$")) // meh
116+
.mkString(".")
117+
'(FullName(~fullName.toExpr))
118+
}
119+
120+
def fullNameMachineImpl(implicit c: Tasty): Expr[FullName.Machine] = {
121+
import c._
122+
val owner = c.rootContext.owner
123+
val fullName = owner.fullName.trim
124+
.split("\\.", -1)
125+
.map(_.stripPrefix("_$").stripSuffix("$")) // meh
126+
.map(adjustName)
127+
.mkString(".")
128+
'(FullName.Machine(~fullName.toExpr))
129+
}
130+
131+
def fileImpl(implicit c: Tasty): Expr[sourcecode.File] = {
132+
import c._
133+
val file = c.rootPosition.sourceFile.toAbsolutePath.toString
134+
'(sourcecode.File(~file.toExpr))
135+
}
136+
137+
def lineImpl(implicit c: Tasty): Expr[sourcecode.Line] = {
138+
import c._
139+
val line = c.rootPosition.startLine + 1
140+
'(sourcecode.Line(~line.toExpr))
141+
}
142+
143+
def enclosingImpl(implicit c: Tasty): Expr[Enclosing] = {
144+
val path = enclosing(c)(
145+
!Util.isSynthetic(c)(_)
146+
)
147+
148+
'(Enclosing(~path.toExpr))
149+
}
150+
151+
def enclosingMachineImpl(implicit c: Tasty): Expr[Enclosing.Machine] = {
152+
val path = enclosing(c, machine = true)(_ => true)
153+
'(Enclosing.Machine(~path.toExpr))
154+
}
155+
156+
def pkgImpl(implicit c: Tasty): Expr[Pkg] = {
157+
import c._
158+
val path = enclosing(c)(
159+
// _.isPackage
160+
s => s.tree match {
161+
case Some(PackageDef(_)) => true
162+
case _ => false
163+
}
164+
)
165+
166+
'(Pkg(~path.toExpr))
167+
}
168+
169+
def argsImpl(implicit c: Tasty): Expr[Args] = {
170+
import c._
171+
172+
val param: List[List[c.ValDef]] = {
173+
def nearestEnclosingMethod(owner: c.Symbol): List[List[c.ValDef]] =
174+
owner.tree match {
175+
case Some(DefDef((_, _, paramss, _, _))) =>
176+
paramss
177+
case Some(ClassDef((_, constructor, _, _, _))) =>
178+
constructor.paramss
179+
case Some(ValDef(_, _, rhs)) =>
180+
nearestEnclosingMethod(owner.owner)
181+
case _ =>
182+
nearestEnclosingMethod(owner.owner)
183+
}
184+
185+
nearestEnclosingMethod(c.rootContext.owner)
186+
}
187+
188+
val texts0 = param.map(_.foldRight('(List.empty[Text[_]])) {
189+
case (vd @ ValDef(nme, _, optV), l) =>
190+
'(Text(~{optV.fold('(None))(v => new TastyTreeExpr(v))}, ~nme.toExpr) :: ~l)
191+
})
192+
val texts = texts0.foldRight('(List.empty[List[Text[_]]])) {
193+
case (l, acc) =>
194+
'(~l :: ~acc)
195+
}
196+
197+
'(Args(~texts))
198+
}
199+
200+
201+
def text[T: Type](v: Expr[T])(implicit c: Tasty): Expr[sourcecode.Text[T]] = {
202+
import c._
203+
import scala.quoted.Toolbox.Default._
204+
val txt = v.show
205+
'(sourcecode.Text[T](~v, ~txt.toExpr))
206+
}
207+
208+
sealed trait Chunk
209+
object Chunk{
210+
case class PkgObj(name: String) extends Chunk
211+
case class ClsTrt(name: String) extends Chunk
212+
case class ValVarLzyDef(name: String) extends Chunk
213+
214+
}
215+
216+
def enclosing(c: Tasty, machine: Boolean = false)(filter: c.Symbol => Boolean): String = {
217+
218+
import c._
219+
var current = c.rootContext.owner
220+
if (!machine)
221+
current = actualOwner(c)(current)
222+
var path = List.empty[Chunk]
223+
while(current.toString != "NoSymbol" && current != definitions.RootPackage && current != definitions.RootClass){
224+
if (filter(current)) {
225+
226+
val chunk = current.tree match {
227+
case Some(ValDef(_)) => Chunk.ValVarLzyDef
228+
case Some(DefDef(_)) => Chunk.ValVarLzyDef
229+
case _ => Chunk.PkgObj
230+
}
231+
232+
// TODO
233+
// val chunk = current match {
234+
// case x if x.flags.isPackage => Chunk.PkgObj
235+
// case x if x.flags.isModuleClass => Chunk.PkgObj
236+
// case x if x.flags.isClass && x.asClass.isTrait => Chunk.ClsTrt
237+
// case x if x.flags.isClass => Chunk.ClsTrt
238+
// case x if x.flags.isMethod => Chunk.ValVarLzyDef
239+
// case x if x.flags.isTerm && x.asTerm.isVar => Chunk.ValVarLzyDef
240+
// case x if x.flags.isTerm && x.asTerm.isLazy => Chunk.ValVarLzyDef
241+
// case x if x.flags.isTerm && x.asTerm.isVal => Chunk.ValVarLzyDef
242+
// }
243+
//
244+
// path = chunk(Util.getName(c)(current)) :: path
245+
246+
path = chunk(Util.getName(c)(current).stripSuffix("$")) :: path
247+
}
248+
current = current.owner
249+
}
250+
path.map{
251+
case Chunk.PkgObj(s) => adjustName(s) + "."
252+
case Chunk.ClsTrt(s) => adjustName(s) + "#"
253+
case Chunk.ValVarLzyDef(s) => adjustName(s) + " "
254+
}.mkString.dropRight(1)
255+
}
256+
}

sourcecode/shared/src/test/scala/sourcecode/Apply.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ object Apply {
4242
}
4343
val b = new Bar{}
4444
}
45-
myLazy
45+
myLazy // FIXME seems like this is not run on dotty
4646
}
4747
}

0 commit comments

Comments
 (0)