diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index e66606fd2882..6eca942d727a 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -550,15 +550,41 @@ object Erasure { if (!ctx.mode.is(Mode.Type)) { if (isErased(tree)) report.error(em"${tree.symbol} is declared as erased, but is in fact used", tree.srcPos) - tree.symbol.getAnnotation(defn.CompileTimeOnlyAnnot) match { - case Some(annot) => - def defaultMsg = - i"""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) - report.error(message, tree.srcPos) - case _ => // OK - } + + def parentSyms(tpe: Type): LazyList[Symbol] = + val ref = tpe.widen.finalResultType + ref.classSymbol #:: ref.parents.to(LazyList).flatMap(parentSyms) + + def checkTree(tree: Tree, pos: util.SrcPos, toCheck: LazyList[Symbol]): Boolean = + toCheck.exists { sym => + sym.getAnnotation(defn.CompileTimeOnlyAnnot) match { + case Some(annot) => + def defaultMsg = + i"""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) + report.error(message, pos) + true + case _ => // OK + false + } + } + + tree match + case ddef: DefDef => + for + vparams <- ddef.vparamss + vparam <- vparams + do + checkTree(vparam, vparam.tpt.srcPos, parentSyms(vparam.tpt.tpe)) + checkTree(ddef, ddef.tpt.srcPos, parentSyms(ddef.tpt.tpe)) + + case vdef: ValDef => checkTree(vdef, vdef.tpt.srcPos, parentSyms(vdef.tpt.tpe)) + + case tree => + // in the other branches we avoid checking the symbol itself + // in-case it is annotated for downstream members + checkTree(tree, tree.srcPos, tree.symbol #:: parentSyms(tree.tpe)) } tree } @@ -844,8 +870,8 @@ object Erasure { override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = if (sym.isEffectivelyErased) erasedDef(sym) else - super.typedValDef(untpd.cpy.ValDef(vdef)( - tpt = untpd.TypedSplice(TypeTree(sym.info).withSpan(vdef.tpt.span))), sym) + checkNotErased(super.typedValDef(untpd.cpy.ValDef(vdef)( + tpt = untpd.TypedSplice(TypeTree(sym.info).withSpan(vdef.tpt.span))), sym)) /** Besides normal typing, this function also compacts anonymous functions * with more than `MaxImplementedFunctionArity` parameters to use a single @@ -889,7 +915,7 @@ object Erasure { vparamss = vparams :: Nil, tpt = untpd.TypedSplice(TypeTree(restpe).withSpan(ddef.tpt.span)), rhs = rhs1) - super.typedDefDef(ddef1, sym) + checkNotErased(super.typedDefDef(ddef1, sym)) end typedDefDef /** The outer parameter definition of a constructor if it needs one */ diff --git a/tests/neg/i10122.scala b/tests/neg/i10122.scala new file mode 100644 index 000000000000..1d19ac4293f8 --- /dev/null +++ b/tests/neg/i10122.scala @@ -0,0 +1,17 @@ +import scala.annotation.compileTimeOnly + +@compileTimeOnly("FooBar can not be used as an expression") trait FooBar + +object foo extends FooBar // error + +val fooAnon = new FooBar {} // error // error +val fooRef1: FooBar = ??? // error +def fooRef2: FooBar = ??? // error +def useFoo(foo: FooBar): foo.type = foo // error // error // error +val bar = fooRef2 // error + +@compileTimeOnly("baz can not be used as an expression") val baz = 23 +val qux = baz // error + +@compileTimeOnly("quux can not be used as an expression") def quux = 47 +val quxx = quux // error diff --git a/tests/neg/i9825.scala b/tests/neg/i9825.scala new file mode 100644 index 000000000000..4108a4ac8a55 --- /dev/null +++ b/tests/neg/i9825.scala @@ -0,0 +1,8 @@ +object Module { + @deprecated("Module.Foo is deprecated") + type Foo[+A] = scala.List[A] +} + +object Test { + val m: Module.Foo[String] = List("wow") +}