diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/FlagsOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/FlagsOpsImpl.scala index a65357d20b32..d71935e79a50 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/FlagsOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/FlagsOpsImpl.scala @@ -40,7 +40,7 @@ trait FlagsOpsImpl extends scala.tasty.reflect.FlagsOps with CoreImpl { def Contravariant: Flags = core.Flags.Contravariant def Scala2X: Flags = core.Flags.Scala2x def DefaultParameterized: Flags = core.Flags.DefaultParameterized - def Stable: Flags = core.Flags.StableRealizable + def StableRealizable: Flags = core.Flags.StableRealizable def Param: Flags = core.Flags.Param def ParamAccessor: Flags = core.Flags.ParamAccessor } diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala index 9f57f543effe..824f003663c1 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala @@ -76,6 +76,10 @@ trait SymbolOpsImpl extends scala.tasty.reflect.SymbolOps with CoreImpl { } } + def isDefinedInCurrentRun(implicit ctx: Context): Boolean = { + symbol.topLevelClass.asClass.isDefinedInCurrentRun + } + } object IsPackageSymbol extends IsPackageSymbolModule { @@ -171,6 +175,10 @@ trait SymbolOpsImpl extends scala.tasty.reflect.SymbolOps with CoreImpl { def DefSymbolDeco(symbol: DefSymbol): DefSymbolAPI = new DefSymbolAPI { def tree(implicit ctx: Context): DefDef = FromSymbol.defDefFromSym(symbol) + + def signature(implicit ctx: Context): Signature = { + symbol.signature + } } object IsValSymbol extends IsValSymbolModule { @@ -181,6 +189,11 @@ trait SymbolOpsImpl extends scala.tasty.reflect.SymbolOps with CoreImpl { def ValSymbolDeco(symbol: ValSymbol): ValSymbolAPI = new ValSymbolAPI { def tree(implicit ctx: Context): ValDef = FromSymbol.valDefFromSym(symbol) + def moduleClass(implicit ctx: Context): Option[ClassSymbol] = { + val sym = symbol.moduleClass + if (sym.exists) Some(sym.asClass) else None + } + def companionClass(implicit ctx: Context): Option[ClassSymbol] = { val sym = symbol.companionClass if (sym.exists) Some(sym.asClass) else None diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsOpsImpl.scala index 5050997b036b..780040bbfae1 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsOpsImpl.scala @@ -19,6 +19,8 @@ trait TypeOrBoundsOpsImpl extends scala.tasty.reflect.TypeOrBoundsOps with CoreI */ def widen(implicit ctx: Context): Type = tpe.widen + def classSymbol(implicit ctx: Context): Option[ClassSymbol] = + if (tpe.classSymbol.exists) Some(tpe.classSymbol.asClass) else None } def ConstantTypeDeco(x: ConstantType): Type.ConstantTypeAPI = new Type.ConstantTypeAPI { diff --git a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala index be9cffb8a598..4785c67f4751 100644 --- a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala @@ -81,6 +81,7 @@ class BootstrappedOnlyCompilationTests extends ParallelTesting { @Test def runWithCompiler: Unit = { implicit val testGroup: TestGroup = TestGroup("runWithCompiler") compileFilesInDir("tests/run-with-compiler", withCompilerOptions) + + compileDir("tests/run-with-compiler-custom-args/tasty-interpreter", withCompilerOptions) + compileFile("tests/run-with-compiler-custom-args/staged-streams_1.scala", withCompilerOptions without "-Yno-deep-subtypes") }.checkRuns() diff --git a/library/src/scala/tasty/reflect/FlagsOps.scala b/library/src/scala/tasty/reflect/FlagsOps.scala index d75c72cf2db7..eafef4cf3d01 100644 --- a/library/src/scala/tasty/reflect/FlagsOps.scala +++ b/library/src/scala/tasty/reflect/FlagsOps.scala @@ -93,8 +93,8 @@ trait FlagsOps extends Core { /** Is this symbol a method with default parameters */ def DefaultParameterized: Flags - /** Is this symbol member that is assumed to be stable */ - def Stable: Flags + /** Is this symbol member that is assumed to be stable and realizable */ + def StableRealizable: Flags /** Is this symbol a parameter */ def Param: Flags diff --git a/library/src/scala/tasty/reflect/Printers.scala b/library/src/scala/tasty/reflect/Printers.scala index 6486334ef52a..cc5b827d7f01 100644 --- a/library/src/scala/tasty/reflect/Printers.scala +++ b/library/src/scala/tasty/reflect/Printers.scala @@ -126,7 +126,7 @@ trait Printers if (flags.is(Flags.Contravariant)) flagList += "Flags.Contravariant" if (flags.is(Flags.Scala2X)) flagList += "Flags.Scala2X" if (flags.is(Flags.DefaultParameterized)) flagList += "Flags.DefaultParameterized" - if (flags.is(Flags.Stable)) flagList += "Flags.Stable" + if (flags.is(Flags.StableRealizable)) flagList += "Flags.StableRealizable" if (flags.is(Flags.Param)) flagList += "Flags.Param" if (flags.is(Flags.ParamAccessor)) flagList += "Flags.ParamAccessor" flagList.result().mkString(" | ") @@ -501,7 +501,7 @@ trait Printers if (flags.is(Flags.Contravariant)) flagList += "contravariant" if (flags.is(Flags.Scala2X)) flagList += "scala2x" if (flags.is(Flags.DefaultParameterized)) flagList += "defaultParameterized" - if (flags.is(Flags.Stable)) flagList += "stable" + if (flags.is(Flags.StableRealizable)) flagList += "stableRealizable" if (flags.is(Flags.Param)) flagList += "param" if (flags.is(Flags.ParamAccessor)) flagList += "paramAccessor" flagList.result().mkString("/*", " ", "*/") diff --git a/library/src/scala/tasty/reflect/SymbolOps.scala b/library/src/scala/tasty/reflect/SymbolOps.scala index e1915c9da180..cd69466efdf7 100644 --- a/library/src/scala/tasty/reflect/SymbolOps.scala +++ b/library/src/scala/tasty/reflect/SymbolOps.scala @@ -51,6 +51,7 @@ trait SymbolOps extends Core { /** Annotations attached to this symbol */ def annots(implicit ctx: Context): List[Term] + def isDefinedInCurrentRun(implicit ctx: Context): Boolean } implicit def SymbolDeco(symbol: Symbol): SymbolAPI @@ -121,7 +122,7 @@ trait SymbolOps extends Core { } trait TypeSymbolAPI { - /** TypeDef tree of this defintion. */ + /** TypeDef tree of this definition. */ def tree(implicit ctx: Context): TypeDef } implicit def TypeSymbolDeco(symbol: TypeSymbol): TypeSymbolAPI @@ -136,6 +137,8 @@ trait SymbolOps extends Core { trait DefSymbolAPI { /** DefDef tree of this defintion. */ def tree(implicit ctx: Context): DefDef + + def signature(implicit ctx: Context): Signature } implicit def DefSymbolDeco(symbol: DefSymbol): DefSymbolAPI @@ -151,8 +154,9 @@ trait SymbolOps extends Core { def tree(implicit ctx: Context): ValDef /** The class symbol of the companion module class */ - def companionClass(implicit ctx: Context): Option[ClassSymbol] + def moduleClass(implicit ctx: Context): Option[ClassSymbol] + def companionClass(implicit ctx: Context): Option[ClassSymbol] } implicit def ValSymbolDeco(symbol: ValSymbol): ValSymbolAPI diff --git a/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala b/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala index f6f3ed7b3182..2e5a90ddf619 100644 --- a/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala +++ b/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala @@ -44,7 +44,7 @@ trait TypeOrBoundsOps extends Core { implicit def TypeLambdaDeco(x: TypeLambda): Type.TypeLambdaAPI implicit def TypeBoundsDeco(bounds: TypeBounds): TypeBoundsAPI - + // ----- Types ---------------------------------------------------- def typeOf[T: scala.quoted.Type]: Type @@ -53,6 +53,7 @@ trait TypeOrBoundsOps extends Core { def =:=(other: Type)(implicit ctx: Context): Boolean def <:<(other: Type)(implicit ctx: Context): Boolean def widen(implicit ctx: Context): Type + def classSymbol(implicit ctx: Context): Option[ClassSymbol] } val IsType: IsTypeModule diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/InterpretedMain.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/InterpretedMain.scala new file mode 100644 index 000000000000..62e870a1b8ad --- /dev/null +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/InterpretedMain.scala @@ -0,0 +1,73 @@ +object IntepretedMain { + def main(args: Array[String]): Unit = { + val x1 = 42 + println(x1) + println() + + lazy val x2 = println("Hello") + x2 + x2 + println() + + def x3 = 42 + println(x3) + println() + + var x4: Int = 42 + x4 = 43 + println(x4) + println() + + if(x1 == 42) + println("if") + else + println("else") + println() + + var x5 = 5 + while(x5 > 0){ + println(x5) + x5 = x5 - 1 + } + println() + + def meth() = 42 + println(meth()) + println() + + def methP(i: Int) = i + println(methP(55)) + + println(Precompiled) + println(Precompiled.staticMeth) + println(Precompiled.staticVal) + println(Precompiled.staticMeth1()) + println(Precompiled.staticMeth2(58)) + println(Precompiled.staticMeth3(new Object)) + println(Precompiled.staticMeth4(new Bar)) + println(Precompiled.staticMeth5(new Bar, 61)) + println(Precompiled.staticMeth4(new InterpretedBar)) + println(Precompiled.staticMeth5(new InterpretedBar, 62)) + + val x6: Any = 64 + println(x6.isInstanceOf[Int]) + println(x6.isInstanceOf[Long]) + println(x6.asInstanceOf[Int]) + + + val bar = new Bar + println(bar.meth() + 5) + println(bar.methA(66)) + + val ibar = new InterpretedBar + println(ibar.meth() + 5) + println(ibar.methA(67)) + } + + def foo(x: Int): Unit = println(x) +} + +class InterpretedBar extends IFace { + def meth(): Int = 62 + def methA(x: Int): Int = x + 1 +} diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/Precompiled.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/Precompiled.scala new file mode 100644 index 000000000000..df050222ae2b --- /dev/null +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/Precompiled.scala @@ -0,0 +1,24 @@ + +class Bar extends IFace { + def meth(): Int = 60 + def methA(x: Int): Int = x +} + +trait IFace { + def meth(): Int + def methA(x: Int): Int +} + +object Precompiled { + def staticMeth = 55 + val staticVal = 56 + + // Todo + def staticMeth1() = 57 + def staticMeth2(arg: Int) = arg + def staticMeth3(arg: Object): Int = 59 + def staticMeth4(arg: IFace): Int = arg.meth() + def staticMeth5(arg: IFace, x: Int): Int = arg.methA(x) + + override def toString() = "precompiledModule" +} \ No newline at end of file diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/Test.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/Test.scala new file mode 100644 index 000000000000..96212d50d70b --- /dev/null +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/Test.scala @@ -0,0 +1,113 @@ +import java.io.{ByteArrayOutputStream, File, PrintStream} + +import dotty.tools.dotc.core.Contexts +import dotty.tools.dotc.reporting.Reporter +import dotty.tools.dotc.reporting.diagnostic.MessageContainer +import dotty.tools.dotc.util.DiffUtil +import dotty.tools.io.Path + +import scala.io.Source +import scala.tasty.file._ +import scala.tasty.interpreter.TastyInterpreter +import scala.tasty.Reflection + +object Test { + def main(args: Array[String]): Unit = { + + val actualOutput = interpret("")("IntepretedMain", "InterpretedBar") + val expectedOutput = + """42 + | + |Hello + | + |42 + | + |43 + | + |if + | + |5 + |4 + |3 + |2 + |1 + | + |42 + | + |55 + |precompiledModule + |55 + |56 + |57 + |58 + |59 + |60 + |61 + |62 + |63 + |true + |false + |64 + |65 + |66 + |67 + |68 + |""".stripMargin + + assert(expectedOutput == actualOutput, + "\n>>>>>>>>>>>>>>>>>>\n" + + DiffUtil.mkColoredCodeDiff(actualOutput, expectedOutput, true) + + "<<<<<<<<<<<<<<<<<<" + ) + + compileAndInterpret("HelloWorld.scala") + compileAndInterpret("nullInstanceEval.scala") + compileAndInterpret("t3327.scala") +// compileAndInterpret("t5614.scala") +// compileAndInterpret("t4054.scala") +// compileAndInterpret("sort.scala") +// compileAndInterpret("t0607.scala") +// compileAndInterpret("i4073b.scala") +// compileAndInterpret("i4430.scala") +// compileAndInterpret("nullAsInstanceOf.scala") +// compileAndInterpret("classof.scala") +// compileAndInterpret("null-hash.scala") +// compileAndInterpret("i3518.scala") +// compileAndInterpret("withIndex.scala") +// compileAndInterpret("unboxingBug.scala") +// compileAndInterpret("traitInit.scala") + } + + def compileAndInterpret(testFileName: String) = { + val reproter = new Reporter { + def doReport(m: MessageContainer)(implicit ctx: Contexts.Context): Unit = println(m) + } + val out = java.nio.file.Paths.get("out/interpreted") + if (!java.nio.file.Files.exists(out)) + java.nio.file.Files.createDirectory(out) + dotty.tools.dotc.Main.process(Array("-classpath", System.getProperty("java.class.path"), "-d", out.toString, "tests/run/" + testFileName), reproter) + + val actualOutput = interpret(out.toString)("Test") + + val checkFile = java.nio.file.Paths.get("tests/run/" + testFileName.stripSuffix(".scala") + ".check") + if (java.nio.file.Files.exists(checkFile)) { + val expectedOutput = Source.fromFile(checkFile.toFile).getLines().mkString("", "\n", "\n") + + assert(expectedOutput == actualOutput, + "\n>>>>>>>>>>>>>>>>>>\n" + + DiffUtil.mkColoredCodeDiff(actualOutput, expectedOutput, true) + + "<<<<<<<<<<<<<<<<<<" + ) + } + } + + def interpret(classpath: String*)(interpretedClasses: String*): String = { + val ps = new ByteArrayOutputStream() + try scala.Console.withOut(ps) { + ConsumeTasty(classpath.mkString(java.io.File.pathSeparatorChar.toString), interpretedClasses.toList, new TastyInterpreter) + } catch { + case e: Throwable => throw new Exception(ps.toString, e) + } + ps.toString + } +} diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala new file mode 100644 index 000000000000..9eaf6c02dc9d --- /dev/null +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala @@ -0,0 +1,25 @@ +package scala.tasty.interpreter + +import scala.tasty.Reflection +import scala.tasty.file.TastyConsumer + +class TastyInterpreter extends TastyConsumer { + + final def apply(reflect: Reflection)(root: reflect.Tree): Unit = { + import reflect._ + object Traverser extends TreeTraverser { + + override def traverseTree(tree: Tree)(implicit ctx: Context): Unit = tree match { + // TODO: check the correct sig and object enclosement for main + case DefDef("main", _, _, _, Some(rhs)) => + val interpreter = new jvm.Interpreter(reflect) + + interpreter.eval(rhs)(Map.empty) + // TODO: recurse only for PackageDef, ClassDef + case tree => + super.traverseTree(tree) + } + } + Traverser.traverseTree(root)(reflect.rootContext) + } +} \ No newline at end of file diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala new file mode 100644 index 000000000000..8df8d08d65b2 --- /dev/null +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala @@ -0,0 +1,216 @@ +package scala.tasty.interpreter + +import scala.tasty.interpreter.jvm.JVMReflection +import scala.tasty.Reflection + +abstract class TreeInterpreter[R <: Reflection & Singleton](val reflect: R) { + import reflect._ + + final val LOG = false + + type Env = Map[Symbol, LocalValue] + + /** Representation of objects and values in the interpreter */ + type AbstractAny + + type Result = implicit Env => AbstractAny + + def localValue(sym: Symbol)(implicit env: Env): LocalValue = env(sym) + + def withLocalValue[T](sym: Symbol, value: LocalValue)(in: implicit Env => T)(implicit env: Env): T = + in(env.updated(sym, value)) + + def withLocalValues[T](syms: List[Symbol], values: List[LocalValue])(in: implicit Env => T)(implicit env: Env): T = + in(env ++ syms.zip(values)) + + def interpretCall(instance: AbstractAny, sym: DefSymbol, args: List[AbstractAny]): Result = { + // TODO + // withLocalValue(`this`, instance) { + val syms = sym.tree.paramss.headOption.getOrElse(Nil).map(_.symbol) + withLocalValues(syms, args.map(LocalValue.valFrom(_))) { + eval(sym.tree.rhs.get) + } + // } + } + + def interpretCall(fn: Term, argss: List[List[Term]]): Result = { + fn match { + case Term.Select (prefix, _) => + val pre = eval (prefix) + // TODO use + case _ => + } + val evaluatedArgs = argss.flatten.map(arg => LocalValue.valFrom(eval(arg))) + val IsDefSymbol(sym) = fn.symbol + val syms = sym.tree.paramss.headOption.getOrElse(Nil).map(_.symbol) + withLocalValues(syms, evaluatedArgs) { + eval(sym.tree.rhs.get) + } + } + + def interpretValGet(fn: Term): Result = + localValue(fn.symbol).get + + def interpretNew(fn: Tree, argss: List[List[Term]]): Result + + def interpretIf(cond: Term, thenp: Term, elsep: Term): Result = + if (eval(cond).asInstanceOf[Boolean]) eval(thenp) + else eval(elsep) + + def interpretWhile(cond: Term, body: Term): Result = { + while (eval(cond).asInstanceOf[Boolean]) eval(body) + interpretUnit() + } + + def interpretBlock(stats: List[Statement], expr: Term): Result = { + val newEnv = stats.foldLeft(implicitly[Env])((accEnv, stat) => stat match { + case ValDef(name, tpt, Some(rhs)) => + def evalRhs = eval(rhs)(accEnv) + val evalRef: LocalValue = + if (stat.symbol.flags.is(Flags.Lazy)) LocalValue.lazyValFrom(evalRhs) + else if (stat.symbol.flags.is(Flags.Mutable)) LocalValue.varFrom(evalRhs) + else LocalValue.valFrom(evalRhs) + + accEnv.updated(stat.symbol, evalRef) + case DefDef(_, _, _, _, _) => + // TODO: record the environment for closure purposes + accEnv + case stat => + eval(stat)(accEnv) + accEnv + }) + eval(expr)(newEnv) + } + + def interpretUnit(): AbstractAny + def interpretLiteral(const: Constant): Result + + def interpretIsInstanceOf(o: AbstractAny, tpt: TypeTree): Result + def interpretAsInstanceOf(o: AbstractAny, tpt: TypeTree): Result + + def interpretRepeated(elems: List[AbstractAny]): AbstractAny + + def interpretEqEq(x: AbstractAny, y: AbstractAny): AbstractAny + + def interpretPrivitiveLt(x: AbstractAny, y: AbstractAny): AbstractAny + def interpretPrivitiveGt(x: AbstractAny, y: AbstractAny): AbstractAny + def interpretPrivitiveLtEq(x: AbstractAny, y: AbstractAny): AbstractAny + def interpretPrivitiveGtEq(x: AbstractAny, y: AbstractAny): AbstractAny + def interpretPrivitivePlus(x: AbstractAny, y: AbstractAny): AbstractAny + def interpretPrivitiveMinus(x: AbstractAny, y: AbstractAny): AbstractAny + def interpretPrivitiveTimes(x: AbstractAny, y: AbstractAny): AbstractAny + def interpretPrivitiveDiv(x: AbstractAny, y: AbstractAny): AbstractAny + def interpretPrivitiveQuot(x: AbstractAny, y: AbstractAny): AbstractAny + def interpretPrivitiveRem(x: AbstractAny, y: AbstractAny): AbstractAny + + def eval(tree: Statement): Result = { + tree match { + case Call(fn, targs, argss) => + fn match { + case Term.Select(_, "") => log("interpretNew", tree)(interpretNew(fn, argss)) + case Term.Select(prefix, "isInstanceOf") => log("interpretIsInstanceOf", tree)(interpretIsInstanceOf(eval(prefix), targs.head)) + case Term.Select(prefix, "asInstanceOf") => log("interpretAsInstanceOf", tree)(interpretAsInstanceOf(eval(prefix), targs.head)) + case Term.Select(prefix, "==") => log("interpretEqEq", tree)(interpretEqEq(eval(prefix), eval(argss.head.head))) + case Term.Select(prefix, name @ ("+" | "-" | "*" | "<" | ">" | "<=" | "=>")) if isNumericPrimitive(prefix.tpe) => + val lhs = eval(prefix) + val rhs = eval(argss.head.head) + name match { + case "+" => log("interpretPrivitivePlus", tree)(interpretPrivitivePlus(lhs, rhs)) + case "-" => log("interpretPrivitiveMinus", tree)(interpretPrivitiveMinus(lhs, rhs)) + case "*" => log("interpretPrivitiveTimes", tree)(interpretPrivitiveTimes(lhs, rhs)) + case "<" => log("interpretPrivitiveLt", tree)(interpretPrivitiveLt(lhs, rhs)) + case ">" => log("interpretPrivitiveGt", tree)(interpretPrivitiveGt(lhs, rhs)) + case "<=" => log("interpretPrivitiveLtEq", tree)(interpretPrivitiveLtEq(lhs, rhs)) + case ">=" => log("interpretPrivitiveGtEq", tree)(interpretPrivitiveGtEq(lhs, rhs)) + } + case Term.Select(prefix, name @ ("/" | "%")) if isIntegralPrimitive(prefix.tpe) => + def lhs = eval(prefix) + def rhs = eval(argss.head.head) + if (name == "/") log("interpretPrivitiveQuot", tree)(interpretPrivitiveQuot(lhs, rhs)) + else log("interpretPrivitiveRem", tree)(interpretPrivitiveRem(lhs, rhs)) + case Term.Select(prefix, name @ "/") if isFractionalPrimitive(prefix.tpe) => + def lhs = eval(prefix) + def rhs = eval(argss.head.head) + log("interpretPrivitiveDiv", tree)(interpretPrivitiveDiv(lhs, rhs)) + case _ => + fn.symbol match { + case IsDefSymbol(sym) => log("interpretCall", tree)(interpretCall(fn, argss)) + case _ => + assert(argss.isEmpty) + log("interpretValGet", tree)(interpretValGet(fn)) + + } + } + + case Term.Assign(lhs, rhs) => + log("", tree)(localValue(lhs.symbol).update(eval(rhs))) + + case Term.If(cond, thenp, elsep) => log("interpretIf", tree)(interpretIf(cond, thenp, elsep)) + case Term.While(cond, body) => log("interpretWhile", tree)(interpretWhile(cond, body)) + case Term.Block(stats, expr) => log("interpretBlock", tree)(interpretBlock(stats, expr)) + case Term.Literal(const) => log("interpretLiteral", tree)(interpretLiteral(const)) + case Term.Typed(expr, _) => log("", tree)(eval(expr)) + case Term.Repeated(elems, _) => log("", tree)(interpretRepeated(elems.map(elem => eval(elem)))) + + case _ => throw new MatchError(tree.show) + } + } + + inline def log[T](tag: => String, tree: => Statement)(thunk: => T): T = { + if (LOG) + println( + s"""#> $tag: + |${tree.showCode} + |${tree.show} + | + |""".stripMargin) + thunk + } + + trait LocalValue { + def get: AbstractAny + def update(rhs: AbstractAny): AbstractAny = throw new UnsupportedOperationException + } + + object LocalValue { + def valFrom(rhs: AbstractAny): LocalValue = new LocalValue { + def get: AbstractAny = rhs + } + def lazyValFrom(rhs: => AbstractAny): LocalValue = new LocalValue { + lazy val get: AbstractAny = rhs + } + def varFrom(rhs: AbstractAny): LocalValue = new LocalValue { + private[this] var value = rhs + def get = value + override def update(rhs: AbstractAny): AbstractAny = { + value = rhs + interpretUnit() + } + } + } + + private def isNumericPrimitive(tpe: Type): Boolean = + isIntegralPrimitive(tpe) || isFractionalPrimitive(tpe) + + private def isIntegralPrimitive(tpe: Type): Boolean = { + tpe <:< definitions.ByteType || + tpe <:< definitions.CharType || + tpe <:< definitions.ShortType || + tpe <:< definitions.IntType || + tpe <:< definitions.LongType + } + + private def isFractionalPrimitive(tpe: Type): Boolean = + tpe <:< definitions.FloatType || tpe <:< definitions.DoubleType + + + private object Call { + def unapply(arg: Tree): Option[(Term, List[TypeTree], List[List[Term]])] = arg match { + case Term.IsSelect(fn) => Some((fn, Nil, Nil)) + case Term.IsIdent(fn) => Some((fn, Nil, Nil)) + case Term.Apply(Call(fn, targs, args1), args2) => Some((fn, targs, args1 :+ args2)) + case Term.TypeApply(Call(fn, _, _), targs) => Some((fn, targs, Nil)) + case _ => None + } + } +} \ No newline at end of file diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala new file mode 100644 index 000000000000..e27e00d14c65 --- /dev/null +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala @@ -0,0 +1,181 @@ +package scala.tasty.interpreter +package jvm + +import scala.tasty.interpreter.jvm.JVMReflection +import scala.tasty.Reflection + +class Interpreter[R <: Reflection & Singleton](reflect0: R) extends TreeInterpreter[R](reflect0) { + import reflect._ + + // All references are represented by themselfs and values are boxed + type AbstractAny = Any + + val jvmReflection = new JVMReflection(reflect) + + def interpretNew(fn: Tree, argss: List[List[Term]]): Result = { + if (fn.symbol.isDefinedInCurrentRun) { + // Best effort to try to create a proxy + fn.symbol.owner match { + case IsClassSymbol(sym) => + val parentSymbols = sym.tree.parents.tail.map(_.asInstanceOf[TypeTree].symbol).head + import java.lang.reflect._ + val handler: InvocationHandler = new InvocationHandler() { + + def invoke(proxy: Object, method: Method, args: scala.Array[Object]): Object = { + if (LOG) { + val proxyString = if (method.getName == "toString") method.invoke(this) else proxy.toString + println(s"%> proxy call `$method` on `$proxyString` with args=${if (args == null) Nil else args.toList}") + } + + // println(method) + val symbol = sym.methods.find(_.name == method.getName).get + + if (symbol.isDefinedInCurrentRun) { + val argsList = if (args == null) Nil else args.toList + symbol match { + case IsDefSymbol(symbol) => + interpretCall(this, symbol, argsList).asInstanceOf[Object] + } + } + else { + assert(method.getClass == classOf[Object]) + method.invoke(this, args: _*) + } + } + + } + val proxyClass: Class[_] = Proxy.getProxyClass(getClass.getClassLoader, jvmReflection.loadClass(parentSymbols.fullName)) + proxyClass.getConstructor(classOf[InvocationHandler]).newInstance(handler); + } + } + else jvmReflection.interpretNew(fn.symbol, evaluatedArgss(argss)) + } + + override def interpretCall(fn: Term, argss: List[List[Term]]): Result = { + if (fn.symbol.isDefinedInCurrentRun) super.interpretCall(fn, argss) + else { + fn match { + case Term.Select(prefix, _) => + val IsDefSymbol(sym) = fn.symbol + val pre = eval(prefix).asInstanceOf[Object] + val argss2 = evaluatedArgss(argss) + jvmReflection.interpretMethodCall(pre, fn.symbol, argss2) + case _ => + val IsDefSymbol(sym) = fn.symbol + val argss2 = evaluatedArgss(argss) + jvmReflection.interpretStaticMethodCall(fn.symbol.owner, fn.symbol, argss2) + } + } + } + + override def interpretValGet(fn: Term): Result = { + if (fn.symbol.isDefinedInCurrentRun) super.interpretValGet(fn) + else { + import Term._ + fn match { + case Select(prefix, _) => + // FIXME not necesarly static + jvmReflection.interpretStaticVal(fn.symbol.owner, fn.symbol) + case _ => + if (fn.symbol.flags.is(Flags.Object)) + jvmReflection.loadModule(fn.symbol.asVal.moduleClass.get) + else + jvmReflection.interpretStaticVal(fn.symbol.owner, fn.symbol) + } + } + } + + def evaluatedArgss(argss: List[List[Term]])(implicit env: Env): List[Object] = argss.flatMap((a: List[Term]) => a.map(b => eval(b).asInstanceOf[Object])) + + def interpretUnit(): AbstractAny = ().asInstanceOf[Object] + + def interpretLiteral(const: Constant): Result = const.value + + def interpretIsInstanceOf(o: AbstractAny, tpt: TypeTree): Result = + jvmReflection.getClassOf(tpt.symbol).isInstance(o) + + def interpretAsInstanceOf(o: AbstractAny, tpt: TypeTree): Result = + jvmReflection.getClassOf(tpt.symbol).cast(o) + + def interpretRepeated(elems: List[AbstractAny]): AbstractAny = elems.toSeq + + def interpretEqEq(x: AbstractAny, y: AbstractAny): AbstractAny = x == y + + def interpretPrivitiveLt(x: AbstractAny, y: AbstractAny): AbstractAny = withNumeric(x, y)(_.lt(_, _)) + def interpretPrivitiveGt(x: AbstractAny, y: AbstractAny): AbstractAny = withNumeric(x, y)(_.gt(_, _)) + def interpretPrivitiveLtEq(x: AbstractAny, y: AbstractAny): AbstractAny = withNumeric(x, y)(_.lteq(_, _)) + def interpretPrivitiveGtEq(x: AbstractAny, y: AbstractAny): AbstractAny = withNumeric(x, y)(_.gteq(_, _)) + def interpretPrivitivePlus(x: AbstractAny, y: AbstractAny): AbstractAny = withNumeric(x, y)(_.plus(_, _)) + def interpretPrivitiveMinus(x: AbstractAny, y: AbstractAny): AbstractAny = withNumeric(x, y)(_.minus(_, _)) + def interpretPrivitiveTimes(x: AbstractAny, y: AbstractAny): AbstractAny = withNumeric(x, y)(_.times(_, _)) + def interpretPrivitiveDiv(x: AbstractAny, y: AbstractAny): AbstractAny = withFractional(x, y)(_.div(_, _)) + def interpretPrivitiveQuot(x: AbstractAny, y: AbstractAny): AbstractAny = withIntegral(x, y)(_.quot(_, _)) + def interpretPrivitiveRem(x: AbstractAny, y: AbstractAny): AbstractAny = withIntegral(x, y)(_.rem(_, _)) + + private def coerce(x: AbstractAny, y: AbstractAny): (AbstractAny, AbstractAny) = { + // TODO complete: Float Double Char + x match { + case x: Byte => + y match { + case y: Byte => (x, y) + case y: Short => (x.toShort, y) + case y: Int => (x.toInt, y) + case y: Long => (x.toLong, y) + } + case x: Short => + y match { + case y: Byte => (x, y.toShort) + case y: Short => (x, y) + case y: Int => (x.toInt, y) + case y: Long => (x.toLong, y) + } + case x: Int => + y match { + case y: Byte => (x, y.toInt) + case y: Short => (x, y.toInt) + case y: Int => (x, y) + case y: Long => (x.toLong, y) + } + case x: Long => + y match { + case y: Byte => (x, y.toLong) + case y: Short => (x, y.toLong) + case y: Int => (x, y.toLong) + case y: Long => (x, y) + } + } + } + + def withNumeric[T](x: AbstractAny, y: AbstractAny)(body: (Numeric[AbstractAny], AbstractAny, AbstractAny) => AbstractAny): AbstractAny = { + val (coX, coY) = coerce(x, y) + def getNumericFor[T](implicit x: Numeric[T]): Numeric[AbstractAny] = + x.asInstanceOf[Numeric[AbstractAny]] + coX match { + case _: Int => body(getNumericFor[Int], coX, coY) + case _: Long => body(getNumericFor[Long], coX, coY) + } + } + + def withIntegral[T](x: AbstractAny, y: AbstractAny)(body: (Integral[AbstractAny], AbstractAny, AbstractAny) => AbstractAny): AbstractAny = { + val (coX, coY) = coerce(x, y) + def getIntegralFor[T](implicit x: Integral[T]): Integral[AbstractAny] = + x.asInstanceOf[Integral[AbstractAny]] + coX match { + case _: Byte => body(getIntegralFor[Byte], coX, coY) + case _: Short => body(getIntegralFor[Short], coX, coY) + case _: Int => body(getIntegralFor[Int], coX, coY) + case _: Long => body(getIntegralFor[Long], coX, coY) + } + } + + def withFractional[T](x: AbstractAny, y: AbstractAny)(body: (Fractional[AbstractAny], AbstractAny, AbstractAny) => AbstractAny): AbstractAny = { + val (coX, coY) = coerce(x, y) + def getFractionalFor[T](implicit x: Fractional[T]): Fractional[AbstractAny] = + x.asInstanceOf[Fractional[AbstractAny]] + coX match { + case _: Float => body(getFractionalFor[Float], coX, coY) + case _: Double => body(getFractionalFor[Double], coX, coY) + } + } + +} \ No newline at end of file diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/JVMReflection.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/JVMReflection.scala new file mode 100644 index 000000000000..3983dd298113 --- /dev/null +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/JVMReflection.scala @@ -0,0 +1,116 @@ +package scala.tasty.interpreter.jvm + +import scala.tasty.Reflection + +class JVMReflection[R <: Reflection & Singleton](val reflect: R) { + import reflect._ + import java.lang.reflect.{InvocationTargetException, Method} + private val classLoader: ClassLoader = getClass.getClassLoader + + // taken from StdNames + final val MODULE_INSTANCE_FIELD = "MODULE$" + + def loadModule(sym: Symbol): Object = { + + sym.owner match { + case IsPackageSymbol(_) => + val moduleClass = getClassOf(sym) + moduleClass.getField(MODULE_INSTANCE_FIELD).get(null) + case _ => + // nested object in an object + // val clazz = loadClass(sym.fullNameSeparated(FlatName)) + // clazz.getConstructor().newInstance().asInstanceOf[Object] + ??? + } + } + + def getClassOf(sym: Symbol): Class[_] = { + sym.fullName match { + case "scala.Boolean" => classOf[java.lang.Boolean] + case "scala.Short" => classOf[java.lang.Short] + case "scala.Char" => classOf[java.lang.Character] + case "scala.Int" => classOf[java.lang.Integer] + case "scala.Long" => classOf[java.lang.Long] + case "scala.Float" => classOf[java.lang.Float] + case "scala.Double" => classOf[java.lang.Double] + case _ => loadClass(sym.fullName) + } + } + + def loadClass(name: String): Class[_] = { + try classLoader.loadClass(name) + catch { + case _: ClassNotFoundException => + val msg = s"Could not find class $name in classpath$extraMsg" + throw new Exception(msg) + } + } + + def interpretStaticVal(moduleClass: Symbol, fn: Symbol): Object = { + val instance = loadModule(moduleClass) + val name = fn.name + val method = getMethod(instance.getClass, name, Nil) + method.invoke(instance) + } + + def interpretStaticMethodCall(moduleClass: Symbol, fn: Symbol, args: List[Object]): Object = { + // TODO can we use interpretMethodCall instead? + val instance = loadModule(moduleClass) + val method = getMethod(instance.getClass, fn.name, paramsSig(fn)) + method.invoke(instance, args: _*) + } + + def interpretMethodCall(instance: Object, fn: Symbol, args: List[Object]): Object = { + val method = getMethod(instance.getClass, fn.name, paramsSig(fn)) + method.invoke(instance, args: _*) + } + + def interpretNew(fn: Symbol, args: List[Object]): Object = { + val clazz = getClassOf(fn.owner) + val constr = clazz.getConstructor(paramsSig(fn): _*) + constr.newInstance(args: _*).asInstanceOf[Object] + } + + def getMethod(clazz: Class[_], name: String, paramClasses: List[Class[_]]): Method = { + try clazz.getMethod(name, paramClasses: _*) + catch { + case _: NoSuchMethodException => + val msg = s"Could not find method ${clazz.getCanonicalName}.$name with parameters ($paramClasses%, %)$extraMsg" + throw new Exception(msg) + } + } + + private def paramsSig(sym: Symbol): List[Class[_]] = { + sym.asDef.signature.paramSigs.map { param => + def javaArraySig(name: String): String = { + if (name.endsWith("[]")) "[" + javaArraySig(name.dropRight(2)) + else name match { + case "scala.Boolean" => "Z" + case "scala.Byte" => "B" + case "scala.Short" => "S" + case "scala.Int" => "I" + case "scala.Long" => "J" + case "scala.Float" => "F" + case "scala.Double" => "D" + case "scala.Char" => "C" + case paramName => "L" + paramName + ";" + } + } + + def javaSig(name: String): String = + if (name.endsWith("[]")) javaArraySig(name) else name + + if (param == "scala.Boolean") classOf[Boolean] + else if (param == "scala.Byte") classOf[Byte] + else if (param == "scala.Char") classOf[Char] + else if (param == "scala.Short") classOf[Short] + else if (param == "scala.Int") classOf[Int] + else if (param == "scala.Long") classOf[Long] + else if (param == "scala.Float") classOf[Float] + else if (param == "scala.Double") classOf[Double] + else java.lang.Class.forName(javaSig(param), false, classLoader) + } + } + + private def extraMsg = ". The most common reason for that is that you apply macros in the compilation run that defines them" +} \ No newline at end of file diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/notes.md b/tests/run-with-compiler-custom-args/tasty-interpreter/notes.md new file mode 100644 index 000000000000..ac461c1c3e3b --- /dev/null +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/notes.md @@ -0,0 +1,14 @@ +## Design Notes + + +- Abstract platform operations + - Arrays +- Proxies + - Enviroment of the object + - `this` in Env + - Class with fields + - Class with custom constructor (and secondary) + +- Stack + - local def env (closures) + - local class env diff --git a/tests/run/HelloWorld.check b/tests/run/HelloWorld.check new file mode 100644 index 000000000000..3b18e512dba7 --- /dev/null +++ b/tests/run/HelloWorld.check @@ -0,0 +1 @@ +hello world diff --git a/tests/run/HelloWorld.scala b/tests/run/HelloWorld.scala new file mode 100644 index 000000000000..41be65f048d1 --- /dev/null +++ b/tests/run/HelloWorld.scala @@ -0,0 +1,3 @@ +object Test { + def main(args: Array[String]): Unit = println("hello world") +}