diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 191f993966cc..da65d980b706 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,6 +27,14 @@ on: - cron: '0 3 * * *' # Every day at 3 AM workflow_dispatch: +# Cancels any in-progress runs within the same group identified by workflow name and GH reference (branch or tag) +# For example it would: +# - terminate previous PR CI execution after pushing more changes to the same PR branch +# - terminate previous on-push CI run after merging new PR to main +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + env: DOTTY_CI_RUN: true diff --git a/tests/neg-macros/annot-crash.check b/tests/neg-macros/annot-crash.check index 0a5d573d2c0d..16eb0f68bc44 100644 --- a/tests/neg-macros/annot-crash.check +++ b/tests/neg-macros/annot-crash.check @@ -2,7 +2,7 @@ -- Error: tests/neg-macros/annot-crash/Test_2.scala:1:0 ---------------------------------------------------------------- 1 |@crash // error |^^^^^^ - |Failed to evaluate macro annotation '@crash'. + |Failed to evaluate macro. | Caused by class scala.NotImplementedError: an implementation is missing | scala.Predef$.$qmark$qmark$qmark(Predef.scala:344) | crash.transform(Macro_1.scala:7) diff --git a/tests/pos-macros/i20353/Macro_1.scala b/tests/pos-macros/i20353/Macro_1.scala index 5845a33ab144..1f9c80020938 100644 --- a/tests/pos-macros/i20353/Macro_1.scala +++ b/tests/pos-macros/i20353/Macro_1.scala @@ -16,7 +16,7 @@ end ImplicitValue @experimental class Test extends MacroAnnotation: - def transform(using Quotes)(definition: quotes.reflect.Definition, companion: Option[quotes.reflect.Definition]) = + def transform(using Quotes)(definition: quotes.reflect.Definition): List[quotes.reflect.Definition] = import quotes.reflect.* Implicits.search(TypeRepr.of[ImplicitValue]) List(definition) diff --git a/tests/pos-with-compiler/A.scala b/tests/pos-with-compiler/A.scala new file mode 100644 index 000000000000..bab94e771eb6 --- /dev/null +++ b/tests/pos-with-compiler/A.scala @@ -0,0 +1,6 @@ +import dotty.tools.dotc.core.Decorators.* +import dotty.tools.dotc.core.NameOps.* + +object Test { + "JFunction".toTermName.specializedFor(Nil, ???, Nil, Nil)(using ???) +} diff --git a/tests/pos-with-compiler/B.scala b/tests/pos-with-compiler/B.scala new file mode 100644 index 000000000000..1cdd19a9c2a3 --- /dev/null +++ b/tests/pos-with-compiler/B.scala @@ -0,0 +1,6 @@ +import dotty.tools.dotc.core.Contexts.Context + +object Formatting { + def rainbows(implicit ctx: Context): String = + ctx.settings.color.value.toString +} diff --git a/tests/pos-with-compiler/Fileish.scala b/tests/pos-with-compiler/Fileish.scala new file mode 100644 index 000000000000..8ba62efb3298 --- /dev/null +++ b/tests/pos-with-compiler/Fileish.scala @@ -0,0 +1,53 @@ +// Inspired by the original Fileish, +// testing combinations of lazy and non-lazy vals for their treatment in constructors +package dotty.tools +package io + +import java.io.InputStream +import java.util.jar.JarEntry +import language.postfixOps + +/** A common interface for File-based things and Stream-based things. + * (In particular, io.File and JarEntry.) + */ +class Fileish(val path: Path, val input: () => InputStream) extends Streamable.Chars { + def inputStream() = input() + + def parent = path.parent + def name = path.name + def isSourceFile = path.hasExtension("java", "scala") + + private lazy val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim } + lazy val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".") + lazy val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "." + + override def toString = path.path +} +class Fileish2(val path: Path, val input: () => InputStream) extends Streamable.Chars { + def inputStream() = input() + + def parent = path.parent + def name = path.name + def isSourceFile = path.hasExtension("java", "scala") + + private val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim } + lazy val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".") + lazy val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "." + + override def toString = path.path +} + +class Fileish3(val path: Path, val input: () => InputStream) extends Streamable.Chars { + def inputStream() = input() + + def parent = path.parent + def name = path.name + def isSourceFile = path.hasExtension("java", "scala") + + private val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim } + private val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".") + private val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "." + + override def toString = path.path +} + diff --git a/tests/pos-with-compiler/Labels.scala b/tests/pos-with-compiler/Labels.scala new file mode 100644 index 000000000000..a01141cd7d53 --- /dev/null +++ b/tests/pos-with-compiler/Labels.scala @@ -0,0 +1,38 @@ +import dotty.tools.dotc.ast.Trees.Thicket +import dotty.tools.dotc.ast.tpd.* + + +object Labels { + def main(args: Array[String]): Unit = { + var i = 10 + while(i>0) { + var j = 0 + while(j0) => println("one") + case t@2 => println("two" + t) + case _ => println("default") + } + + def flatten(trees: Tree): Int = { + trees match { + case Thicket(elems) => + while (trees ne trees) { + } + case tree => + 33 + } + 55 + } + + +} diff --git a/tests/pos-with-compiler/Patterns.scala b/tests/pos-with-compiler/Patterns.scala new file mode 100644 index 000000000000..6492ce6f8c72 --- /dev/null +++ b/tests/pos-with-compiler/Patterns.scala @@ -0,0 +1,137 @@ +import dotty.tools.dotc.ast.Trees.* +import dotty.tools.dotc.core.Types.* + +object Patterns { + val d: Object = null + private def rebase(tp: NamedType): Type = { + def rebaseFrom(prefix: Type): Type = ??? + tp.prefix match { + case SkolemType(rt) => rebaseFrom(rt) + case pre: ThisType => rebaseFrom(pre) + case _ => tp + } + } + d match { + case WildcardType(bounds: TypeBounds) => bounds.lo + case a @ Assign(Ident(id), rhs) => id + case a: Object => a + } + + ('1', "1") match { + case (digit, str) => true + case _ => false + } + + + def foo2(x: AnyRef) = x match { case x: Function0[Any] => x() } + object Breakdown { + def unapplySeq(x: Int): Some[List[String]] = Some(List("", "there")) + } + + object Test2 { + 42 match { + case a@Breakdown(f@"") => // needed to trigger bug + case b@Breakdown(d@"foo") => // needed to trigger bug + case c@Breakdown(e@"", who) => println ("hello " + who) + } + } + + val names = List("a", "b", "c") + object SeqExtractors { + val y = names match { + case List(x, z) => x + case List(x) => x + case List() => "" + case x @ _ => "wildcard" + } + val yy: String = y + } + + + + val xs = List('2' -> "ABC", '3' -> "DEF") + + xs filter { + case (digit, str) => true + case _ => false + } + + (xs: Any) match { + case x: Int @unchecked => true + case xs: List[Int @ unchecked] => true + case _ => false + } + + def sum(xs: List[Int]): Int = xs match { + case Nil => 0 + case x :: xs1 => x + sum(xs1) + } + + def len[T](xs: List[T]): Int = xs match { + case _ :: xs1 => 1 + len(xs1) + case Nil => 0 + } + + final def sameLength[T](xs: List[T], ys: List[T]): Boolean = xs match { + case _ :: xs1 => xs1.isEmpty + ys match { + case _ :: ys1 => sameLength(xs1, ys1) + case _ => false + } + case _ => ys.isEmpty + } + + class A{ + class B + } + val a1 = new A + val a2 = new A + d match { + case t: a1.B => + t + case t: a2.B => + t + } + + class caseWithPatternVariableHelper1[A] + class caseWithPatternVariableHelper2[A] + + def caseWithPatternVariable(x: Any) = x match { + case a: caseWithPatternVariableHelper1[_] => () + case b: caseWithPatternVariableHelper2[_] => () + } + +} + +object NestedPattern { + val xss: List[List[String]] = ??? + val List(List(x)) = xss +} + +// Tricky case (exercised by Scala parser combinators) where we use +// both get/isEmpty and product-based pattern matching in different +// matches on the same types. +object ProductAndGet { + + trait Result[+T] + case class Success[+T](in: String, x: T) extends Result[T] { + def isEmpty = false + def get: T = x + } + case class Failure[+T](in: String, msg: String) extends Result[T] { + def isEmpty = false + def get: String = msg + } + + val r: Result[Int] = ??? + + r match { + case Success(in, x) => x + case Failure(in, msg) => -1 + } + + r match { + case Success(x) => x + case Failure(msg) => -1 + } +} diff --git a/tests/pos-with-compiler/benchSets.scala b/tests/pos-with-compiler/benchSets.scala new file mode 100644 index 000000000000..8619318956f9 --- /dev/null +++ b/tests/pos-with-compiler/benchSets.scala @@ -0,0 +1,192 @@ +type Elem = Object + +def newElem() = new Object() + +val MissFactor = 2 + +val Runs = 100 // number of runs to warm-up, and then number of runs to test +val ItersPerRun = 1000 +val elems = Array.fill(1024 * MissFactor)(newElem()) + +def testJava = + val set = java.util.HashMap[Elem, Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set.put(e, e) + i += 1 + while i > 0 do + i -= 1 + val v = set.get(elems(i)) + if v != null then + count += 1 + iter += 1 + count + +def testJavaId = + val set = java.util.IdentityHashMap[Elem, Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set.put(e, e) + i += 1 + while i > 0 do + i -= 1 + val v = set.get(elems(i)) + if v != null then + count += 1 + iter += 1 + count + +def testScalaMap = + val set = scala.collection.mutable.HashMap[Elem, Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set.update(e, e) + i += 1 + while i > 0 do + i -= 1 + set.get(elems(i)) match + case Some(_) => count += 1 + case None => + iter += 1 + count + +def testAnyRefMap = + val set = scala.collection.mutable.AnyRefMap[Elem, Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set.update(e, e) + i += 1 + while i > 0 do + i -= 1 + val v = set.getOrNull(elems(i)) + if v != null then + count += 1 + iter += 1 + count + +def testDottyMap = + val set = dotty.tools.dotc.util.HashMap[Elem, Elem](128) + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set.update(e, e) + i += 1 + while i > 0 do + i -= 1 + val v = set.lookup(elems(i)) + if v != null then + count += 1 + iter += 1 + count + +def testDottySet = + val set = dotty.tools.dotc.util.HashSet[Elem](64) + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set += e + i += 1 + while i > 0 do + i -= 1 + if set.contains(elems(i)) then + count += 1 + iter += 1 + count + +def testScalaSet = + val set = scala.collection.mutable.HashSet[Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set += e + i += 1 + while i > 0 do + i -= 1 + if set.contains(elems(i)) then + count += 1 + iter += 1 + count + +def testLinearSet = + var set = dotty.tools.dotc.util.LinearSet.empty[Elem] + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set += e + i += 1 + while i > 0 do + i -= 1 + if set.contains(elems(i)) then + count += 1 + iter += 1 + count + +val expected = (elems.size / MissFactor) * ItersPerRun + +def profile(name: String, op: => Int) = + System.gc() + for i <- 0 until 100 do assert(op == expected) + val start = System.nanoTime() + var count = 0 + for i <- 0 until 100 do count += op + //println(count) + assert(count == expected * Runs) + println(s"$name took ${(System.nanoTime().toDouble - start)/1_000_000} ms") + +@main def Test = + + profile("dotty.tools.dotc.LinearSet", testLinearSet) + profile("dotty.tools.dotc.HashSet ", testDottySet) + profile("dotty.tools.dotc.HashMap ", testDottyMap) + profile("scala.collection.HashSet ", testScalaSet) + profile("scala.collection.AnyRefMap", testAnyRefMap) + profile("scala.collection.HashMap ", testScalaMap) + profile("java.util.IdentityHashMap ", testJavaId) + profile("java.util.HashMap ", testJava) + + profile("java.util.HashMap ", testJava) + profile("java.util.IdentityHashMap ", testJavaId) + profile("scala.collection.HashMap ", testScalaMap) + profile("scala.collection.AnyRefMap", testAnyRefMap) + profile("scala.collection.HashSet ", testScalaSet) + profile("dotty.tools.dotc.HashMap ", testDottyMap) + profile("dotty.tools.dotc.HashSet ", testDottySet) + profile("dotty.tools.dotc.LinearSet", testLinearSet) + + diff --git a/tests/pos-with-compiler/i143.scala b/tests/pos-with-compiler/i143.scala new file mode 100644 index 000000000000..bd49cd21a963 --- /dev/null +++ b/tests/pos-with-compiler/i143.scala @@ -0,0 +1,12 @@ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.core.Denotations.* +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.Contexts.* + +class TC5(val ctx: Context) extends AnyVal { + def candidates(mbr: SingleDenotation): Boolean = { + mbr.symbol.denot(using ctx).exists + } +} diff --git a/tests/pos-with-compiler/lazyValsSepComp.scala b/tests/pos-with-compiler/lazyValsSepComp.scala new file mode 100644 index 000000000000..c7e97dc12142 --- /dev/null +++ b/tests/pos-with-compiler/lazyValsSepComp.scala @@ -0,0 +1,16 @@ +package dotty.tools +package io + +import java.io.InputStream +import java.util.jar.JarEntry +import dotty.tools.dotc.core.Definitions +import language.postfixOps +import dotty.tools.dotc.core.Contexts.* + + +/** A test to trigger issue with separate compilation and lazy vals */ +object Foo { + val definitions: Definitions = null + def defn = definitions + def go = defn.ScalaBoxedClasses +} diff --git a/tests/pos-with-compiler/tasty/test-definitions.scala b/tests/pos-with-compiler/tasty/test-definitions.scala new file mode 100644 index 000000000000..f2b6232a8d5e --- /dev/null +++ b/tests/pos-with-compiler/tasty/test-definitions.scala @@ -0,0 +1,460 @@ +package tasty + +object definitions { + +// ====== Trees ====================================== + + sealed trait Tree // Top level statement + +// ------ Statements --------------------------------- + + sealed trait Statement extends Tree + + case class PackageClause(pkg: Term, body: List[Tree]) extends Tree + + case class Import(expr: Term, selector: List[ImportSelector]) extends Statement + + enum ImportSelector { + case SimpleSelector(id: Id) + case RenameSelector(id1: Id, id2: Id) + case OmitSelector(id1: Id) + } + + case class Id(name: String) extends Positioned // untyped ident + +// ------ Definitions --------------------------------- + + trait Definition { + def name: String + def owner: Definition = ??? + } + + // Does DefDef need a `def tpe: MethodType | PolyType`? + case class ValDef(name: String, tpt: TypeTree, rhs: Option[Term]) extends Definition { + def flags: FlagSet = ??? + def privateWithin: Option[Type] = ??? + def protectedWithin: Option[Type] = ??? + def annots: List[Term] = ??? + } + case class DefDef(name: String, typeParams: List[TypeDef], paramss: List[List[ValDef]], + returnTpt: TypeTree, rhs: Option[Term]) extends Definition { + def flags: FlagSet = ??? + def privateWithin: Option[Type] = ??? + def protectedWithin: Option[Type] = ??? + def annots: List[Term] = ??? + } + case class TypeDef(name: String, rhs: TypeTree | TypeBoundsTree) extends Definition { + def flags: FlagSet = ??? + def privateWithin: Option[Type] = ??? + def protectedWithin: Option[Type] = ??? + def annots: List[Term] = ??? + } + case class ClassDef(name: String, constructor: DefDef, parents: List[Term | TypeTree], + self: Option[ValDef], body: List[Statement]) extends Definition { + def flags: FlagSet = ??? + def privateWithin: Option[Type] = ??? + def protectedWithin: Option[Type] = ??? + def annots: List[Term] = ??? + } + case class PackageDef(name: String, override val owner: PackageDef) extends Definition { + def members: List[Statement] = ??? + } + +// ------ Terms --------------------------------- + + /** Trees denoting terms */ + enum Term extends Statement { + def tpe: Type = ??? + case Ident(name: String, override val tpe: Type) + case Select(prefix: Term, name: String, signature: Option[Signature]) + case Literal(value: Constant) + case This(id: Option[Id]) + case New(tpt: TypeTree) + case Throw(expr: Term) + case NamedArg(name: String, arg: Term) + case Apply(fn: Term, args: List[Term]) + case TypeApply(fn: Term, args: List[TypeTree]) + case Super(thiz: Term, mixin: Option[Id]) + case Typed(expr: Term, tpt: TypeTree) + case Assign(lhs: Term, rhs: Term) + case Block(stats: List[Statement], expr: Term) + case Inlined(call: Option[Term], bindings: List[Definition], expr: Term) + case Lambda(method: Term, tpt: Option[TypeTree]) + case If(cond: Term, thenPart: Term, elsePart: Term) + case Match(scrutinee: Term, cases: List[CaseDef]) + case Try(body: Term, catches: List[CaseDef], finalizer: Option[Term]) + case Return(expr: Term) + case Repeated(args: List[Term]) + case SelectOuter(from: Term, levels: Int, target: Type) // can be generated by inlining + case While(cond: Term, body: Term) + } + + /** Trees denoting types */ + enum TypeTree extends Positioned { + def tpe: Type = ??? + case Synthetic() + case Ident(name: String, override val tpe: Type) + case TermSelect(prefix: Term, name: String) + case TypeSelect(prefix: TypeTree, name: String) + case Singleton(ref: Term) + case Refined(underlying: TypeTree, refinements: List[Definition]) + case Applied(tycon: TypeTree, args: List[TypeTree | TypeBoundsTree]) + case Annotated(tpt: TypeTree, annotation: Term) + case And(left: TypeTree, right: TypeTree) + case Or(left: TypeTree, right: TypeTree) + case ByName(tpt: TypeTree) + case TypeLambda(tparams: List[TypeDef], body: Type | TypeBoundsTree) + case Bind(name: String, bounds: TypeBoundsTree) + } + + /** Trees denoting type bounds */ + case class TypeBoundsTree(loBound: TypeTree, hiBound: TypeTree) extends Tree { + def tpe: Type.TypeBounds = ??? + } + + /** Trees denoting type inferred bounds */ + case class SyntheticBounds() extends Tree { + def tpe: Type.TypeBounds = ??? + } + + /** Trees denoting patterns */ + enum Pattern extends Positioned { + def tpe: Type = ??? + case Value(v: Term) + case Bind(name: String, pat: Pattern) + case Unapply(unapply: Term, implicits: List[Term], pats: List[Pattern]) + case Alternative(pats: List[Pattern]) + case TypeTest(tpt: TypeTree) + } + + /** Tree denoting pattern match case */ + case class CaseDef(pat: Pattern, guard: Option[Term], rhs: Term) extends Tree + +// ====== Types ====================================== + + sealed trait Type + + object Type { + private val PlaceHolder = ConstantType(Constant.Unit) + + case class ConstantType(value: Constant) extends Type + case class SymRef(sym: Definition, qualifier: Type | NoPrefix = NoPrefix) extends Type + case class TypeRef(name: String, qualifier: Type | NoPrefix = NoPrefix) extends Type // NoPrefix means: select from _root_ + case class TermRef(name: String, qualifier: Type | NoPrefix = NoPrefix) extends Type // NoPrefix means: select from _root_ + case class SuperType(thistp: Type, underlying: Type) extends Type + case class Refinement(underlying: Type, name: String, tpe: Type | TypeBounds) extends Type + case class AppliedType(tycon: Type, args: List[Type | TypeBounds]) extends Type + case class AnnotatedType(underlying: Type, annotation: Term) extends Type + case class AndType(left: Type, right: Type) extends Type + case class OrType(left: Type, right: Type) extends Type + case class ByNameType(underlying: Type) extends Type + case class ParamRef(binder: LambdaType[_, _], idx: Int) extends Type + case class ThisType(tp: Type) extends Type + case class RecursiveThis(binder: RecursiveType) extends Type + + case class RecursiveType private (private var _underlying: Type) extends Type { + def underlying = _underlying + } + object RecursiveType { + def apply(underlyingExp: RecursiveType => Type) = { + val rt = new RecursiveType(PlaceHolder) {} + rt._underlying = underlyingExp(rt) + rt + } + } + + abstract class LambdaType[ParamInfo, This <: LambdaType[ParamInfo, This]] + extends Type { + val companion: LambdaTypeCompanion[ParamInfo, This] + private[Type] var _pinfos: List[ParamInfo] + private[Type] var _restpe: Type + + def paramNames: List[String] + def paramInfos: List[ParamInfo] = _pinfos + def resultType: Type = _restpe + } + + abstract class LambdaTypeCompanion[ParamInfo, This <: LambdaType[ParamInfo, This]] { + def apply(pnames: List[String], ptypes: List[ParamInfo], restpe: Type): This + + def apply(pnames: List[String], ptypesExp: This => List[ParamInfo], restpeExp: This => Type): This = { + val lambda = apply(pnames, Nil, PlaceHolder) + lambda._pinfos = ptypesExp(lambda) + lambda._restpe = restpeExp(lambda) + lambda + } + } + + case class MethodType(paramNames: List[String], private[Type] var _pinfos: List[Type], private[Type] var _restpe: Type) + extends LambdaType[Type, MethodType] { + override val companion = MethodType + def isImplicit = (companion `eq` ImplicitMethodType) || (companion `eq` ErasedImplicitMethodType) + def isErased = (companion `eq` ErasedMethodType) || (companion `eq` ErasedImplicitMethodType) + } + + case class PolyType(paramNames: List[String], private[Type] var _pinfos: List[TypeBounds], private[Type] var _restpe: Type) + extends LambdaType[TypeBounds, PolyType] { + override val companion = PolyType + } + + case class TypeLambda(paramNames: List[String], private[Type] var _pinfos: List[TypeBounds], private[Type] var _restpe: Type) + extends LambdaType[TypeBounds, TypeLambda] { + override val companion = TypeLambda + } + + object TypeLambda extends LambdaTypeCompanion[TypeBounds, TypeLambda] + object PolyType extends LambdaTypeCompanion[TypeBounds, PolyType] + object MethodType extends LambdaTypeCompanion[Type, MethodType] + + class SpecializedMethodTypeCompanion extends LambdaTypeCompanion[Type, MethodType] { self => + def apply(pnames: List[String], ptypes: List[Type], restpe: Type): MethodType = + new MethodType(pnames, ptypes, restpe) { override val companion = self } + } + object ImplicitMethodType extends SpecializedMethodTypeCompanion + object ErasedMethodType extends SpecializedMethodTypeCompanion + object ErasedImplicitMethodType extends SpecializedMethodTypeCompanion + + case class TypeBounds(loBound: Type, hiBound: Type) + + case class NoPrefix() + object NoPrefix extends NoPrefix + } + +// ====== Modifiers ================================== + + enum Modifier { + case Flags(flags: FlagSet) + case QualifiedPrivate(boundary: Type) + case QualifiedProtected(boundary: Type) + case Annotation(tree: Term) + } + + trait FlagSet { + def isProtected: Boolean + def isAbstract: Boolean + def isFinal: Boolean + def isSealed: Boolean + def isCase: Boolean + def isImplicit: Boolean + def isErased: Boolean + def isLazy: Boolean + def isOverride: Boolean + def isInline: Boolean + def isMacro: Boolean // inline method containing toplevel splices + def isStatic: Boolean // mapped to static Java member + def isObject: Boolean // an object or its class (used for a ValDef or a ClassDef extends Modifier respectively) + def isTrait: Boolean // a trait (used for a ClassDef) + def isLocal: Boolean // used in conjunction with Private/private[Type] to mean private[this] extends Modifier protected[this] + def isSynthetic: Boolean // generated by Scala compiler + def isArtifact: Boolean // to be tagged Java Synthetic + def isMutable: Boolean // when used on a ValDef: a var + def isLabel: Boolean // method generated as a label + def isFieldAccessor: Boolean // a getter or setter + def isCaseAccessor: Boolean // getter for class parameter + def isCovariant: Boolean // type parameter marked “+” + def isContravariant: Boolean // type parameter marked “-” + def isScala2X: Boolean // Imported from Scala2.x + def hasDefault: Boolean // Parameter with default + def isStable: Boolean // Method that is assumed to be stable + } + + case class Signature(paramSigs: List[String], resultSig: String) + +// ====== Positions ================================== + + case class Position(firstOffset: Int, lastOffset: Int, sourceFile: String) { + def startLine: Int = ??? + def startColumn: Int = ??? + def endLine: Int = ??? + def endColumn: Int = ??? + } + + trait Positioned { + def pos: Position = ??? + } + +// ====== Constants ================================== + + enum Constant(val value: Any) { + case Unit extends Constant(()) + case Null extends Constant(null) + case Boolean(v: scala.Boolean) extends Constant(v) + case Byte(v: scala.Byte) extends Constant(v) + case Short(v: scala.Short) extends Constant(v) + case Char(v: scala.Char) extends Constant(v) + case Int(v: scala.Int) extends Constant(v) + case Long(v: scala.Long) extends Constant(v) + case Float(v: scala.Float) extends Constant(v) + case Double(v: scala.Double) extends Constant(v) + case String(v: java.lang.String) extends Constant(v) + case Class(v: Type) extends Constant(v) + case Enum(v: Type) extends Constant(v) + } +} + +// --- A sample extractor ------------------ + +// The abstract class, that's what we export to macro users +abstract class Tasty { + + type Type + trait TypeAPI { + // exported type fields + } + implicit def TypeDeco(x: Type): TypeAPI + + type Symbol + trait SymbolAPI { + // exported symbol fields + } + implicit def SymbolDeco(s: Symbol): SymbolAPI + + type Context + trait ContextAPI { + val owner: Symbol + // more exported fields + } + implicit def ContextDeco(x: Context): ContextAPI + + type Position + trait PositionAPI { + val start: Int + val end: Int + // more fields + } + implicit def PositionDeco(p: Position): PositionAPI + + trait TypedPositioned { + val pos: Position + val tpe: Type + } + + type Pattern + implicit def PatternDeco(p: Pattern): TypedPositioned + + type Term + implicit def TermDeco(t: Term): TypedPositioned + + type CaseDef + implicit def CaseDefDeco(c: CaseDef): TypedPositioned + + val CaseDef: CaseDefExtractor + abstract class CaseDefExtractor { + def apply(pat: Pattern, guard: Term, rhs: Term)(implicit ctx: Context): CaseDef + def unapply(x: CaseDef): Some[(Pattern, Term, Term)] + } + // and analogously for all other concrete trees, patterns, types, etc +} + +// The concrete implementation - hidden from users. +object ReflectionImpl extends Tasty { + import definitions.* + import dotty.tools.dotc.* + import ast.tpd + import core.{Types, Symbols, Contexts} + import util.Spans + + type Type = Types.Type + implicit class TypeDeco(x: Type) extends TypeAPI {} + + type Symbol = Symbols.Symbol + implicit class SymbolDeco(s: Symbol) extends SymbolAPI {} + + type Context = Contexts.Context + implicit class ContextDeco(c: Context) extends ContextAPI { + val owner = c.owner + } + + type Position = Spans.Span + implicit class PositionDeco(p: Position) extends PositionAPI { + val start = p.start + val end = p.end + } + + type Pattern = tpd.Tree + implicit class PatternDeco(p: Pattern) extends TypedPositioned { + val pos = p.span + val tpe = p.tpe + } + + type Term = tpd.Tree + implicit class TermDeco(t: Term) extends TypedPositioned { + val pos = t.span + val tpe = t.tpe + } + + type CaseDef = tpd.CaseDef + implicit class CaseDefDeco(c: CaseDef) extends TypedPositioned { + val pos = c.span + val tpe = c.tpe + } + + object CaseDef extends CaseDefExtractor { + def apply(pat: Pattern, guard: Term, rhs: Term)(implicit ctx: Context): CaseDef = + tpd.CaseDef(pat, guard, rhs) + def unapply(x: CaseDef): Some[(Pattern, Term, Term)] = + Some((x.pat, x.guard, x.body)) + } +} + +/* Dependencies: + + the reflect library (which is probably part of stdlib) contains a + + val tasty: Tasty + + this val is implemented reflectively, loading ReflectionImpl on demand. ReflectionImpl in turn + depends on `tools.dotc`. + +*/ + + +/* If the dotty implementations all inherit the ...API traits, + and the API traits inherit thmeselves from ProductN, we can + also do the following, faster implementation. + This still does full information hiding, but should be almost + as fast as native access. + +object ReflectionImpl extends TastyAST { + import definitions.* + import dotty.tools.dotc.* + import ast.tpd + import core.{Types, Symbols, Contexts} + import util.{Positions} + + type Type = Types.Type + implicit def TypeDeco(x: Type) = x + + type Symbol = Symbols.Symbol + implicit def SymbolDeco(s: Symbol) = s + + type Context = Contexts.Context + implicit def ContextDeco(c: Context) = c + + type Position = Positions.Position + implicit def PositionDeco(p: Position) = p + + type Pattern = tpd.Tree + implicit def PatternDeco(p: Pattern) = p + + type Term = tpd.Tree + implicit def TermDeco(t: Term) = t + + type CaseDef = tpd.CaseDef + implicit def CaseDefDeco(c: CaseDef) = c + + object CaseDef extends CaseDefExtractor { + def apply(pat: Pattern, guard: Term, rhs: Term)(implicit ctx: Context): CaseDef = + tpd.CaseDef(pat, guard, rhs) + def unapply(x: CaseDef): CaseDefAPI = x + } +} + +This approach is fast because all accesses work without boxing. But there are also downsides: + +1. The added reflect supertypes for the dotty types might have a negative performance + impact for normal compilation. + +2. There would be an added dependency from compiler to reflect library, which + complicates things. +*/