diff --git a/compiler/src/dotty/tools/dotc/core/Constants.scala b/compiler/src/dotty/tools/dotc/core/Constants.scala index f45e9e5217de..ce8b6e03a415 100644 --- a/compiler/src/dotty/tools/dotc/core/Constants.scala +++ b/compiler/src/dotty/tools/dotc/core/Constants.scala @@ -2,12 +2,13 @@ package dotty.tools package dotc package core -import Types._, Symbols._, Contexts._ +import Types.*, Symbols.*, Contexts.* import printing.Printer import printing.Texts.Text -object Constants { +object Constants: + type Tag = Int inline val NoTag = 0 inline val UnitTag = 1 inline val BooleanTag = 2 @@ -22,7 +23,7 @@ object Constants { inline val NullTag = 11 inline val ClazzTag = 12 - class Constant(val value: Any, val tag: Int) extends printing.Showable with Product1[Any] { + class Constant(val value: Any, val tag: Tag) extends printing.Showable with Product1[Any]: import java.lang.Double.doubleToRawLongBits import java.lang.Float.floatToRawIntBits @@ -223,9 +224,8 @@ object Constants { def get: Any = value def isEmpty: Boolean = false def _1: Any = value - } - object Constant { + object Constant: def apply(x: Null): Constant = new Constant(x, NullTag) def apply(x: Unit): Constant = new Constant(x, UnitTag) def apply(x: Boolean): Constant = new Constant(x, BooleanTag) @@ -257,5 +257,5 @@ object Constants { ) def unapply(c: Constant): Constant = c - } -} + + end Constant diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 919598c41d6e..91667145d478 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -23,7 +23,7 @@ import config.Config import reporting._ import io.{AbstractFile, NoAbstractFile, PlainFile, Path} import scala.io.Codec -import collection.mutable +import collection.mutable, mutable.ArrayBuffer import printing._ import config.{JavaPlatform, SJSPlatform, Platform, ScalaSettings} import classfile.ReusableDataReader @@ -52,8 +52,10 @@ object Contexts { private val (notNullInfosLoc, store8) = store7.newLocation[List[NotNullInfo]]() private val (importInfoLoc, store9) = store8.newLocation[ImportInfo | Null]() private val (typeAssignerLoc, store10) = store9.newLocation[TypeAssigner](TypeAssigner) + private val (usagesLoc, store11) = store10.newLocation[Any]() // unusages feature + private val (deferredChecksLoc, store12) = store11.newLocation[DeferredChecks]() - private val initialStore = store10 + private val initialStore = store12 /** The current context */ inline def ctx(using ctx: Context): Context = ctx @@ -93,6 +95,9 @@ object Contexts { inline def atPhaseNoEarlier[T](limit: Phase)(inline op: Context ?=> T)(using Context): T = op(using if !limit.exists || limit <= ctx.phase then ctx else ctx.withPhase(limit)) + inline def deferredCheck(phase: String)(inline op: Context ?=> Unit)(using Context): Unit = + ctx.deferredChecks.add(phase)(op) + inline def inMode[T](mode: Mode)(inline op: Context ?=> T)(using ctx: Context): T = op(using if mode != ctx.mode then ctx.fresh.setMode(mode) else ctx) @@ -239,6 +244,8 @@ object Contexts { /** The current type assigner or typer */ def typeAssigner: TypeAssigner = store(typeAssignerLoc) + def deferredChecks: DeferredChecks = store(deferredChecksLoc) + /** The new implicit references that are introduced by this scope */ protected var implicitsCache: ContextualImplicits | Null = null def implicits: ContextualImplicits = { @@ -812,6 +819,7 @@ object Contexts { store = initialStore .updated(settingsStateLoc, settingsGroup.defaultState) .updated(notNullInfosLoc, Nil) + .updated(deferredChecksLoc, DeferredChecks()) .updated(compilationUnitLoc, NoCompilationUnit) searchHistory = new SearchRoot gadt = EmptyGadtConstraint @@ -954,13 +962,13 @@ object Contexts { protected[dotc] val indentTab: String = " " - private[Contexts] val exploreContexts = new mutable.ArrayBuffer[FreshContext] + private[Contexts] val exploreContexts = ArrayBuffer.empty[FreshContext] private[Contexts] var exploresInUse: Int = 0 - private[Contexts] val changeOwnerContexts = new mutable.ArrayBuffer[FreshContext] + private[Contexts] val changeOwnerContexts = ArrayBuffer.empty[FreshContext] private[Contexts] var changeOwnersInUse: Int = 0 - private[Contexts] val comparers = new mutable.ArrayBuffer[TypeComparer] + private[Contexts] val comparers = ArrayBuffer.empty[TypeComparer] private[Contexts] var comparersInUse: Int = 0 private var charArray = new Array[Char](256) @@ -996,4 +1004,15 @@ object Contexts { if (thread == null) thread = Thread.currentThread() else assert(thread == Thread.currentThread(), "illegal multithreaded access to ContextBase") } + + class DeferredChecks: + private val checks = mutable.Map[String, ArrayBuffer[Context ?=> Unit]]() + + def add(phase: String)(item: Context ?=> Unit): Unit = checks.getOrElseUpdate(phase, ArrayBuffer.empty).addOne(item) + + def run(phase: String)(using Context): Unit = + for items <- checks.remove(phase) do + for item <- items do + item + end DeferredChecks } diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 59440d1cb965..b2ac074d6a34 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -301,6 +301,13 @@ object Decorators { def ex(args: Shown*)(using Context): String = explained(new StringFormatter(sc).assemble(args)) + /** Print a message and stack trace, for debugging only. + */ + @deprecated("Stack trace facility for debugging only.", since="3.2") + def tr(args: Shown*)(using Context): Unit = + Console.err.println(i(args*)) + Thread.dumpStack() + extension [T <: AnyRef](arr: Array[T]) def binarySearch(x: T | Null): Int = java.util.Arrays.binarySearch(arr.asInstanceOf[Array[Object | Null]], x) diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index b4a2dcac1b85..c51cbaa89aa4 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -57,10 +57,10 @@ object Phases { final def phasePlan: List[List[Phase]] = this.phasesPlan final def setPhasePlan(phasess: List[List[Phase]]): Unit = this.phasesPlan = phasess - /** Squash TreeTransform's beloning to same sublist to a single TreeTransformer - * Each TreeTransform gets own period, - * whereas a combined TreeTransformer gets period equal to union of periods of it's TreeTransforms - */ + /** Squash TreeTransforms that belong to same sublist to a single TreeTransformer. + * Each TreeTransform gets own period, + * whereas a combined TreeTransformer gets period equal to union of periods of its TreeTransforms. + */ final def fusePhases(phasess: List[List[Phase]], phasesToSkip: List[String], stopBeforePhases: List[String], diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index d1a88406fe45..e5bbd5d3e293 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -78,14 +78,18 @@ object Inlines: * @return An `Inlined` node that refers to the original call and the inlined bindings * and body that replace it. */ - def inlineCall(tree: Tree)(using Context): Tree = + def inlineCall(tree: Tree)(using Context): Tree = inlineCall(tree, retainer = false) + + private def inlineCall(tree: Tree, retainer: Boolean)(using Context): Tree = if tree.symbol.denot != SymDenotations.NoDenotation && tree.symbol.effectiveOwner == defn.CompiletimeTestingPackage.moduleClass then if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree) if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree) - CrossVersionChecks.checkExperimentalRef(tree.symbol, tree.srcPos) + if !retainer then + CrossVersionChecks.checkExperimentalRef(tree.symbol, tree.srcPos) + CrossVersionChecks.checkDeprecatedDeferred(tree.symbol, tree.srcPos) if tree.symbol.isConstructor then return tree // error already reported for the inline constructor definition @@ -240,7 +244,8 @@ object Inlines: retainer.deriveTargetNameAnnotation(meth, name => BodyRetainerName(name.asTermName)) DefDef(retainer, prefss => inlineCall( - ref(meth).appliedToArgss(prefss).withSpan(mdef.rhs.span.startPos))( + ref(meth).appliedToArgss(prefss).withSpan(mdef.rhs.span.startPos), + retainer = true)( using ctx.withOwner(retainer))) .showing(i"retainer for $meth: $result", inlining) diff --git a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala index 81b1de67b707..475b99955cb1 100644 --- a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala +++ b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala @@ -11,6 +11,7 @@ import Constants._ import Names._ import StdNames._ import Contexts._ +import transform.CrossVersionChecks import transform.TypeUtils._ object ConstFold: @@ -27,14 +28,13 @@ object ConstFold: def Apply[T <: Apply](tree: T)(using Context): T = tree.fun match - case Select(xt, op) if foldedBinops.contains(op) => + case fun @ Select(xt, op) if foldedBinops.contains(op) => xt match case ConstantTree(x) => tree.args match - case yt :: Nil => - yt match - case ConstantTree(y) => tree.withFoldedType(foldBinop(op, x, y)) - case _ => tree + case ConstantTree(y) :: Nil => + CrossVersionChecks.checkDeprecatedDeferred(fun.symbol, fun.srcPos) + tree.withFoldedType(foldBinop(op, x, y)) case _ => tree case _ => tree case TypeApply(Select(qual, nme.getClass_), _) @@ -73,7 +73,7 @@ object ConstFold: private def withFoldedType(c: Constant | Null): T = if c == null then tree else tree.withType(ConstantType(c)).asInstanceOf[T] - private def foldUnop(op: Name, x: Constant): Constant | Null = (op, x.tag) match { + private def foldUnop(op: Name, x: Constant): Constant | Null = (op, x.tag) match case (nme.UNARY_!, BooleanTag) => Constant(!x.booleanValue) case (nme.UNARY_~ , IntTag ) => Constant(~x.intValue) @@ -90,104 +90,97 @@ object ConstFold: case (nme.UNARY_- , DoubleTag ) => Constant(-x.doubleValue) case _ => null - } - - /** These are local helpers to keep foldBinop from overly taxing the - * optimizer. - */ - private def foldBooleanOp(op: Name, x: Constant, y: Constant): Constant | Null = op match { - case nme.ZOR => Constant(x.booleanValue | y.booleanValue) - case nme.OR => Constant(x.booleanValue | y.booleanValue) - case nme.XOR => Constant(x.booleanValue ^ y.booleanValue) - case nme.ZAND => Constant(x.booleanValue & y.booleanValue) - case nme.AND => Constant(x.booleanValue & y.booleanValue) - case nme.EQ => Constant(x.booleanValue == y.booleanValue) - case nme.NE => Constant(x.booleanValue != y.booleanValue) - case _ => null - } - private def foldSubrangeOp(op: Name, x: Constant, y: Constant): Constant | Null = op match { - case nme.OR => Constant(x.intValue | y.intValue) - case nme.XOR => Constant(x.intValue ^ y.intValue) - case nme.AND => Constant(x.intValue & y.intValue) - case nme.LSL => Constant(x.intValue << y.intValue) - case nme.LSR => Constant(x.intValue >>> y.intValue) - case nme.ASR => Constant(x.intValue >> y.intValue) - case nme.EQ => Constant(x.intValue == y.intValue) - case nme.NE => Constant(x.intValue != y.intValue) - case nme.LT => Constant(x.intValue < y.intValue) - case nme.GT => Constant(x.intValue > y.intValue) - case nme.LE => Constant(x.intValue <= y.intValue) - case nme.GE => Constant(x.intValue >= y.intValue) - case nme.ADD => Constant(x.intValue + y.intValue) - case nme.SUB => Constant(x.intValue - y.intValue) - case nme.MUL => Constant(x.intValue * y.intValue) - case nme.DIV => Constant(x.intValue / y.intValue) - case nme.MOD => Constant(x.intValue % y.intValue) - case _ => null - } - private def foldLongOp(op: Name, x: Constant, y: Constant): Constant | Null = op match { - case nme.OR => Constant(x.longValue | y.longValue) - case nme.XOR => Constant(x.longValue ^ y.longValue) - case nme.AND => Constant(x.longValue & y.longValue) - case nme.LSL => if (x.tag <= IntTag) Constant(x.intValue << y.longValue.toInt) else Constant(x.longValue << y.longValue) - case nme.LSR => if (x.tag <= IntTag) Constant(x.intValue >>> y.longValue.toInt) else Constant(x.longValue >>> y.longValue) - case nme.ASR => if (x.tag <= IntTag) Constant(x.intValue >> y.longValue.toInt) else Constant(x.longValue >> y.longValue) - case nme.EQ => Constant(x.longValue == y.longValue) - case nme.NE => Constant(x.longValue != y.longValue) - case nme.LT => Constant(x.longValue < y.longValue) - case nme.GT => Constant(x.longValue > y.longValue) - case nme.LE => Constant(x.longValue <= y.longValue) - case nme.GE => Constant(x.longValue >= y.longValue) - case nme.ADD => Constant(x.longValue + y.longValue) - case nme.SUB => Constant(x.longValue - y.longValue) - case nme.MUL => Constant(x.longValue * y.longValue) - case nme.DIV => Constant(x.longValue / y.longValue) - case nme.MOD => Constant(x.longValue % y.longValue) - case _ => null - } - private def foldFloatOp(op: Name, x: Constant, y: Constant): Constant | Null = op match { - case nme.EQ => Constant(x.floatValue == y.floatValue) - case nme.NE => Constant(x.floatValue != y.floatValue) - case nme.LT => Constant(x.floatValue < y.floatValue) - case nme.GT => Constant(x.floatValue > y.floatValue) - case nme.LE => Constant(x.floatValue <= y.floatValue) - case nme.GE => Constant(x.floatValue >= y.floatValue) - case nme.ADD => Constant(x.floatValue + y.floatValue) - case nme.SUB => Constant(x.floatValue - y.floatValue) - case nme.MUL => Constant(x.floatValue * y.floatValue) - case nme.DIV => Constant(x.floatValue / y.floatValue) - case nme.MOD => Constant(x.floatValue % y.floatValue) - case _ => null - } - private def foldDoubleOp(op: Name, x: Constant, y: Constant): Constant | Null = op match { - case nme.EQ => Constant(x.doubleValue == y.doubleValue) - case nme.NE => Constant(x.doubleValue != y.doubleValue) - case nme.LT => Constant(x.doubleValue < y.doubleValue) - case nme.GT => Constant(x.doubleValue > y.doubleValue) - case nme.LE => Constant(x.doubleValue <= y.doubleValue) - case nme.GE => Constant(x.doubleValue >= y.doubleValue) - case nme.ADD => Constant(x.doubleValue + y.doubleValue) - case nme.SUB => Constant(x.doubleValue - y.doubleValue) - case nme.MUL => Constant(x.doubleValue * y.doubleValue) - case nme.DIV => Constant(x.doubleValue / y.doubleValue) - case nme.MOD => Constant(x.doubleValue % y.doubleValue) - case _ => null - } - private def foldStringOp(op: Name, x: Constant, y: Constant): Constant | Null = op match { - case nme.ADD => Constant(x.stringValue + y.stringValue) - case nme.EQ => Constant(x.stringValue == y.stringValue) - case nme.NE => Constant(x.stringValue != y.stringValue) - case _ => null - } - - private def foldNullOp(op: Name, x: Constant, y: Constant): Constant | Null= - assert(x.tag == NullTag || y.tag == NullTag) - op match - case nme.EQ => Constant(x.tag == y.tag) - case nme.NE => Constant(x.tag != y.tag) + end foldUnop + + private def foldBinop(op: Name, x: Constant, y: Constant)(using Context): Constant | Null = + val nme = StdNames.nme + import nme.{Constant as _, *} + inline def foldBooleanOp: Constant | Null = op match + case ZOR => Constant(x.booleanValue | y.booleanValue) + case OR => Constant(x.booleanValue | y.booleanValue) + case XOR => Constant(x.booleanValue ^ y.booleanValue) + case ZAND => Constant(x.booleanValue & y.booleanValue) + case AND => Constant(x.booleanValue & y.booleanValue) + case EQ => Constant(x.booleanValue == y.booleanValue) + case NE => Constant(x.booleanValue != y.booleanValue) case _ => null - - private def foldBinop(op: Name, x: Constant, y: Constant): Constant | Null = + inline def foldSubrangeOp: Constant | Null = op match + case OR => Constant(x.intValue | y.intValue) + case XOR => Constant(x.intValue ^ y.intValue) + case AND => Constant(x.intValue & y.intValue) + case LSL => Constant(x.intValue << y.intValue) + case LSR => Constant(x.intValue >>> y.intValue) + case ASR => Constant(x.intValue >> y.intValue) + case EQ => Constant(x.intValue == y.intValue) + case NE => Constant(x.intValue != y.intValue) + case LT => Constant(x.intValue < y.intValue) + case GT => Constant(x.intValue > y.intValue) + case LE => Constant(x.intValue <= y.intValue) + case GE => Constant(x.intValue >= y.intValue) + case ADD => Constant(x.intValue + y.intValue) + case SUB => Constant(x.intValue - y.intValue) + case MUL => Constant(x.intValue * y.intValue) + case DIV => Constant(x.intValue / y.intValue) + case MOD => Constant(x.intValue % y.intValue) + case _ => null + inline def foldLongOp: Constant | Null = op match + case OR => Constant(x.longValue | y.longValue) + case XOR => Constant(x.longValue ^ y.longValue) + case AND => Constant(x.longValue & y.longValue) + case LSL => if (x.tag <= IntTag) Constant(x.intValue << y.longValue.toInt) else Constant(x.longValue << y.longValue) + case LSR => if (x.tag <= IntTag) Constant(x.intValue >>> y.longValue.toInt) else Constant(x.longValue >>> y.longValue) + case ASR => if (x.tag <= IntTag) Constant(x.intValue >> y.longValue.toInt) else Constant(x.longValue >> y.longValue) + case EQ => Constant(x.longValue == y.longValue) + case NE => Constant(x.longValue != y.longValue) + case LT => Constant(x.longValue < y.longValue) + case GT => Constant(x.longValue > y.longValue) + case LE => Constant(x.longValue <= y.longValue) + case GE => Constant(x.longValue >= y.longValue) + case ADD => Constant(x.longValue + y.longValue) + case SUB => Constant(x.longValue - y.longValue) + case MUL => Constant(x.longValue * y.longValue) + case DIV => Constant(x.longValue / y.longValue) + case MOD => Constant(x.longValue % y.longValue) + case _ => null + inline def foldFloatOp: Constant | Null = op match + case EQ => Constant(x.floatValue == y.floatValue) + case NE => Constant(x.floatValue != y.floatValue) + case LT => Constant(x.floatValue < y.floatValue) + case GT => Constant(x.floatValue > y.floatValue) + case LE => Constant(x.floatValue <= y.floatValue) + case GE => Constant(x.floatValue >= y.floatValue) + case ADD => Constant(x.floatValue + y.floatValue) + case SUB => Constant(x.floatValue - y.floatValue) + case MUL => Constant(x.floatValue * y.floatValue) + case DIV => Constant(x.floatValue / y.floatValue) + case MOD => Constant(x.floatValue % y.floatValue) + case _ => null + inline def foldDoubleOp: Constant | Null = op match + case EQ => Constant(x.doubleValue == y.doubleValue) + case NE => Constant(x.doubleValue != y.doubleValue) + case LT => Constant(x.doubleValue < y.doubleValue) + case GT => Constant(x.doubleValue > y.doubleValue) + case LE => Constant(x.doubleValue <= y.doubleValue) + case GE => Constant(x.doubleValue >= y.doubleValue) + case ADD => Constant(x.doubleValue + y.doubleValue) + case SUB => Constant(x.doubleValue - y.doubleValue) + case MUL => Constant(x.doubleValue * y.doubleValue) + case DIV => Constant(x.doubleValue / y.doubleValue) + case MOD => Constant(x.doubleValue % y.doubleValue) + case _ => null + inline def foldStringOp: Constant | Null = op match + case ADD => Constant(x.stringValue + y.stringValue) + case EQ => Constant(x.stringValue == y.stringValue) + case NE => Constant(x.stringValue != y.stringValue) + case _ => null + inline def foldNullOp: Constant | Null = + assert(x.tag == NullTag || y.tag == NullTag) + op match + case EQ => Constant(x.tag == y.tag) + case NE => Constant(x.tag != y.tag) + case _ => null + + // begin foldBinop val optag = if (x.tag == y.tag) x.tag else if (x.isNumeric && y.isNumeric) math.max(x.tag, y.tag) @@ -195,16 +188,16 @@ object ConstFold: else NoTag try optag match - case BooleanTag => foldBooleanOp(op, x, y) - case ByteTag | ShortTag | CharTag | IntTag => foldSubrangeOp(op, x, y) - case LongTag => foldLongOp(op, x, y) - case FloatTag => foldFloatOp(op, x, y) - case DoubleTag => foldDoubleOp(op, x, y) - case StringTag => foldStringOp(op, x, y) - case NullTag => foldNullOp(op, x, y) - case _ => null - catch case ex: ArithmeticException => null // the code will crash at runtime, - // but that is better than the - // compiler itself crashing + case BooleanTag => foldBooleanOp + case ByteTag | ShortTag | CharTag | IntTag => foldSubrangeOp + case LongTag => foldLongOp + case FloatTag => foldFloatOp + case DoubleTag => foldDoubleOp + case StringTag => foldStringOp + case NullTag => foldNullOp + case _ => null + catch case ex: ArithmeticException => null // the code will crash at runtime, + // but that is better than the + // compiler itself crashing end foldBinop end ConstFold diff --git a/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala b/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala index 044dd7bb8528..8a88b3819950 100644 --- a/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala @@ -8,10 +8,9 @@ import util.SrcPos import config.{ScalaVersion, NoScalaVersion, Feature, ScalaRelease} import MegaPhase.MiniPhase import scala.util.{Failure, Success} -import ast.tpd +import ast.tpd.* class CrossVersionChecks extends MiniPhase: - import tpd.* import CrossVersionChecks.* override def phaseName: String = CrossVersionChecks.name @@ -21,109 +20,6 @@ class CrossVersionChecks extends MiniPhase: override def runsAfterGroupsOf: Set[String] = Set(FirstTransform.name) // We assume all type trees except TypeTree have been eliminated - // Note: if a symbol has both @deprecated and @migration annotations and both - // warnings are enabled, only the first one checked here will be emitted. - // I assume that's a consequence of some code trying to avoid noise by suppressing - // warnings after the first, but I think it'd be better if we didn't have to - // arbitrarily choose one as more important than the other. - private def checkUndesiredProperties(sym: Symbol, pos: SrcPos)(using Context): Unit = - checkDeprecated(sym, pos) - checkExperimentalRef(sym, pos) - - val xMigrationValue = ctx.settings.Xmigration.value - if xMigrationValue != NoScalaVersion then - checkMigration(sym, pos, xMigrationValue) - end checkUndesiredProperties - - /** If @deprecated is present, and the point of reference is not enclosed - * in either a deprecated member or a scala bridge method, issue a warning. - */ - private def checkDeprecated(sym: Symbol, pos: SrcPos)(using Context): Unit = - - /** is the owner an enum or its companion and also the owner of sym */ - def isEnumOwner(owner: Symbol)(using Context) = - // pre: sym is an enumcase - if owner.isEnumClass then owner.companionClass eq sym.owner - else if owner.is(ModuleClass) && owner.companionClass.isEnumClass then owner eq sym.owner - else false - - def isDeprecatedOrEnum(owner: Symbol)(using Context) = - // pre: sym is an enumcase - owner.isDeprecated - || isEnumOwner(owner) - - /**Scan the chain of outer declaring scopes from the current context - * a deprecation warning will be skipped if one the following holds - * for a given declaring scope: - * - the symbol associated with the scope is also deprecated. - * - if and only if `sym` is an enum case, the scope is either - * a module that declares `sym`, or the companion class of the - * module that declares `sym`. - */ - def skipWarning(using Context) = - ctx.owner.ownersIterator.exists(if sym.isEnumCase then isDeprecatedOrEnum else _.isDeprecated) - - for annot <- sym.getAnnotation(defn.DeprecatedAnnot) do - if !skipWarning then - val msg = annot.argumentConstant(0).map(": " + _.stringValue).getOrElse("") - val since = annot.argumentConstant(1).map(" since " + _.stringValue).getOrElse("") - report.deprecationWarning(s"${sym.showLocated} is deprecated${since}${msg}", pos) - - private def checkExperimentalSignature(sym: Symbol, pos: SrcPos)(using Context): Unit = - class Checker extends TypeTraverser: - def traverse(tp: Type): Unit = - if tp.typeSymbol.isExperimental then - Feature.checkExperimentalDef(tp.typeSymbol, pos) - else - traverseChildren(tp) - if !sym.isInExperimentalScope then - new Checker().traverse(sym.info) - - private def checkExperimentalAnnots(sym: Symbol)(using Context): Unit = - if !sym.isInExperimentalScope then - for annot <- sym.annotations if annot.symbol.isExperimental do - Feature.checkExperimentalDef(annot.symbol, annot.tree) - - /** If @migration is present (indicating that the symbol has changed semantics between versions), - * emit a warning. - */ - private def checkMigration(sym: Symbol, pos: SrcPos, xMigrationValue: ScalaVersion)(using Context): Unit = - for annot <- sym.getAnnotation(defn.MigrationAnnot) do - val migrationVersion = ScalaVersion.parse(annot.argumentConstant(1).get.stringValue) - migrationVersion match - case Success(symVersion) if xMigrationValue < symVersion => - val msg = annot.argumentConstant(0).get.stringValue - report.warning(SymbolChangedSemanticsInVersion(sym, symVersion, msg), pos) - case Failure(ex) => - report.warning(SymbolHasUnparsableVersionNumber(sym, ex.getMessage.nn), pos) - case _ => - - /** Check that a deprecated val or def does not override a - * concrete, non-deprecated method. If it does, then - * deprecation is meaningless. - */ - private def checkDeprecatedOvers(tree: Tree)(using Context): Unit = { - val symbol = tree.symbol - if (symbol.isDeprecated) { - val concrOvers = - symbol.allOverriddenSymbols.filter(sym => - !sym.isDeprecated && !sym.is(Deferred)) - if (!concrOvers.isEmpty) - report.deprecationWarning( - symbol.toString + " overrides concrete, non-deprecated symbol(s):" + - concrOvers.map(_.name).mkString(" ", ", ", ""), tree.srcPos) - } - } - - /** Check that classes extending experimental classes or nested in experimental classes have the @experimental annotation. */ - private def checkExperimentalInheritance(cls: ClassSymbol)(using Context): Unit = - if !cls.isAnonymousClass && !cls.hasAnnotation(defn.ExperimentalAnnot) then - cls.info.parents.find(_.typeSymbol.isExperimental) match - case Some(parent) => - report.error(em"extension of experimental ${parent.typeSymbol} must have @experimental annotation", cls.srcPos) - case _ => - end checkExperimentalInheritance - override def transformValDef(tree: ValDef)(using Context): ValDef = checkDeprecatedOvers(tree) checkExperimentalAnnots(tree.symbol) @@ -142,22 +38,19 @@ class CrossVersionChecks extends MiniPhase: checkExperimentalAnnots(cls) tree - override def transformIdent(tree: Ident)(using Context): Ident = { + override def transformIdent(tree: Ident)(using Context): Ident = checkUndesiredProperties(tree.symbol, tree.srcPos) tree - } - override def transformSelect(tree: Select)(using Context): Select = { + override def transformSelect(tree: Select)(using Context): Select = checkUndesiredProperties(tree.symbol, tree.srcPos) tree - } - override def transformNew(tree: New)(using Context): New = { + override def transformNew(tree: New)(using Context): New = checkUndesiredProperties(tree.tpe.typeSymbol, tree.srcPos) tree - } - override def transformTypeTree(tree: TypeTree)(using Context): TypeTree = { + override def transformTypeTree(tree: TypeTree)(using Context): TypeTree = val tpe = tree.tpe tpe.foreachPart { case TypeRef(_, sym: Symbol) => @@ -169,12 +62,14 @@ class CrossVersionChecks extends MiniPhase: case _ => } tree - } - override def transformTypeDef(tree: TypeDef)(using Context): TypeDef = { + override def transformTypeDef(tree: TypeDef)(using Context): TypeDef = checkExperimentalAnnots(tree.symbol) tree - } + + override def transformUnit(tree: Tree)(using Context): Tree = + ctx.deferredChecks.run(name) + super.transformUnit(tree) override def transformOther(tree: Tree)(using Context): Tree = tree match case tree: Import => @@ -197,3 +92,108 @@ object CrossVersionChecks: def checkExperimentalRef(sym: Symbol, pos: SrcPos)(using Context): Unit = if sym.isExperimental && !ctx.owner.isInExperimentalScope then Feature.checkExperimentalDef(sym, pos) + + // Note: if a symbol has both @deprecated and @migration annotations and both + // warnings are enabled, only the first one checked here will be emitted. + // I assume that's a consequence of some code trying to avoid noise by suppressing + // warnings after the first, but I think it'd be better if we didn't have to + // arbitrarily choose one as more important than the other. + def checkUndesiredProperties(sym: Symbol, pos: SrcPos)(using Context): Unit = + checkDeprecated(sym, pos) + checkExperimentalRef(sym, pos) + val xMigrationValue = ctx.settings.Xmigration.value + if xMigrationValue != NoScalaVersion then + checkMigration(sym, pos, xMigrationValue) + end checkUndesiredProperties + + /** If @deprecated is present, and the point of reference is not enclosed + * in either a deprecated member or a scala bridge method, issue a warning. + */ + def checkDeprecated(sym: Symbol, pos: SrcPos)(using Context): Unit = checkDeprecatedImpl(sym, pos, deferred = false) + /** Defer check to avoid cycling. */ + def checkDeprecatedDeferred(sym: Symbol, pos: SrcPos)(using Context): Unit = checkDeprecatedImpl(sym, pos, deferred = true) + + private def checkDeprecatedImpl(sym: Symbol, pos: SrcPos, deferred: Boolean)(using Context): Unit = + + // pre: sym is an enumcase + def isDeprecatedOrEnum(owner: Symbol)(using Context): Boolean = + /** is the owner an enum or its companion and also the owner of sym */ + def isEnumOwner: Boolean = + if owner.isEnumClass then owner.companionClass eq sym.owner + else if owner.is(ModuleClass) && owner.companionClass.isEnumClass then owner eq sym.owner + else false + owner.isDeprecated || isEnumOwner + + /** Scan the chain of outer declaring scopes from the current context + * a deprecation warning will be skipped if one the following holds + * for a given declaring scope: + * - the symbol associated with the scope is also deprecated. + * - if and only if `sym` is an enum case, the scope is either + * a module that declares `sym`, or the companion class of the + * module that declares `sym`. + */ + def skipWarningAt(owner: Symbol)(using Context) = owner.ownersIterator.exists(if sym.isEnumCase then isDeprecatedOrEnum else _.isDeprecated) + + for annot <- sym.getAnnotation(defn.DeprecatedAnnot) do + val text = + val msg = annot.argumentConstant(0).map(": " + _.stringValue).getOrElse("") + val since = annot.argumentConstant(1).map(" since " + _.stringValue).getOrElse("") + s"${sym.showLocated} is deprecated${since}${msg}" + val owner = ctx.owner + if deferred then + ctx.deferredChecks.add(name) { + if !skipWarningAt(owner) then report.deprecationWarning(text, pos) + } + else if !skipWarningAt(owner) then report.deprecationWarning(text, pos) + end checkDeprecatedImpl + + def checkExperimentalSignature(sym: Symbol, pos: SrcPos)(using Context): Unit = + class Checker extends TypeTraverser: + def traverse(tp: Type): Unit = + if tp.typeSymbol.isExperimental then + Feature.checkExperimentalDef(tp.typeSymbol, pos) + else + traverseChildren(tp) + if !sym.isInExperimentalScope then + new Checker().traverse(sym.info) + + def checkExperimentalAnnots(sym: Symbol)(using Context): Unit = + if !sym.isInExperimentalScope then + for annot <- sym.annotations if annot.symbol.isExperimental do + Feature.checkExperimentalDef(annot.symbol, annot.tree) + + /** If @migration is present (indicating that the symbol has changed semantics between versions), + * emit a warning. + */ + def checkMigration(sym: Symbol, pos: SrcPos, xMigrationValue: ScalaVersion)(using Context): Unit = + for annot <- sym.getAnnotation(defn.MigrationAnnot) do + val migrationVersion = ScalaVersion.parse(annot.argumentConstant(1).get.stringValue) + migrationVersion match + case Success(symVersion) if xMigrationValue < symVersion => + val msg = annot.argumentConstant(0).get.stringValue + report.warning(SymbolChangedSemanticsInVersion(sym, symVersion, msg), pos) + case Failure(ex) => + report.warning(SymbolHasUnparsableVersionNumber(sym, ex.getMessage.nn), pos) + case _ => + + /** Check that a deprecated val or def does not override a + * concrete, non-deprecated method. If it does, then + * deprecation is meaningless. + */ + def checkDeprecatedOvers(tree: Tree)(using Context): Unit = + val symbol = tree.symbol + if symbol.isDeprecated then + val concrOvers = symbol.allOverriddenSymbols.filter(sym => !sym.isDeprecated && !sym.is(Deferred)) + if !concrOvers.isEmpty then + report.deprecationWarning( + s"$symbol overrides concrete, non-deprecated symbol(s):${ + concrOvers.map(_.name).mkString(" ", ", ", "")}", tree.srcPos) + + /** Check that classes extending experimental classes or nested in experimental classes have the @experimental annotation. */ + def checkExperimentalInheritance(cls: ClassSymbol)(using Context): Unit = + if !cls.isAnonymousClass && !cls.hasAnnotation(defn.ExperimentalAnnot) then + cls.info.parents.find(_.typeSymbol.isExperimental) match + case Some(parent) => + report.error(em"extension of experimental ${parent.typeSymbol} must have @experimental annotation", cls.srcPos) + case _ => + end checkExperimentalInheritance diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 7099234c80e1..6b03b8599f21 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -127,7 +127,7 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking catch { case NonFatal(ex) => if ctx.phase != Phases.typerPhase && ctx.phase != Phases.inliningPhase then - println(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}") + println(i"exception in ${ctx.phase} while typing $tree of class ${tree.getClass} # ${tree.uniqueId}") throw ex } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 8d7a16dad8a4..b480d05478ca 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -191,6 +191,7 @@ class CompilationTests { compileFile("tests/neg-custom-args/i13026.scala", defaultOptions.and("-print-lines")), compileFile("tests/neg-custom-args/i13838.scala", defaultOptions.and("-Ximplicit-search-limit", "1000")), compileFile("tests/neg-custom-args/jdk-9-app.scala", defaultOptions.and("-release:8")), + compileDir("tests/neg-no-args", TestFlags(basicClasspath, Array.empty)), ).checkExpectedErrors() } diff --git a/tests/neg-no-args/i2968c.check b/tests/neg-no-args/i2968c.check new file mode 100644 index 000000000000..340c4d1a53e1 --- /dev/null +++ b/tests/neg-no-args/i2968c.check @@ -0,0 +1,5 @@ +-- Error: tests/neg-no-args/i2968c.scala:3:19 ---------------------------------- +3 |def f = 0x01030507 << 36L == 42 // error + | ^^^^^^^^^^^^^ + |method << in class Int is deprecated since 2.12.7: shifting a value by a `Long` argument is deprecated (except when the value is a `Long`). + |Call `toInt` on the argument to maintain the current behavior and avoid the deprecation warning. diff --git a/tests/neg-no-args/i2968c.scala b/tests/neg-no-args/i2968c.scala new file mode 100644 index 000000000000..2ff3fcd9c951 --- /dev/null +++ b/tests/neg-no-args/i2968c.scala @@ -0,0 +1,8 @@ +// scalac: -Werror -deprecation + +def f = 0x01030507 << 36L == 42 // error +//def f = 0x01030507 << 36L == 271601776 //?error + +@deprecated("Usage from deprecated class is OK", since="0.1") +class C: + def test = 0x01030507 << 36L == 271601776 // noerror diff --git a/tests/neg/i2968-ycheck.check b/tests/neg/i2968-ycheck.check new file mode 100644 index 000000000000..09c9baa983e8 --- /dev/null +++ b/tests/neg/i2968-ycheck.check @@ -0,0 +1,5 @@ +-- Error: tests/neg/i2968-ycheck.scala:3:19 ---------------------------------------------------------------------------- +3 |def f = 0x01030507 << 36L == 42 // error + | ^^^^^^^^^^^^^ + |method << in class Int is deprecated since 2.12.7: shifting a value by a `Long` argument is deprecated (except when the value is a `Long`). + |Call `toInt` on the argument to maintain the current behavior and avoid the deprecation warning. diff --git a/tests/neg/i2968-ycheck.scala b/tests/neg/i2968-ycheck.scala new file mode 100644 index 000000000000..2ff3fcd9c951 --- /dev/null +++ b/tests/neg/i2968-ycheck.scala @@ -0,0 +1,8 @@ +// scalac: -Werror -deprecation + +def f = 0x01030507 << 36L == 42 // error +//def f = 0x01030507 << 36L == 271601776 //?error + +@deprecated("Usage from deprecated class is OK", since="0.1") +class C: + def test = 0x01030507 << 36L == 271601776 // noerror diff --git a/tests/neg/i2968.check b/tests/neg/i2968.check new file mode 100644 index 000000000000..579f658e5dac --- /dev/null +++ b/tests/neg/i2968.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i2968.scala:6:27 ----------------------------------------------------------------------------------- +6 |@main def test() = println(g) // error + | ^ + | method g is deprecated since 0.1: 42 is Jackie Robinson's retired number diff --git a/tests/neg/i2968.scala b/tests/neg/i2968.scala new file mode 100644 index 000000000000..f55a4d95b004 --- /dev/null +++ b/tests/neg/i2968.scala @@ -0,0 +1,10 @@ +// scalac: -Werror -deprecation + +@deprecated("42 is Jackie Robinson's retired number", since="0.1") +inline def g = 42 + +@main def test() = println(g) // error + +@deprecated("Usage from deprecated class is OK", since="0.1") +class C: + def test() = println(g) // noerror