diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 14664af30c9e..cbcc62b7fb6b 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -7,9 +7,9 @@ import typer.{TyperPhase, RefChecks} import parsing.Parser import Phases.Phase import transform._ -import dotty.tools.backend.jvm.{CollectSuperCalls, GenBCode} import dotty.tools.backend -import dotty.tools.dotc.transform.localopt.StringInterpolatorOpt +import backend.jvm.{CollectSuperCalls, GenBCode} +import localopt.StringInterpolatorOpt /** The central class of the dotc compiler. The job of a compiler is to create * runs, which process given `phases` in a given `rootContext`. @@ -67,23 +67,24 @@ class Compiler { new CheckLoopingImplicits, // Check that implicit defs do not call themselves in an infinite loop new BetaReduce, // Reduce closure applications new InlineVals, // Check right hand-sides of an `inline val`s - new ExpandSAMs) :: // Expand single abstract method closures to anonymous classes + new ExpandSAMs, // Expand single abstract method closures to anonymous classes + new ElimRepeated, // Rewrite vararg parameters and arguments + new RefChecks) :: // Various checks mostly related to abstract members and overriding List(new init.Checker) :: // Check initialization of objects - List(new ElimRepeated, // Rewrite vararg parameters and arguments + List(new CrossVersionChecks, // Check issues related to deprecated and experimental new ProtectedAccessors, // Add accessors for protected members new ExtensionMethods, // Expand methods of value classes with extension methods new UncacheGivenAliases, // Avoid caching RHS of simple parameterless given aliases - new ByNameClosures, // Expand arguments to by-name parameters to closures + new ElimByName, // Map by-name parameters to functions new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope + new ForwardDepChecks, // Check that there are no forward references to local vals new SpecializeApplyMethods, // Adds specialized methods to FunctionN - new RefChecks, // Various checks mostly related to abstract members and overriding new TryCatchPatterns, // Compile cases in try/catch new PatternMatcher) :: // Compile pattern matches List(new ElimOpaque, // Turn opaque into normal aliases new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only) new ExplicitOuter, // Add accessors to outer classes from nested ones. new ExplicitSelf, // Make references to non-trivial self types explicit as casts - new ElimByName, // Expand by-name parameter references new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatenations List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_` diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2e9c81332200..794119cd7a79 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -447,12 +447,6 @@ class Definitions { @tu lazy val andType: TypeSymbol = enterBinaryAlias(tpnme.AND, AndType(_, _)) @tu lazy val orType: TypeSymbol = enterBinaryAlias(tpnme.OR, OrType(_, _, soft = false)) - /** Marker method to indicate an argument to a call-by-name parameter. - * Created by byNameClosures and elimByName, eliminated by Erasure, - */ - @tu lazy val cbnArg: TermSymbol = enterPolyMethod(OpsPackageClass, nme.cbnArg, 1, - pt => MethodType(List(FunctionOf(Nil, pt.paramRefs(0))), pt.paramRefs(0))) - /** Method representing a throw */ @tu lazy val throwMethod: TermSymbol = enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(ThrowableType), NothingType)) @@ -1082,6 +1076,24 @@ class Definitions { } } + object ByNameFunction: + def apply(tp: Type)(using Context): Type = + defn.ContextFunction0.typeRef.appliedTo(tp :: Nil) + def unapply(tp: Type)(using Context): Option[Type] = tp match + case tp @ AppliedType(tycon, arg :: Nil) if defn.isByNameFunctionClass(tycon.typeSymbol) => + Some(arg) + case tp @ AnnotatedType(parent, _) => + unapply(parent) + case _ => + None + + final def isByNameFunctionClass(sym: Symbol): Boolean = + sym eq ContextFunction0 + + def isByNameFunction(tp: Type)(using Context): Boolean = tp match + case ByNameFunction(_) => true + case _ => false + final def isCompiletime_S(sym: Symbol)(using Context): Boolean = sym.name == tpnme.S && sym.owner == CompiletimeOpsIntModuleClass @@ -1295,10 +1307,12 @@ class Definitions { ).symbol.asClass @tu lazy val Function0_apply: Symbol = Function0.requiredMethod(nme.apply) + @tu lazy val ContextFunction0_apply: Symbol = ContextFunction0.requiredMethod(nme.apply) @tu lazy val Function0: Symbol = FunctionClass(0) @tu lazy val Function1: Symbol = FunctionClass(1) @tu lazy val Function2: Symbol = FunctionClass(2) + @tu lazy val ContextFunction0: Symbol = FunctionClass(0, isContextual = true) def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): TypeRef = FunctionClass(n, isContextual && !ctx.erasedTypes, isErased).typeRef @@ -1545,7 +1559,8 @@ class Definitions { new PerRun(Function2SpecializedReturnTypes.map(_.symbol)) def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(using Context): Boolean = - paramTypes.length <= 2 && cls.derivesFrom(FunctionClass(paramTypes.length)) + paramTypes.length <= 2 + && (cls.derivesFrom(FunctionClass(paramTypes.length)) || isByNameFunctionClass(cls)) && isSpecializableFunctionSAM(paramTypes, retType) /** If the Single Abstract Method of a Function class has this type, is it specializable? */ diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index b1268d7034a9..623286d837b3 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -207,6 +207,7 @@ object Phases { private var myRefChecksPhase: Phase = _ private var myPatmatPhase: Phase = _ private var myElimRepeatedPhase: Phase = _ + private var myElimByNamePhase: Phase = _ private var myExtensionMethodsPhase: Phase = _ private var myExplicitOuterPhase: Phase = _ private var myGettersPhase: Phase = _ @@ -229,6 +230,7 @@ object Phases { final def refchecksPhase: Phase = myRefChecksPhase final def patmatPhase: Phase = myPatmatPhase final def elimRepeatedPhase: Phase = myElimRepeatedPhase + final def elimByNamePhase: Phase = myElimByNamePhase final def extensionMethodsPhase: Phase = myExtensionMethodsPhase final def explicitOuterPhase: Phase = myExplicitOuterPhase final def gettersPhase: Phase = myGettersPhase @@ -253,6 +255,7 @@ object Phases { myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields]) myRefChecksPhase = phaseOfClass(classOf[RefChecks]) myElimRepeatedPhase = phaseOfClass(classOf[ElimRepeated]) + myElimByNamePhase = phaseOfClass(classOf[ElimByName]) myExtensionMethodsPhase = phaseOfClass(classOf[ExtensionMethods]) myErasurePhase = phaseOfClass(classOf[Erasure]) myElimErasedValueTypePhase = phaseOfClass(classOf[ElimErasedValueType]) @@ -427,6 +430,7 @@ object Phases { def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase def elimRepeatedPhase(using Context): Phase = ctx.base.elimRepeatedPhase + def elimByNamePhase(using Context): Phase = ctx.base.elimByNamePhase def extensionMethodsPhase(using Context): Phase = ctx.base.extensionMethodsPhase def explicitOuterPhase(using Context): Phase = ctx.base.explicitOuterPhase def gettersPhase(using Context): Phase = ctx.base.gettersPhase diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index a8915cb9d74b..f8c70176482c 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -445,7 +445,6 @@ object StdNames { val bytes: N = "bytes" val canEqual_ : N = "canEqual" val canEqualAny : N = "canEqualAny" - val cbnArg: N = "" val checkInitialized: N = "checkInitialized" val ClassManifestFactory: N = "ClassManifestFactory" val classOf: N = "classOf" diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 1d75c9ef0019..a2b61d0b7c24 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -4,7 +4,7 @@ package core import Types._, Contexts._, Symbols._, Flags._, Names._, NameOps._, Denotations._ import Decorators._ -import Phases.gettersPhase +import Phases.{gettersPhase, elimByNamePhase} import StdNames.nme import TypeOps.refineUsingParent import collection.mutable @@ -846,7 +846,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case _ => tp2.isAnyRef } compareJavaArray - case tp1: ExprType if ctx.phase.id > gettersPhase.id => + case tp1: ExprType if ctx.phaseId > gettersPhase.id => // getters might have converted T to => T, need to compensate. recur(tp1.widenExpr, tp2) case _ => @@ -1499,7 +1499,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling false } - def isSubArg(arg1: Type, arg2: Type): Boolean = arg2 match { + def isSubArg(arg1: Type, arg2: Type): Boolean = arg2 match case arg2: TypeBounds => val arg1norm = arg1 match { case arg1: TypeBounds => @@ -1510,15 +1510,23 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case _ => arg1 } arg2.contains(arg1norm) + case ExprType(arg2res) + if ctx.phaseId > elimByNamePhase.id && !ctx.erasedTypes + && defn.isByNameFunction(arg1.dealias) => + // ElimByName maps `=> T` to `()? => T`, but only in method parameters. It leaves + // embedded `=> T` arguments alone. This clause needs to compensate for that. + isSubArg(arg1.dealias.argInfos.head, arg2res) case _ => - arg1 match { + arg1 match case arg1: TypeBounds => compareCaptured(arg1, arg2) + case ExprType(arg1res) + if ctx.phaseId > elimByNamePhase.id && !ctx.erasedTypes + && defn.isByNameFunction(arg2.dealias) => + isSubArg(arg1res, arg2.argInfos.head) case _ => (v > 0 || isSubType(arg2, arg1)) && (v < 0 || isSubType(arg1, arg2)) - } - } isSubArg(args1.head, args2.head) } && recurArgs(args1.tail, args2.tail, tparams2.tail) diff --git a/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala b/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala deleted file mode 100644 index 51242e7f2dbe..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala +++ /dev/null @@ -1,40 +0,0 @@ -package dotty.tools.dotc -package transform - -import core._ -import Contexts._ -import Symbols._ -import Types._ -import Flags._ -import DenotTransformers.IdentityDenotTransformer -import core.StdNames.nme - -/** This phase translates arguments to call-by-name parameters, using the rules - * - * x ==> x if x is a => parameter - * e.apply() ==> (e) if e is pure - * e ==> (() => e) for all other arguments - * - * where - * - * : [T](() => T): T - * - * is a synthetic method defined in Definitions. Erasure will later strip the wrappers. - */ -class ByNameClosures extends TransformByNameApply with IdentityDenotTransformer { thisPhase => - import ast.tpd._ - - override def phaseName: String = ByNameClosures.name - - override def runsAfterGroupsOf: Set[String] = Set(ExpandSAMs.name) - // ExpanSAMs applied to partial functions creates methods that need - // to be fully defined before converting. Test case is pos/i9391.scala. - - override def mkByNameClosure(arg: Tree, argType: Type)(using Context): Tree = - val meth = newAnonFun(ctx.owner, MethodType(Nil, argType)) - Closure(meth, _ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase)).withSpan(arg.span) -} - -object ByNameClosures { - val name: String = "byNameClosures" -} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala index aeed5ac76d39..b3d5ab1da4b4 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala @@ -1,80 +1,160 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package transform import core._ -import DenotTransformers.InfoTransformer -import Symbols._ import Contexts._ +import Symbols._ import Types._ +import Flags._ +import SymDenotations.* +import DenotTransformers.InfoTransformer +import NameKinds.SuperArgName import core.StdNames.nme -import ast.Trees._ +import MegaPhase.* +import Decorators.* +import typer.RefChecks +import reporting.trace -/** This phase eliminates ExprTypes `=> T` as types of method parameter references, and replaces them b - * nullary function types. More precisely: +/** This phase implements the following transformations: * - * For the types of parameter symbols: + * 1. For types of method and class parameters: * - * => T ==> () => T + * => T becomes () ?=> T * - * For cbn parameter values + * 2. For references to cbn-parameters: * - * x ==> x() + * x becomes x.apply() * - * Note: This scheme to have inconsistent types between method types (whose formal types are still - * ExprTypes and parameter valdefs (which are now FunctionTypes) is not pretty. There are two - * other options which have been abandoned or not yet pursued. + * 3. For arguments to cbn parameters * - * Option 1: Transform => T to () => T also in method and function types. The problem with this is - * that is that it requires to look at every type, and this forces too much, causing - * Cyclic Reference errors. Abandoned for this reason. + * e becomes () ?=> e * - * Option 2: Merge ElimByName with erasure, or have it run immediately before. This has not been - * tried yet. + * An optimization is applied: If the argument `e` to a cbn parameter is already + * of type `() ?=> T` and is a pure expression, we avoid (2) and (3), i.e. we + * pass `e` directly instead of `() ?=> e.apply()`. + * + * Note that `() ?=> T` cannot be written in source since user-defined context functions + * must have at least one parameter. We use the type here as a convenient marker + * of something that will erase to Function0, and where we know that it came from + * a by-name parameter. + * + * Note also that the transformation applies only to types of parameters, not to other + * occurrences of ExprTypes. In particular, embedded occurrences in function types + * such as `(=> T) => U` are left as-is here (they are eliminated in erasure). + * Trying to convert these as well would mean traversing all the types, and that + * leads to cyclic reference errors in many cases. This can cause problems in that + * we might have sometimes a `() ?=> T` where a `=> T` is expected. To compensate, + * there is a new clause in TypeComparer#subArg that declares `() ?=> T` to be a + * subtype of `=> T` for arguments of type applications at any point after this phase + * and up to erasure. */ -class ElimByName extends TransformByNameApply with InfoTransformer { +class ElimByName extends MiniPhase, InfoTransformer: + thisPhase => + import ast.tpd._ override def phaseName: String = ElimByName.name - override def changesParents: Boolean = true // Only true for by-names + override def runsAfterGroupsOf: Set[String] = Set(ExpandSAMs.name, ElimRepeated.name, RefChecks.name) + // - ExpanSAMs applied to partial functions creates methods that need + // to be fully defined before converting. Test case is pos/i9391.scala. + // - ElimByName needs to run in a group after ElimRepeated since ElimRepeated + // works on simple arguments but not converted closures, and it sees the arguments + // after transformations by subsequent miniphases in the same group. + // - ElimByName should run in a group after RefChecks, since RefChecks does heavy + // comparisons of signatures, and ElimByName distorts these signatures by not + // replacing `=>` with `() ?=> T` everywhere. + + override def changesParents: Boolean = true + // Expr types in parent type arguments are changed to function types. + + /** If denotation had an ExprType before, it now gets a function type */ + private def exprBecomesFunction(symd: SymDenotation)(using Context): Boolean = + symd.is(Param) || symd.is(ParamAccessor, butNot = Method) + + def transformInfo(tp: Type, sym: Symbol)(using Context): Type = tp match { + case ExprType(rt) if exprBecomesFunction(sym) => + defn.ByNameFunction(rt) + case tp: MethodType => + def exprToFun(tp: Type) = tp match + case ExprType(rt) => defn.ByNameFunction(rt) + case tp => tp + tp.derivedLambdaType( + paramInfos = tp.paramInfos.mapConserve(exprToFun), + resType = transformInfo(tp.resType, sym)) + case tp: PolyType => + tp.derivedLambdaType(resType = transformInfo(tp.resType, sym)) + case _ => tp + } + + override def infoMayChange(sym: Symbol)(using Context): Boolean = + sym.is(Method) || exprBecomesFunction(sym) + + def byNameClosure(arg: Tree, argType: Type)(using Context): Tree = + val meth = newAnonFun(ctx.owner, MethodType(Nil, argType), coord = arg.span) + Closure(meth, + _ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase), + targetType = defn.ByNameFunction(argType) + ).withSpan(arg.span) - /** Map `tree` to `tree.apply()` is `ftree` was of ExprType and becomes now a function */ - private def applyIfFunction(tree: Tree, ftree: Tree)(using Context) = - if (isByNameRef(ftree)) { + private def isByNameRef(tree: Tree)(using Context): Boolean = + defn.isByNameFunction(tree.tpe.widen) + + /** Map `tree` to `tree.apply()` is `tree` is of type `() ?=> T` */ + private def applyIfFunction(tree: Tree)(using Context) = + if isByNameRef(tree) then val tree0 = transformFollowing(tree) - atPhase(next) { tree0.select(defn.Function0_apply).appliedToNone } - } + atPhase(next) { tree0.select(defn.ContextFunction0_apply).appliedToNone } else tree override def transformIdent(tree: Ident)(using Context): Tree = - applyIfFunction(tree, tree) + applyIfFunction(tree) override def transformSelect(tree: Select)(using Context): Tree = - applyIfFunction(tree, tree) + applyIfFunction(tree) override def transformTypeApply(tree: TypeApply)(using Context): Tree = tree match { case TypeApply(Select(_, nme.asInstanceOf_), arg :: Nil) => // tree might be of form e.asInstanceOf[x.type] where x becomes a function. // See pos/t296.scala - applyIfFunction(tree, arg) + applyIfFunction(tree) case _ => tree } + override def transformApply(tree: Apply)(using Context): Tree = + trace(s"transforming ${tree.show} at phase ${ctx.phase}", show = true) { + + def transformArg(arg: Tree, formal: Type): Tree = formal match + case defn.ByNameFunction(formalResult) => + def stripTyped(t: Tree): Tree = t match + case Typed(expr, _) => stripTyped(expr) + case _ => t + stripTyped(arg) match + case Apply(Select(qual, nme.apply), Nil) + if isByNameRef(qual) && (isPureExpr(qual) || qual.symbol.isAllOf(InlineParam)) => + qual + case _ => + if isByNameRef(arg) || arg.symbol.name.is(SuperArgName) + then arg + else + var argType = arg.tpe.widenIfUnstable + if argType.isBottomType then argType = formalResult + byNameClosure(arg, argType) + case _ => + arg + + val mt @ MethodType(_) = tree.fun.tpe.widen + val args1 = tree.args.zipWithConserve(mt.paramInfos)(transformArg) + cpy.Apply(tree)(tree.fun, args1) + } + override def transformValDef(tree: ValDef)(using Context): Tree = atPhase(next) { - if (exprBecomesFunction(tree.symbol)) + if exprBecomesFunction(tree.symbol) then cpy.ValDef(tree)(tpt = tree.tpt.withType(tree.symbol.info)) else tree } - def transformInfo(tp: Type, sym: Symbol)(using Context): Type = tp match { - case ExprType(rt) => defn.FunctionOf(Nil, rt) - case _ => tp - } - - override def infoMayChange(sym: Symbol)(using Context): Boolean = sym.isTerm && exprBecomesFunction(sym) -} - -object ElimByName { - val name: String = "elimByName" -} +object ElimByName: + val name: String = "elimByName" \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala index 0e62d1357fa5..aabbca4bc7bf 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala @@ -117,7 +117,7 @@ class ElimErasedValueType extends MiniPhase with InfoTransformer { thisPhase => // Do the test at the earliest phase where both symbols existed. val phaseId = sym1.originDenotation.validFor.firstPhaseId max sym2.originDenotation.validFor.firstPhaseId - atPhase(elimRepeatedPhase.next)(checkNoConflict(sym1, sym2, sym1.info)) + atPhase(elimByNamePhase.next)(checkNoConflict(sym1, sym2, sym1.info)) opc.next() } } diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 91214341f650..765d514de5c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -805,49 +805,46 @@ object Erasure { */ override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = val Apply(fun, args) = tree - if fun.symbol == defn.cbnArg then - typedUnadapted(args.head, pt) - else - val origFun = fun.asInstanceOf[tpd.Tree] - val origFunType = origFun.tpe.widen(using preErasureCtx) - val ownArgs = if origFunType.isErasedMethod then Nil else args - val fun1 = typedExpr(fun, AnyFunctionProto) - fun1.tpe.widen match - case mt: MethodType => - val (xmt, // A method type like `mt` but with bunched arguments expanded to individual ones - bunchArgs, // whether arguments are bunched - outers) = // the outer reference parameter(s) - if fun1.isInstanceOf[Apply] then - (mt, fun1.removeAttachment(BunchedArgs).isDefined, Nil) - else - val xmt = expandedMethodType(mt, origFun) - (xmt, xmt ne mt, outer.args(origFun)) - - val args0 = outers ::: ownArgs - val args1 = args0.zipWithConserve(xmt.paramInfos)(typedExpr) - .asInstanceOf[List[Tree]] - - def mkApply(finalFun: Tree, finalArgs: List[Tree]) = - val app = untpd.cpy.Apply(tree)(finalFun, finalArgs) - .withType(applyResultType(xmt, args1)) - if bunchArgs then app.withAttachment(BunchedArgs, ()) else app - - def app(fun1: Tree): Tree = fun1 match - case Block(stats, expr) => - cpy.Block(fun1)(stats, app(expr)) - case Apply(fun2, SeqLiteral(prevArgs, argTpt) :: _) if bunchArgs => - mkApply(fun2, JavaSeqLiteral(prevArgs ++ args1, argTpt) :: Nil) - case Apply(fun2, prevArgs) => - mkApply(fun2, prevArgs ++ args1) - case _ if bunchArgs => - mkApply(fun1, JavaSeqLiteral(args1, TypeTree(defn.ObjectType)) :: Nil) - case _ => - mkApply(fun1, args1) - - app(fun1) - case t => - if ownArgs.isEmpty then fun1 - else throw new MatchError(i"tree $tree has unexpected type of function $fun/$fun1: $t, was $origFunType, args = $ownArgs") + val origFun = fun.asInstanceOf[tpd.Tree] + val origFunType = origFun.tpe.widen(using preErasureCtx) + val ownArgs = if origFunType.isErasedMethod then Nil else args + val fun1 = typedExpr(fun, AnyFunctionProto) + fun1.tpe.widen match + case mt: MethodType => + val (xmt, // A method type like `mt` but with bunched arguments expanded to individual ones + bunchArgs, // whether arguments are bunched + outers) = // the outer reference parameter(s) + if fun1.isInstanceOf[Apply] then + (mt, fun1.removeAttachment(BunchedArgs).isDefined, Nil) + else + val xmt = expandedMethodType(mt, origFun) + (xmt, xmt ne mt, outer.args(origFun)) + + val args0 = outers ::: ownArgs + val args1 = args0.zipWithConserve(xmt.paramInfos)(typedExpr) + .asInstanceOf[List[Tree]] + + def mkApply(finalFun: Tree, finalArgs: List[Tree]) = + val app = untpd.cpy.Apply(tree)(finalFun, finalArgs) + .withType(applyResultType(xmt, args1)) + if bunchArgs then app.withAttachment(BunchedArgs, ()) else app + + def app(fun1: Tree): Tree = fun1 match + case Block(stats, expr) => + cpy.Block(fun1)(stats, app(expr)) + case Apply(fun2, SeqLiteral(prevArgs, argTpt) :: _) if bunchArgs => + mkApply(fun2, JavaSeqLiteral(prevArgs ++ args1, argTpt) :: Nil) + case Apply(fun2, prevArgs) => + mkApply(fun2, prevArgs ++ args1) + case _ if bunchArgs => + mkApply(fun1, JavaSeqLiteral(args1, TypeTree(defn.ObjectType)) :: Nil) + case _ => + mkApply(fun1, args1) + + app(fun1) + case t => + if ownArgs.isEmpty then fun1 + else throw new MatchError(i"tree $tree has unexpected type of function $fun/$fun1: $t, was $origFunType, args = $ownArgs") end typedApply // The following four methods take as the proto-type the erasure of the pre-existing type, diff --git a/compiler/src/dotty/tools/dotc/transform/ForwardDepChecks.scala b/compiler/src/dotty/tools/dotc/transform/ForwardDepChecks.scala new file mode 100644 index 000000000000..ac3b271ece76 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ForwardDepChecks.scala @@ -0,0 +1,111 @@ +package dotty.tools +package dotc +package transform + +import core.* +import Symbols.*, Types.*, Contexts.*, Flags.*, Decorators.*, reporting.* +import util.Spans.Span +import util.Store +import collection.immutable +import ast.tpd +import MegaPhase.MiniPhase + +object ForwardDepChecks: + + import tpd.* + + val name: String = "forwardDepChecks" + + type LevelAndIndex = immutable.Map[Symbol, (LevelInfo, Int)] + + class OptLevelInfo { + def levelAndIndex: LevelAndIndex = Map() + def enterReference(sym: Symbol, span: Span): Unit = () + } + + /** A class to help in forward reference checking */ + class LevelInfo(outerLevelAndIndex: LevelAndIndex, stats: List[Tree])(using Context) + extends OptLevelInfo { + override val levelAndIndex: LevelAndIndex = + stats.foldLeft(outerLevelAndIndex, 0) {(mi, stat) => + val (m, idx) = mi + val m1 = stat match { + case stat: MemberDef => m.updated(stat.symbol, (this, idx)) + case _ => m + } + (m1, idx + 1) + }._1 + var maxIndex: Int = Int.MinValue + var refSpan: Span = _ + var refSym: Symbol = _ + + override def enterReference(sym: Symbol, span: Span): Unit = + if (sym.exists && sym.owner.isTerm) + levelAndIndex.get(sym) match { + case Some((level, idx)) if (level.maxIndex < idx) => + level.maxIndex = idx + level.refSpan = span + level.refSym = sym + case _ => + } + } + + val NoLevelInfo: OptLevelInfo = new OptLevelInfo() + +class ForwardDepChecks extends MiniPhase: + import ForwardDepChecks.* + import tpd.* + + override def phaseName: String = ForwardDepChecks.name + + override def runsAfter: Set[String] = Set(ElimByName.name) + + private var LevelInfo: Store.Location[OptLevelInfo] = _ + private def currentLevel(using Context): OptLevelInfo = ctx.store(LevelInfo) + + override def initContext(ctx: FreshContext): Unit = + LevelInfo = ctx.addLocation(NoLevelInfo) + + override def prepareForStats(trees: List[Tree])(using Context): Context = + if (ctx.owner.isTerm) + ctx.fresh.updateStore(LevelInfo, new LevelInfo(currentLevel.levelAndIndex, trees)) + else ctx + + override def transformValDef(tree: ValDef)(using Context): ValDef = + val sym = tree.symbol + if sym.exists && sym.owner.isTerm && !sym.is(Lazy) then + currentLevel.levelAndIndex.get(sym) match + case Some((level, symIdx)) if symIdx <= level.maxIndex => + report.error(ForwardReferenceExtendsOverDefinition(sym, level.refSym), + ctx.source.atSpan(level.refSpan)) + case _ => + tree + + override def transformIdent(tree: Ident)(using Context): Ident = { + currentLevel.enterReference(tree.symbol, tree.span) + tree + } + + override def transformApply(tree: Apply)(using Context): Apply = { + if (isSelfConstrCall(tree)) { + assert(currentLevel.isInstanceOf[LevelInfo], s"${ctx.owner}/" + i"$tree") + val level = currentLevel.asInstanceOf[LevelInfo] + if (level.maxIndex > 0) { + // An implementation restriction to avoid VerifyErrors and lazyvals mishaps; see SI-4717 + report.debuglog("refsym = " + level.refSym) + report.error("forward reference not allowed from self constructor invocation", + ctx.source.atSpan(level.refSpan)) + } + } + tree + } + + override def transformNew(tree: New)(using Context): New = { + currentLevel.enterReference(tree.tpe.typeSymbol, tree.span) + tree.tpe.dealias.foreachPart { + case TermRef(_, s: Symbol) => currentLevel.enterReference(s, tree.span) + case _ => + } + tree + } +end ForwardDepChecks \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala index e1bcc7e6a9b4..c87fd037f2da 100644 --- a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala +++ b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala @@ -45,10 +45,8 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase def phaseName: String = HoistSuperArgs.name - override def runsAfter: Set[String] = Set(ByNameClosures.name) + override def runsAfter: Set[String] = Set(ElimByName.name) // By name closures need to be introduced first in order to be hoisted out here. - // There's an interaction with by name closures in that the marker - // application should not be hoisted, but be left at the point of call. /** Defines methods for hoisting complex supercall arguments out of * parent super calls and constructor definitions. @@ -126,8 +124,6 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase // begin hoistSuperArg arg match { - case Apply(fn, arg1 :: Nil) if fn.symbol == defn.cbnArg => - cpy.Apply(arg)(fn, hoistSuperArg(arg1, cdef) :: Nil) case _ if arg.existsSubTree(needsHoist) => val superMeth = newSuperArgMethod(arg.tpe) val superArgDef = DefDef(superMeth, prefss => { diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala index 91555dc2b995..78b6edcd213b 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala @@ -76,24 +76,27 @@ class SpecializeFunctions extends MiniPhase { argTypes, retType ) - - if (!isSpecializable || argTypes.exists(_.isInstanceOf[ExprType])) return tree - - val specializedApply = nme.apply.specializedFunction(retType, argTypes) - val newSel = fun match { - case Select(qual, _) => - qual.select(specializedApply) - case _ => - (fun.tpe: @unchecked) match { - case TermRef(prefix: ThisType, name) => - tpd.This(prefix.cls).select(specializedApply) - case TermRef(prefix: NamedType, name) => - tpd.ref(prefix).select(specializedApply) - } - } - - newSel.appliedToTermArgs(args) - + if isSpecializable then + val specializedApply = nme.apply.specializedFunction(retType, argTypes) + val newSel = fun match + case Select(qual, _) => + val qual1 = qual.tpe.widen match + case defn.ByNameFunction(res) => + // Need to cast to regular function, since specialied apply methods + // are not members of ContextFunction0. The cast will be eliminated in + // erasure. + qual.cast(defn.FunctionOf(Nil, res)) + case _ => + qual + qual1.select(specializedApply) + case _ => + (fun.tpe: @unchecked) match + case TermRef(prefix: ThisType, name) => + tpd.This(prefix.cls).select(specializedApply) + case TermRef(prefix: NamedType, name) => + tpd.ref(prefix).select(specializedApply) + newSel.appliedToTermArgs(args) + else tree case _ => tree } diff --git a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala b/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala deleted file mode 100644 index 9eb9b3f0ad5a..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala +++ /dev/null @@ -1,67 +0,0 @@ -package dotty.tools -package dotc -package transform - -import MegaPhase._ -import core._ -import Symbols._ -import SymDenotations._ -import Contexts._ -import Types._ -import Flags._ -import Decorators._ -import DenotTransformers._ -import StdNames.nme -import NameKinds.SuperArgName -import ast.Trees._ -import reporting.trace - -/** Abstract base class of ByNameClosures and ElimByName, factoring out the - * common functionality to transform arguments of by-name parameters. - */ -abstract class TransformByNameApply extends MiniPhase { thisPhase: DenotTransformer => - import ast.tpd._ - - /** The info of the tree's symbol before it is potentially transformed in this phase */ - private def originalDenotation(tree: Tree)(using Context) = - atPhase(thisPhase)(tree.symbol.denot) - - /** If denotation had an ExprType before, it now gets a function type */ - protected def exprBecomesFunction(symd: SymDenotation)(using Context): Boolean = - symd.is(Param) || symd.is(ParamAccessor, butNot = Method) - - protected def isByNameRef(tree: Tree)(using Context): Boolean = { - val origDenot = originalDenotation(tree) - origDenot.info.isInstanceOf[ExprType] && exprBecomesFunction(origDenot) - } - - def mkByNameClosure(arg: Tree, argType: Type)(using Context): Tree = unsupported(i"mkClosure($arg)") - - override def transformApply(tree: Apply)(using Context): Tree = - trace(s"transforming ${tree.show} at phase ${ctx.phase}", show = true) { - - def transformArg(arg: Tree, formal: Type): Tree = formal.dealias match { - case formalExpr: ExprType => - var argType = arg.tpe.widenIfUnstable - if (argType.isBottomType) argType = formal.widenExpr - def wrap(arg: Tree) = - ref(defn.cbnArg).appliedToType(argType).appliedTo(arg).withSpan(arg.span) - def unTyped(t: Tree): Tree = t match { case Typed(expr, _) => unTyped(expr) case _ => t } - unTyped(arg) match { - case Apply(Select(qual, nme.apply), Nil) - if qual.tpe.derivesFrom(defn.Function0) && (isPureExpr(qual) || qual.symbol.isAllOf(Inline | Param)) => - wrap(qual) - case _ => - if isByNameRef(arg) || arg.symbol == defn.cbnArg || arg.symbol.name.is(SuperArgName) - then arg - else wrap(mkByNameClosure(arg, argType)) - } - case _ => - arg - } - - val mt @ MethodType(_) = tree.fun.tpe.widen - val args1 = tree.args.zipWithConserve(mt.paramInfos)(transformArg) - cpy.Apply(tree)(tree.fun, args1) - } -} diff --git a/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala b/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala new file mode 100644 index 000000000000..5a48da34d87f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala @@ -0,0 +1,210 @@ +package dotty.tools +package dotc +package transform + +import core.* +import Symbols.*, Types.*, Contexts.*, Flags.*, SymUtils.*, Decorators.*, reporting.* +import util.SrcPos +import config.{ScalaVersion, NoScalaVersion, Feature, ScalaRelease} +import MegaPhase.MiniPhase +import scala.util.{Failure, Success} +import ast.tpd + +class CrossVersionChecks extends MiniPhase: + import tpd.* + + def phaseName = "crossVersionChecks" + + 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) + checkExperimental(sym, pos) + checkSinceAnnot(sym, pos) + + val xMigrationValue = ctx.settings.Xmigration.value + if xMigrationValue != NoScalaVersion then + checkMigration(sym, pos, xMigrationValue) + + + /** 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 checkExperimental(sym: Symbol, pos: SrcPos)(using Context): Unit = + if sym.isExperimental && !ctx.owner.isInExperimentalScope then + Feature.checkExperimentalDef(sym, 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) + + private def checkSinceAnnot(sym: Symbol, pos: SrcPos)(using Context): Unit = + for + annot <- sym.getAnnotation(defn.SinceAnnot) + releaseName <- annot.argumentConstantString(0) + do + ScalaRelease.parse(releaseName) match + case Some(release) if release > ctx.scalaRelease => + report.error( + i"$sym was added in Scala release ${releaseName.show}, therefore it cannot be used in the code targeting Scala ${ctx.scalaRelease.show}", + pos) + case None => + report.error(i"$sym has an unparsable release name: '${releaseName}'", annot.tree.srcPos) + case _ => + + private def checkSinceAnnotInSignature(sym: Symbol, pos: SrcPos)(using Context) = + new TypeTraverser: + def traverse(tp: Type) = + if tp.typeSymbol.hasAnnotation(defn.SinceAnnot) then + checkSinceAnnot(tp.typeSymbol, pos) + else + traverseChildren(tp) + .traverse(sym.info) + + /** 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), 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) + checkExperimentalSignature(tree.symbol, tree) + checkSinceAnnot(tree.symbol, tree.srcPos) + checkSinceAnnotInSignature(tree.symbol, tree) + tree + + override def transformDefDef(tree: DefDef)(using Context): DefDef = + checkDeprecatedOvers(tree) + checkExperimentalAnnots(tree.symbol) + checkExperimentalSignature(tree.symbol, tree) + checkSinceAnnotInSignature(tree.symbol, tree) + tree + + override def transformTemplate(tree: Template)(using Context): Tree = + val cls = ctx.owner.asClass + checkExperimentalInheritance(cls) + checkExperimentalAnnots(cls) + tree + + override def transformIdent(tree: Ident)(using Context): Ident = { + checkUndesiredProperties(tree.symbol, tree.srcPos) + tree + } + + override def transformSelect(tree: Select)(using Context): Select = { + checkUndesiredProperties(tree.symbol, tree.srcPos) + tree + } + + override def transformNew(tree: New)(using Context): New = { + checkUndesiredProperties(tree.tpe.typeSymbol, tree.srcPos) + tree + } + + override def transformTypeTree(tree: TypeTree)(using Context): TypeTree = { + val tpe = tree.tpe + tpe.foreachPart { + case TypeRef(_, sym: Symbol) => + checkDeprecated(sym, tree.srcPos) + checkExperimental(sym, tree.srcPos) + checkSinceAnnot(sym, tree.srcPos) + case TermRef(_, sym: Symbol) => + checkDeprecated(sym, tree.srcPos) + checkExperimental(sym, tree.srcPos) + checkSinceAnnot(sym, tree.srcPos) + case _ => + } + tree + } + + override def transformTypeDef(tree: TypeDef)(using Context): TypeDef = { + checkExperimentalAnnots(tree.symbol) + checkSinceAnnot(tree.symbol, tree.srcPos) + tree + } + +end CrossVersionChecks \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index c7ba668b7e79..9ababe3e5f07 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -9,13 +9,11 @@ import StdNames._, Denotations._, SymUtils._, Phases._, SymDenotations._ import NameKinds.DefaultGetterName import Annotations._ import util.Spans._ -import util.{Store, SrcPos} +import util.SrcPos import scala.collection.{ mutable, immutable } import ast._ import MegaPhase._ import config.Printers.{checks, noPrinter} -import scala.util.{Try, Failure, Success} -import config.{ScalaVersion, NoScalaVersion, ScalaRelease} import Decorators._ import OverridingPairs.isOverridingPair import typer.ErrorReporting._ @@ -25,7 +23,6 @@ import reporting._ import scala.util.matching.Regex._ import Constants.Constant import NullOpsDecorator._ -import dotty.tools.dotc.config.Feature object RefChecks { import tpd._ @@ -903,128 +900,6 @@ object RefChecks { } } - // 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) - checkExperimental(sym, pos) - checkSinceAnnot(sym, pos) - - val xMigrationValue = ctx.settings.Xmigration.value - if xMigrationValue != NoScalaVersion then - checkMigration(sym, pos, xMigrationValue) - - - /** 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 checkExperimental(sym: Symbol, pos: SrcPos)(using Context): Unit = - if sym.isExperimental && !ctx.owner.isInExperimentalScope then - Feature.checkExperimentalDef(sym, 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) - - private def checkSinceAnnot(sym: Symbol, pos: SrcPos)(using Context): Unit = - for - annot <- sym.getAnnotation(defn.SinceAnnot) - releaseName <- annot.argumentConstantString(0) - do - ScalaRelease.parse(releaseName) match - case Some(release) if release > ctx.scalaRelease => - report.error( - i"$sym was added in Scala release ${releaseName.show}, therefore it cannot be used in the code targeting Scala ${ctx.scalaRelease.show}", - pos) - case None => - report.error(i"$sym has an unparsable release name: '${releaseName}'", annot.tree.srcPos) - case _ => - - private def checkSinceAnnotInSignature(sym: Symbol, pos: SrcPos)(using Context) = - new TypeTraverser: - def traverse(tp: Type) = - if tp.typeSymbol.hasAnnotation(defn.SinceAnnot) then - checkSinceAnnot(tp.typeSymbol, pos) - else - traverseChildren(tp) - .traverse(sym.info) - - /** 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), 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 we do not "override" anything with a private method * or something that becomes a private method. According to the Scala * modeling this is non-sensical since private members don't override. @@ -1037,7 +912,7 @@ object RefChecks { */ def checkNoPrivateOverrides(tree: Tree)(using Context): Unit = val sym = tree.symbol - if sym.owner.isClass + if sym.maybeOwner.isClass && sym.is(Private) && (sym.isOneOf(MethodOrLazyOrMutable) || !sym.is(Local)) // in these cases we'll produce a getter later && !sym.isConstructor @@ -1096,42 +971,6 @@ object RefChecks { end checkUnaryMethods - type LevelAndIndex = immutable.Map[Symbol, (LevelInfo, Int)] - - class OptLevelInfo { - def levelAndIndex: LevelAndIndex = Map() - def enterReference(sym: Symbol, span: Span): Unit = () - } - - /** A class to help in forward reference checking */ - class LevelInfo(outerLevelAndIndex: LevelAndIndex, stats: List[Tree])(using Context) - extends OptLevelInfo { - override val levelAndIndex: LevelAndIndex = - stats.foldLeft(outerLevelAndIndex, 0) {(mi, stat) => - val (m, idx) = mi - val m1 = stat match { - case stat: MemberDef => m.updated(stat.symbol, (this, idx)) - case _ => m - } - (m1, idx + 1) - }._1 - var maxIndex: Int = Int.MinValue - var refSpan: Span = _ - var refSym: Symbol = _ - - override def enterReference(sym: Symbol, span: Span): Unit = - if (sym.exists && sym.owner.isTerm) - levelAndIndex.get(sym) match { - case Some((level, idx)) if (level.maxIndex < idx) => - level.maxIndex = idx - level.refSpan = span - level.refSym = sym - case _ => - } - } - - val NoLevelInfo: RefChecks.OptLevelInfo = new OptLevelInfo() - /** Verify that references in the user-defined `@implicitNotFound` message are valid. * (i.e. they refer to a type variable that really occurs in the signature of the annotated symbol.) */ @@ -1213,15 +1052,6 @@ object RefChecks { end checkImplicitNotFoundAnnotation - - /** 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 } import RefChecks._ @@ -1261,50 +1091,24 @@ class RefChecks extends MiniPhase { thisPhase => override def phaseName: String = RefChecks.name - // Needs to run after ElimRepeated for override checks involving varargs methods override def runsAfter: Set[String] = Set(ElimRepeated.name) - - private var LevelInfo: Store.Location[OptLevelInfo] = _ - private def currentLevel(using Context): OptLevelInfo = ctx.store(LevelInfo) - - override def initContext(ctx: FreshContext): Unit = - LevelInfo = ctx.addLocation(NoLevelInfo) - - override def prepareForStats(trees: List[Tree])(using Context): Context = - if (ctx.owner.isTerm) - ctx.fresh.updateStore(LevelInfo, new LevelInfo(currentLevel.levelAndIndex, trees)) - else ctx + // Needs to run after ElimRepeated for override checks involving varargs methods override def transformValDef(tree: ValDef)(using Context): ValDef = { - checkNoPrivateOverrides(tree) - checkDeprecatedOvers(tree) - checkExperimentalAnnots(tree.symbol) - checkExperimentalSignature(tree.symbol, tree) - checkSinceAnnot(tree.symbol, tree.srcPos) - checkSinceAnnotInSignature(tree.symbol, tree) - val sym = tree.symbol - if (sym.exists && sym.owner.isTerm) { - tree.rhs match { - case Ident(nme.WILDCARD) => report.error(UnboundPlaceholderParameter(), sym.srcPos) - case _ => - } - if (!sym.is(Lazy)) - currentLevel.levelAndIndex.get(sym) match { - case Some((level, symIdx)) if symIdx <= level.maxIndex => - report.error(ForwardReferenceExtendsOverDefinition(sym, level.refSym), - ctx.source.atSpan(level.refSpan)) + if tree.symbol.exists then + checkNoPrivateOverrides(tree) + val sym = tree.symbol + if (sym.exists && sym.owner.isTerm) { + tree.rhs match { + case Ident(nme.WILDCARD) => report.error(UnboundPlaceholderParameter(), sym.srcPos) case _ => } - } + } tree } override def transformDefDef(tree: DefDef)(using Context): DefDef = { checkNoPrivateOverrides(tree) - checkDeprecatedOvers(tree) - checkExperimentalAnnots(tree.symbol) - checkExperimentalSignature(tree.symbol, tree) - checkSinceAnnotInSignature(tree.symbol, tree) checkImplicitNotFoundAnnotation.defDef(tree.symbol.denot) checkUnaryMethods(tree.symbol) tree @@ -1318,8 +1122,6 @@ class RefChecks extends MiniPhase { thisPhase => checkCompanionNameClashes(cls) checkAllOverrides(cls) checkImplicitNotFoundAnnotation.template(cls.classDenot) - checkExperimentalInheritance(cls) - checkExperimentalAnnots(cls) tree } catch { @@ -1327,65 +1129,6 @@ class RefChecks extends MiniPhase { thisPhase => report.error(ex, tree.srcPos) tree } - - override def transformIdent(tree: Ident)(using Context): Ident = { - checkUndesiredProperties(tree.symbol, tree.srcPos) - currentLevel.enterReference(tree.symbol, tree.span) - tree - } - - override def transformSelect(tree: Select)(using Context): Select = { - checkUndesiredProperties(tree.symbol, tree.srcPos) - tree - } - - override def transformApply(tree: Apply)(using Context): Apply = { - if (isSelfConstrCall(tree)) { - assert(currentLevel.isInstanceOf[LevelInfo], s"${ctx.owner}/" + i"$tree") - val level = currentLevel.asInstanceOf[LevelInfo] - if (level.maxIndex > 0) { - // An implementation restriction to avoid VerifyErrors and lazyvals mishaps; see SI-4717 - report.debuglog("refsym = " + level.refSym) - report.error("forward reference not allowed from self constructor invocation", - ctx.source.atSpan(level.refSpan)) - } - } - tree - } - - override def transformNew(tree: New)(using Context): New = { - val tpe = tree.tpe - val sym = tpe.typeSymbol - checkUndesiredProperties(sym, tree.srcPos) - currentLevel.enterReference(sym, tree.span) - tpe.dealias.foreachPart { - case TermRef(_, s: Symbol) => currentLevel.enterReference(s, tree.span) - case _ => - } - tree - } - - override def transformTypeTree(tree: TypeTree)(using Context): TypeTree = { - val tpe = tree.tpe - tpe.foreachPart { - case TypeRef(_, sym: Symbol) => - checkDeprecated(sym, tree.srcPos) - checkExperimental(sym, tree.srcPos) - checkSinceAnnot(sym, tree.srcPos) - case TermRef(_, sym: Symbol) => - checkDeprecated(sym, tree.srcPos) - checkExperimental(sym, tree.srcPos) - checkSinceAnnot(sym, tree.srcPos) - case _ => - } - tree - } - - override def transformTypeDef(tree: TypeDef)(using Context): TypeDef = { - checkExperimentalAnnots(tree.symbol) - checkSinceAnnot(tree.symbol, tree.srcPos) - tree - } } /* todo: rewrite and re-enable diff --git a/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala b/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala index 3d4b952644bd..c1fab5c13f42 100644 --- a/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala +++ b/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala @@ -14,7 +14,7 @@ import Decorators._ import Symbols.Symbol import Constants.Constant import Types._ -import transform.ElimRepeated +import transform.ProtectedAccessors class InitPlugin extends StandardPlugin { import tpd._ @@ -31,7 +31,7 @@ class InitChecker extends PluginPhase { val phaseName = "symbolTreeChecker" override val runsAfter = Set(SetDefTree.name) - override val runsBefore = Set(ElimRepeated.name) + override val runsBefore = Set(ProtectedAccessors.name) private def checkDef(tree: Tree)(implicit ctx: Context): Tree = { if (tree.symbol.defTree.isEmpty)