Skip to content

Fix #6419: Support @compileTimeOnly #6424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ class Definitions {
lazy val TransientParamAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.constructorOnly")
def TransientParamAnnot(implicit ctx: Context): ClassSymbol = TransientParamAnnotType.symbol.asClass
lazy val CompileTimeOnlyAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.compileTimeOnly")
def CompileTimeOnlyParamAnnot(implicit ctx: Context): ClassSymbol = CompileTimeOnlyAnnotType.symbol.asClass
def CompileTimeOnlyAnnot(implicit ctx: Context): ClassSymbol = CompileTimeOnlyAnnotType.symbol.asClass
lazy val SwitchAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.switch")
def SwitchAnnot(implicit ctx: Context): ClassSymbol = SwitchAnnotType.symbol.asClass
lazy val ThrowsAnnotType: TypeRef = ctx.requiredClassRef("scala.throws")
Expand Down
15 changes: 13 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,19 @@ object Erasure {
}

private def checkNotErased(tree: Tree)(implicit ctx: Context): tree.type = {
if (isErased(tree) && !ctx.mode.is(Mode.Type))
ctx.error(em"${tree.symbol} is declared as erased, but is in fact used", tree.sourcePos)
if (!ctx.mode.is(Mode.Type)) {
if (isErased(tree))
ctx.error(em"${tree.symbol} is declared as erased, but is in fact used", tree.sourcePos)
tree.symbol.getAnnotation(defn.CompileTimeOnlyAnnot) match {
case Some(annot) =>
def defaultMsg =
s"""Reference to ${tree.symbol.showLocated} should not have survived,
|it should have been processed and eliminated during expansion of an enclosing macro or term erasure."""
val message = annot.argumentConstant(0).fold(defaultMsg)(_.stringValue)
ctx.error(message, tree.sourcePos)
case _ => // OK
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In RefChecks.scala, there is commented code:

    /*  (Not enabled yet)
       *  See an explanation of compileTimeOnly in its scaladoc at scala.annotation.compileTimeOnly.
       *
      if (sym.isCompileTimeOnly) {
        def defaultMsg =
          sm"""Reference to ${sym.fullLocationString} should not have survived past type checking,
              |it should have been processed and eliminated during expansion of an enclosing macro."""
        // The getOrElse part should never happen, it's just here as a backstop.
        ctx.error(sym.compileTimeOnlyMessage getOrElse defaultMsg, pos)
      }*/

Maybe we should just revive the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it would not take into account if it is into an erased context or not. Which is already performed in the place where I check it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in https://github.com/lampepfl/dotty/pull/6424/files#diff-a12cb93e6ac05566103dd86ede6e5901R12, where the reference of the @compileTimeOnly is inside an erased def

tree
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
private object dropInlines extends TreeMap {
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
case Inlined(call, _, _) =>
cpy.Inlined(tree)(call, Nil, Typed(ref(defn.Predef_undefined), TypeTree(tree.tpe)))
cpy.Inlined(tree)(call, Nil, Typed(ref(defn.Predef_undefined), TypeTree(tree.tpe)).withSpan(tree.span))
case _ => super.transform(tree)
}
}
Expand Down
2 changes: 0 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,6 @@ object Checking {
if (!sym.is(Deferred))
fail(NativeMembersMayNotHaveImplementation(sym))
}
if (sym.hasAnnotation(defn.CompileTimeOnlyParamAnnot))
ctx.migrationWarning("`@compileTimeOnly(msg)` will be replaced by `scala.compiletime.error(msg)`", sym.sourcePos)
else if (sym.is(Deferred, butNot = Param) && !sym.isType && !sym.isSelfSym) {
if (!sym.owner.isClass || sym.owner.is(Module) || sym.owner.isAnonymousClass)
fail(OnlyClassesCanHaveDeclaredButUndefinedMembers(sym))
Expand Down
10 changes: 0 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -822,16 +822,6 @@ object RefChecks {
case _ =>
}
}
/* (Not enabled yet)
* See an explanation of compileTimeOnly in its scaladoc at scala.annotation.compileTimeOnly.
*
if (sym.isCompileTimeOnly) {
def defaultMsg =
sm"""Reference to ${sym.fullLocationString} should not have survived past type checking,
|it should have been processed and eliminated during expansion of an enclosing macro."""
// The getOrElse part should never happen, it's just here as a backstop.
ctx.error(sym.compileTimeOnlyMessage getOrElse defaultMsg, pos)
}*/
}

/** Check that a deprecated val or def does not override a
Expand Down
3 changes: 1 addition & 2 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,7 @@ class CompilationTests extends ParallelTesting {
"tests/neg-custom-args/toplevel-samesource/S.scala",
"tests/neg-custom-args/toplevel-samesource/nested/S.scala"),
defaultOptions),
compileFile("tests/neg-custom-args/i6300.scala", allowDeepSubtypes),
compileFile("tests/neg-custom-args/i6312.scala", defaultOptions and "-Xfatal-warnings" and "-migration")
compileFile("tests/neg-custom-args/i6300.scala", allowDeepSubtypes)
).checkExpectedErrors()
}

Expand Down
2 changes: 1 addition & 1 deletion library/src-3.x/scala/runtime/DynamicTuple.scala
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ object DynamicTuple {
def dynamic_*: [This <: Tuple, H] (self: Tuple, x: H): H *: This = {
type Result = H *: This
(self: Any) match {
case Unit =>
case () =>
Tuple1(x).asInstanceOf[Result]
case self: Tuple1[_] =>
Tuple2(x, self._1).asInstanceOf[Result]
Expand Down
12 changes: 12 additions & 0 deletions tests/neg/i6419.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
trait A {
@scala.annotation.compileTimeOnly("oops") def f: Int
}

class B extends A {
def f = 0
}

object App {
(new B).f
(new B: A).f // error
}
12 changes: 12 additions & 0 deletions tests/neg/i6419b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
trait A {
inline def f: Int = scala.compiletime.error("oops")
}

class B extends A {
override def f = 0
}

object App {
(new B).f
(new B: A).f // error
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Foo {
inline def foo: Unit = {
@scala.annotation.compileTimeOnly("some message") val res = ??? // error
@scala.annotation.compileTimeOnly("some message") val res = ???
res
}
}
14 changes: 14 additions & 0 deletions tests/pos/i6419.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class Foo {
inline def foo: Unit = {
@scala.annotation.compileTimeOnly("some message") val res = ???
res
}

inline def bar: Unit = {
foo
}

erased def baz: Unit = {
foo
}
}