Skip to content

Commit

Permalink
Merge pull request #10020 from scalacenter/tasty/support-experimental
Browse files Browse the repository at this point in the history
  • Loading branch information
dwijnand committed May 5, 2022
2 parents df355f9 + 45f1f38 commit 33a82d8
Show file tree
Hide file tree
Showing 26 changed files with 424 additions and 75 deletions.
28 changes: 9 additions & 19 deletions src/compiler/scala/tools/nsc/tasty/bridge/AnnotationOps.scala
Expand Up @@ -30,34 +30,24 @@ trait AnnotationOps { self: TastyUniverse =>
throw new Exception(s"unexpected annotation kind from TASTy: ${u.showRaw(tree)}")
}

abstract class DeferredAnnotation {
sealed abstract class DeferredAnnotation {

private[bridge] def eager(annotee: Symbol)(implicit ctx: Context): u.AnnotationInfo
private[bridge] def lzy(annotee: Symbol)(implicit ctx: Context): u.LazyAnnotationInfo = {
u.AnnotationInfo.lazily {
eager(annotee)
}
private[bridge] final def lzy(annotee: Symbol)(implicit ctx: Context): u.LazyAnnotationInfo = {
u.AnnotationInfo.lazily(eager(annotee))
}
}

object DeferredAnnotation {

def fromTree(tree: Symbol => Context => Tree) =
new FromTree(tree)

class FromTree(tree: Symbol => Context => Tree) extends DeferredAnnotation {
private[bridge] def eager(annotee: Symbol)(implicit ctx: Context): u.AnnotationInfo = {
val atree = tree(annotee)(ctx)
val annot = mkAnnotation(atree)
val annotSym = annot.tpe.typeSymbol
if ((annotSym eq defn.TargetNameAnnotationClass) || (annotSym eq defn.StaticMethodAnnotationClass)) {
annotee.addAnnotation(
u.definitions.CompileTimeOnlyAttr,
u.Literal(u.Constant(unsupportedMessage(s"annotation on $annotee: @$annot"))))
def fromTree(tree: Symbol => Context => Tree): DeferredAnnotation = {
new DeferredAnnotation {
private[bridge] final def eager(annotee: Symbol)(implicit ctx: Context): u.AnnotationInfo = {
val atree = tree(annotee)(ctx)
mkAnnotation(atree)
}
annot
}
}

}

}
14 changes: 14 additions & 0 deletions src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala
Expand Up @@ -120,6 +120,8 @@ trait ContextOps { self: TastyUniverse =>
*/
private def analyseAnnotations(sym: Symbol)(implicit ctx: Context): Unit = {

def inOwner[T](op: Context => T): T = op(ctx.withOwner(sym.owner))

def lookupChild(childTpe: Type): Symbol = {
val child = symOfType(childTpe)
assert(isSymbol(child), s"did not find symbol of sealed child ${showType(childTpe)}")
Expand All @@ -132,6 +134,8 @@ trait ContextOps { self: TastyUniverse =>
}
}

var problematic: List[String] = Nil

for (annot <- sym.annotations) {
annot.completeInfo()
if (annot.tpe.typeSymbolDirect === defn.ChildAnnot) {
Expand All @@ -154,6 +158,16 @@ trait ContextOps { self: TastyUniverse =>
ctx.log(s"adding sealed child ${showSym(child)} to ${showSym(sym)}")
sym.addChild(child)
}
if ((annot.symbol eq defn.TargetNameAnnotationClass) ||
(annot.symbol eq defn.StaticMethodAnnotationClass)) {
problematic ::= inOwner { implicit ctx =>
unsupportedMessage(s"annotation on $sym: @$annot")
}
}
}
if (problematic.nonEmpty) {
sym.removeAnnotation(u.definitions.CompileTimeOnlyAttr)
sym.addAnnotation(u.definitions.CompileTimeOnlyAttr, u.Literal(u.Constant(problematic.head)))
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/compiler/scala/tools/nsc/tasty/bridge/NameOps.scala
Expand Up @@ -54,6 +54,8 @@ trait NameOps { self: TastyUniverse =>
final val Tuple: String = "Tuple"
final val Matchable: String = "Matchable"

val ErasedFunctionN = raw"ErasedFunction(\d+)".r
val ErasedContextFunctionN = raw"ErasedContextFunction(\d+)".r
val ContextFunctionN = raw"ContextFunction(\d+)".r
val FunctionN = raw"Function(\d+)".r

Expand Down
3 changes: 3 additions & 0 deletions src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala
Expand Up @@ -117,6 +117,9 @@ trait SymbolOps { self: TastyUniverse =>
def safeOwner: Symbol = if (sym.owner eq sym) sym else sym.owner
}

/** Is this symbol annotated with `scala.annotation.experimental`? */
def symIsExperimental(sym: Symbol) = sym.hasAnnotation(defn.ExperimentalAnnotationClass)

/** if isConstructor, make sure it has one non-implicit parameter list */
def normalizeIfConstructor(termParamss: List[List[Symbol]], isConstructor: Boolean): List[List[Symbol]] =
if (isConstructor &&
Expand Down
51 changes: 35 additions & 16 deletions src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala
Expand Up @@ -36,8 +36,11 @@ trait TypeOps { self: TastyUniverse =>

/** `*:` erases to either TupleXXL or Product */
@inline final def genTupleIsUnsupported[T](name: String)(implicit ctx: Context): T = unsupportedError(s"generic tuple type $name in ${boundsString(ctx.owner)}")
@inline final def bigFnIsUnsupported[T](tpeStr: String)(implicit ctx: Context): T = unsupportedError(s"function type with more than 22 parameters in ${boundsString(ctx.owner)}: $tpeStr")
@inline final def ctxFnIsUnsupported[T](tpeStr: String)(implicit ctx: Context): T = unsupportedError(s"context function type in ${boundsString(ctx.owner)}: $tpeStr")
@inline final def fnIsUnsupported[T](kind: String => String, tpeStr: String)(implicit ctx: Context): T = unsupportedError(s"${kind("function type")} in ${boundsString(ctx.owner)}: $tpeStr")
@inline final def bigFnIsUnsupported[T](tpeStr: String)(implicit ctx: Context): T = fnIsUnsupported(ft => s"$ft with more than 22 parameters", tpeStr)
@inline final def ctxFnIsUnsupported[T](tpeStr: String)(implicit ctx: Context): T = fnIsUnsupported(ft => s"context $ft", tpeStr)
@inline final def erasedFnIsUnsupported[T](tpeStr: String)(implicit ctx: Context): T = fnIsUnsupported(ft => s"erased $ft", tpeStr)
@inline final def erasedCtxFnIsUnsupported[T](tpeStr: String)(implicit ctx: Context): T = fnIsUnsupported(ft => s"erased context $ft", tpeStr)
@inline final def unionIsUnsupported[T](implicit ctx: Context): T = unsupportedError(s"union in ${boundsString(ctx.owner)}")
@inline final def matchTypeIsUnsupported[T](implicit ctx: Context): T = unsupportedError(s"match type in ${boundsString(ctx.owner)}")
@inline final def erasedRefinementIsUnsupported[T](implicit ctx: Context): T = unsupportedError(s"erased modifier in refinement of ${ctx.owner}")
Expand Down Expand Up @@ -142,6 +145,7 @@ trait TypeOps { self: TastyUniverse =>
final val RepeatedAnnot: Symbol = u.definitions.RepeatedAnnotationClass
final val TargetNameAnnotationClass: Symbol = u.definitions.TargetNameAnnotationClass
final val StaticMethodAnnotationClass: Symbol = u.definitions.StaticMethodAnnotationClass
final val ExperimentalAnnotationClass: Symbol = u.definitions.ExperimentalAnnotationClass

object PolyFunctionType {

Expand Down Expand Up @@ -258,21 +262,26 @@ trait TypeOps { self: TastyUniverse =>

def AppliedType(tycon: Type, args: List[Type])(implicit ctx: Context): Type = {

def formatFnType(arrow: String, arity: Int, args: List[Type]): String = {
def formatFnType(arrow: String, isErased: Boolean, arity: Int, args: List[Type]): String = {
val len = args.length
assert(len == arity + 1) // tasty should be type checked already
val res = args.last
val params = args.init
val paramsBody = params.mkString(",")
val paramsBody = {
val body = params.mkString(",")
if (isErased) s"erased $body" else body
}
val argList = if (len == 2) paramsBody else s"($paramsBody)"
s"$argList $arrow $res"
}

def typeRefUncurried(tycon: Type, args: List[Type]): Type = tycon match {
case tycon: u.TypeRef if tycon.typeArgs.nonEmpty =>
unsupportedError(s"curried type application $tycon[${args.mkString(",")}]")
case ContextFunctionType(n) => ctxFnIsUnsupported(formatFnType("?=>", n, args))
case FunctionXXLType(n) => bigFnIsUnsupported(formatFnType("=>", n, args))
case ContextFunctionType(n) => ctxFnIsUnsupported(formatFnType("?=>", isErased = false, n, args))
case ErasedContextFunctionType(n) => erasedCtxFnIsUnsupported(formatFnType("?=>", isErased = true, n, args))
case ErasedFunctionType(n) => erasedFnIsUnsupported(formatFnType("=>", isErased = true, n, args))
case FunctionXXLType(n) => bigFnIsUnsupported(formatFnType("=>", isErased = false, n, args))
case _ =>
u.appliedType(tycon, args)
}
Expand Down Expand Up @@ -326,15 +335,17 @@ trait TypeOps { self: TastyUniverse =>
if (prefix.typeSymbol === u.definitions.ScalaPackage) {
name match {
case TypeName(SimpleName(raw @ SyntheticScala3Type())) => raw match {
case tpnme.And => AndTpe
case tpnme.Or => unionIsUnsupported
case tpnme.ContextFunctionN(n) if (n.toInt > 0) => ContextFunctionType(n.toInt)
case tpnme.FunctionN(n) if (n.toInt > 22) => FunctionXXLType(n.toInt)
case tpnme.TupleCons => genTupleIsUnsupported("scala.*:")
case tpnme.Tuple if !ctx.mode.is(ReadParents) => genTupleIsUnsupported("scala.Tuple")
case tpnme.AnyKind => u.definitions.AnyTpe
case tpnme.Matchable => u.definitions.AnyTpe
case _ => doLookup
case tpnme.And => AndTpe
case tpnme.Or => unionIsUnsupported
case tpnme.ContextFunctionN(n) => ContextFunctionType(n.toInt)
case tpnme.FunctionN(n) if (n.toInt > 22) => FunctionXXLType(n.toInt)
case tpnme.TupleCons => genTupleIsUnsupported("scala.*:")
case tpnme.Tuple if !ctx.mode.is(ReadParents) => genTupleIsUnsupported("scala.Tuple")
case tpnme.AnyKind => u.definitions.AnyTpe
case tpnme.Matchable => u.definitions.AnyTpe
case tpnme.ErasedContextFunctionN(n) if n.toInt > 0 => ErasedContextFunctionType(n.toInt)
case tpnme.ErasedFunctionN(n) => ErasedFunctionType(n.toInt)
case _ => doLookup
}

case _ => doLookup
Expand Down Expand Up @@ -486,6 +497,14 @@ trait TypeOps { self: TastyUniverse =>
*/
case object AndTpe extends Type

case class ErasedFunctionType(arity: Int) extends Type {
assert(arity > 0)
}

case class ErasedContextFunctionType(arity: Int) extends Type {
assert(arity > 0)
}

case class ContextFunctionType(arity: Int) extends Type {
assert(arity > 0)
}
Expand All @@ -495,7 +514,7 @@ trait TypeOps { self: TastyUniverse =>
}

private val SyntheticScala3Type =
raw"^(?:&|\||AnyKind|(?:Context)?Function\d+|\*:|Tuple|Matchable)$$".r
raw"^(?:&|\||AnyKind|(?:Erased)?(?:Context)?Function\d+|\*:|Tuple|Matchable)$$".r

sealed abstract trait TastyRepr extends u.Type {
def tflags: TastyFlagSet
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
Expand Up @@ -1286,6 +1286,11 @@ abstract class RefChecks extends Transform {
if (changed)
refchecksWarning(pos, s"${sym.fullLocationString} has changed semantics in version ${sym.migrationVersion.get}:\n${sym.migrationMessage.get}", WarningCategory.OtherMigration)
}
if (sym.isExperimental && !currentOwner.ownerChain.exists(x => x.isExperimental)) {
val msg =
s"${sym.fullLocationString} is marked @experimental and therefore its enclosing scope must be experimental."
reporter.error(pos, msg)
}
// See an explanation of compileTimeOnly in its scaladoc at scala.annotation.compileTimeOnly.
// async/await is expanded after erasure
if (sym.isCompileTimeOnly && !inAnnotation && !currentOwner.ownerChain.exists(x => x.isCompileTimeOnly)) {
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/internal/Definitions.scala
Expand Up @@ -1316,6 +1316,7 @@ trait Definitions extends api.StandardDefinitions {
lazy val TargetNameAnnotationClass = getClassIfDefined("scala.annotation.targetName")
lazy val StaticMethodAnnotationClass = getClassIfDefined("scala.annotation.static")
lazy val PolyFunctionClass = getClassIfDefined("scala.PolyFunction")
lazy val ExperimentalAnnotationClass = getClassIfDefined("scala.annotation.experimental")

lazy val BeanPropertyAttr = requiredClass[scala.beans.BeanProperty]
lazy val BooleanBeanPropertyAttr = requiredClass[scala.beans.BooleanBeanProperty]
Expand Down
2 changes: 2 additions & 0 deletions src/reflect/scala/reflect/internal/Symbols.scala
Expand Up @@ -934,6 +934,8 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
def isCompileTimeOnly = hasAnnotation(CompileTimeOnlyAttr)
def compileTimeOnlyMessage = getAnnotation(CompileTimeOnlyAttr) flatMap (_ stringArg 0)

def isExperimental = hasAnnotation(ExperimentalAnnotationClass)

/** Is this symbol an accessor method for outer? */
final def isOuterAccessor = hasFlag(STABLE | ARTIFACT) && (unexpandedName == nme.OUTER)

Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
Expand Up @@ -443,6 +443,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
definitions.TargetNameAnnotationClass
definitions.StaticMethodAnnotationClass
definitions.PolyFunctionClass
definitions.ExperimentalAnnotationClass
definitions.BeanPropertyAttr
definitions.BooleanBeanPropertyAttr
definitions.CompileTimeOnlyAttr
Expand Down
64 changes: 53 additions & 11 deletions src/tastytest/scala/tools/tastytest/Dotc.scala
Expand Up @@ -8,6 +8,9 @@ import scala.reflect.runtime.ReflectionUtils
import java.lang.reflect.{Modifier, Method}

import ClasspathOps._
import java.io.OutputStream
import java.io.BufferedReader
import java.io.PrintWriter

object Dotc extends Script.Command {

Expand Down Expand Up @@ -36,45 +39,83 @@ object Dotc extends Script.Command {
def invokeStatic(
className: String,
methodName: String,
args: Seq[String]
args: Seq[(Class[_], Any)],
)(implicit cl: Dotc.ClassLoader): Try[Object] = {
val cls = loadClass(className)
val method = cls.getMethod(methodName, classOf[Array[String]])
val (tpes, provided) = args.unzip
val method = cls.getMethod(methodName, tpes:_*)
Try {
invokeStatic(method, Seq(args.toArray))
invokeStatic(method, provided)
}
}

def invoke(method: Method, obj: AnyRef, args: Seq[Any])(implicit cl: Dotc.ClassLoader) = {
try cl.parent.asContext[AnyRef] {
inClassloader[AnyRef] {
method.invoke(obj, args.toArray:_*)
}
}

def inClassloader[T](op: => T)(implicit cl: Dotc.ClassLoader): T = {
try cl.parent.asContext[T] {
op
}
catch {
case NonFatal(ex) => throw ReflectionUtils.unwrapThrowable(ex)
}
}

private def dotcProcess(args: Seq[String])(implicit cl: Dotc.ClassLoader) = processMethod("dotty.tools.dotc.Main")(args)
def processMethod(className: String)(args: Seq[String])(implicit cl: Dotc.ClassLoader): Try[Boolean] =
processMethodImpl(className)(args, None)

private def makeConsoleReporter(stream: OutputStream)(implicit cl: Dotc.ClassLoader): Try[AnyRef] = Try {
val consoleReporterCls = loadClass("dotty.tools.dotc.reporting.ConsoleReporter")
val ctor = consoleReporterCls.getConstructor(classOf[BufferedReader], classOf[PrintWriter])
val pwriter = new PrintWriter(stream, true)
inClassloader[AnyRef] {
ctor.newInstance(Console.in, pwriter)
}
}

def processMethod(className: String)(args: Seq[String])(implicit cl: Dotc.ClassLoader): Try[Boolean] = {
private def processMethodImpl(className: String)(args: Seq[String], writer: Option[OutputStream])(implicit cl: Dotc.ClassLoader): Try[Boolean] = {
val reporterCls = loadClass("dotty.tools.dotc.reporting.Reporter")
val Reporter_hasErrors = reporterCls.getMethod("hasErrors")
for (reporter <- invokeStatic(className, "process", args)) yield {
val processArgs: Try[Seq[(Class[_], Any)]] = {
writer match {
case Some(stream) =>
val callbackCls = loadClass("dotty.tools.dotc.interfaces.CompilerCallback")
for (myReporter <- makeConsoleReporter(stream)) yield
Seq(classOf[Array[String]] -> args.toArray, reporterCls -> myReporter, callbackCls -> null)
case _ =>
Try(Seq(classOf[Array[String]] -> args.toArray))
}
}
for {
args <- processArgs
reporter <- invokeStatic(className, "process", args)
} yield {
val hasErrors = invoke(Reporter_hasErrors, reporter, Seq.empty).asInstanceOf[Boolean]
!hasErrors
}
}

def mainMethod(className: String)(args: Seq[String])(implicit cl: Dotc.ClassLoader): Try[Unit] =
for (_ <- invokeStatic(className, "main", args)) yield ()
def mainMethod(className: String)(args: Seq[String])(implicit cl: Dotc.ClassLoader): Try[Unit] = {
val mainArgs = Seq(classOf[Array[String]] -> args.toArray)
for (_ <- invokeStatic(className, "main", mainArgs)) yield ()
}

def dotcVersion(implicit cl: Dotc.ClassLoader): String = {
val compilerPropertiesClass = loadClass("dotty.tools.dotc.config.Properties")
val Properties_simpleVersionString = compilerPropertiesClass.getMethod("simpleVersionString")
invokeStatic(Properties_simpleVersionString, Seq.empty).asInstanceOf[String]
}

def dotc(out: String, classpath: String, additionalSettings: Seq[String], sources: String*)(implicit cl: Dotc.ClassLoader): Try[Boolean] = {
def dotc(out: String, classpath: String, additionalSettings: Seq[String], sources: String*)(implicit cl: Dotc.ClassLoader): Try[Boolean] =
dotcImpl(None, out, classpath, additionalSettings, sources:_*)

def dotc(writer: OutputStream, out: String, classpath: String, additionalSettings: Seq[String], sources: String*)(implicit cl: Dotc.ClassLoader): Try[Boolean] =
dotcImpl(Some(writer), out, classpath, additionalSettings, sources:_*)

def dotcImpl(writer: Option[OutputStream], out: String, classpath: String, additionalSettings: Seq[String], sources: String*)(implicit cl: Dotc.ClassLoader): Try[Boolean] = {
if (sources.isEmpty) {
Success(true)
}
Expand All @@ -85,11 +126,12 @@ object Dotc extends Script.Command {
"-classpath", libraryDeps.mkString(classpath + Files.classpathSep, Files.classpathSep, ""),
"-deprecation",
"-Xfatal-warnings",
"-color:never",
) ++ additionalSettings ++ sources
if (TastyTest.verbose) {
println(yellow(s"Invoking dotc (version $dotcVersion) with args: $args"))
}
dotcProcess(args)
processMethodImpl("dotty.tools.dotc.Main")(args, writer)
}
}

Expand Down
13 changes: 10 additions & 3 deletions src/tastytest/scala/tools/tastytest/Scalac.scala
Expand Up @@ -3,10 +3,15 @@ package scala.tools.tastytest
import scala.collection.immutable.ArraySeq
import scala.util.{ Try, Success, chaining }, chaining._
import scala.tools.nsc.{Global, Settings, reporters}, reporters.ConsoleReporter
import java.io.OutputStream
import java.io.PrintWriter

object Scalac extends Script.Command {

def scalac(out: String, additionalSettings: Seq[String], sources: String*): Try[Boolean] = {
def scalac(out: String, additionalSettings: Seq[String], sources: String*): Try[Boolean] =
scalac(Console.out, out, additionalSettings, sources:_*)

def scalac(writer: OutputStream, out: String, additionalSettings: Seq[String], sources: String*) = {

def runCompile(global: Global): Boolean = {
global.reporter.reset()
Expand All @@ -19,8 +24,10 @@ object Scalac extends Script.Command {
def newCompiler(args: String*): Global =
fromSettings(new Settings().tap(_.processArguments(args.toList, true)))

def fromSettings(settings: Settings): Global =
Global(settings, new ConsoleReporter(settings).tap(_.shortname = true))
def fromSettings(settings: Settings): Global = {
val pwriter = new PrintWriter(writer, true)
Global(settings, new ConsoleReporter(settings, Console.in, pwriter).tap(_.shortname = true))
}

def compile(args: String*) =
Try(runCompile(newCompiler(args: _*)))
Expand Down
1 change: 1 addition & 0 deletions src/tastytest/scala/tools/tastytest/SourceKind.scala
Expand Up @@ -9,6 +9,7 @@ object SourceKind {
case object NoSource extends SourceKind("")(filter = _ => false)
case object Scala extends SourceKind(".scala")()
case object ScalaFail extends SourceKind("_fail.scala")()
case object ScalaPre extends SourceKind("_pre.scala")()
case object Check extends SourceKind(".check")()
case object SkipCheck extends SourceKind(".skipcheck")()
case object Java extends SourceKind(".java")()
Expand Down

0 comments on commit 33a82d8

Please sign in to comment.