diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 3386dc7d7a6c..36f2d593de1c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -674,7 +674,8 @@ object desugar { val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) => val app = Apply(nu, vparams.map(refOfDef)) vparams match { - case vparam :: _ if vparam.mods.is(Given) => app.setApplyKind(ApplyKind.Using) + case vparam :: _ if vparam.mods.is(Given) || vparam.name.is(ContextBoundParamName) => + app.setApplyKind(ApplyKind.Using) case _ => app } } diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 98873dba85c7..7268ec720ce2 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -99,7 +99,7 @@ object DesugarEnums { val clazzOf = TypeApply(ref(defn.Predef_classOf.termRef), tpt :: Nil) val ctag = Apply(TypeApply(ref(defn.ClassTagModule_apply.termRef), tpt :: Nil), clazzOf :: Nil) val apply = Select(ref(defn.ArrayModule.termRef), nme.apply) - Apply(Apply(TypeApply(apply, tpt :: Nil), values), ctag :: Nil) + Apply(Apply(TypeApply(apply, tpt :: Nil), values), ctag :: Nil).setApplyKind(ApplyKind.Using) /** The following lists of definitions for an enum type E and known value cases e_0, ..., e_n: * diff --git a/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala b/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala index d4afc599896c..4dd9d065395b 100644 --- a/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala @@ -6,10 +6,16 @@ import SourceVersion.* import Feature.* import core.Contexts.Context -class MigrationVersion(val warnFrom: SourceVersion, val errorFrom: SourceVersion): - assert(warnFrom.ordinal <= errorFrom.ordinal) +class MigrationVersion( + val warnFrom: SourceVersion, + val errorFrom: SourceVersion): + require(warnFrom.ordinal <= errorFrom.ordinal) + def needsPatch(using Context): Boolean = - sourceVersion.isMigrating && sourceVersion.isAtLeast(errorFrom) + sourceVersion.isMigrating && sourceVersion.isAtLeast(warnFrom) + + def patchFrom: SourceVersion = + warnFrom.prevMigrating object MigrationVersion: @@ -27,6 +33,8 @@ object MigrationVersion: val AscriptionAfterPattern = MigrationVersion(`3.3`, future) + val ExplicitContextBoundArgument = MigrationVersion(`3.4`, `3.5`) + val AlphanumericInfix = MigrationVersion(`3.4`, future) val RemoveThisQualifier = MigrationVersion(`3.4`, future) val UninitializedVars = MigrationVersion(`3.4`, future) diff --git a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala index 33b946ed173f..7a464d331930 100644 --- a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala @@ -19,6 +19,9 @@ enum SourceVersion: def stable: SourceVersion = if isMigrating then SourceVersion.values(ordinal + 1) else this + def prevMigrating: SourceVersion = + if isMigrating then this else SourceVersion.values(ordinal - 1).prevMigrating + def isAtLeast(v: SourceVersion) = stable.ordinal >= v.ordinal def isAtMost(v: SourceVersion) = stable.ordinal <= v.ordinal diff --git a/compiler/src/dotty/tools/dotc/typer/Migrations.scala b/compiler/src/dotty/tools/dotc/typer/Migrations.scala new file mode 100644 index 000000000000..84db91f9dee9 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -0,0 +1,118 @@ +package dotty.tools +package dotc +package typer + +import core.* +import ast.* +import Contexts.* +import Types.* +import Flags.* +import Names.* +import StdNames.* +import Symbols.* +import Trees.* +import ProtoTypes.* +import Decorators.* +import config.MigrationVersion as mv +import config.Feature.{sourceVersion, migrateTo3} +import config.SourceVersion.* +import reporting.* +import NameKinds.ContextBoundParamName +import rewrites.Rewrites.patch +import util.Spans.Span +import rewrites.Rewrites + +/** A utility trait containing source-dependent deprecation messages + * and migrations. + */ +trait Migrations: + this: Typer => + + import tpd.* + + /** Run `migration`, asserting we are in the proper Typer (not a ReTyper) */ + inline def migrate[T](inline migration: T): T = + assert(!this.isInstanceOf[ReTyper]) + migration + + /** Run `migration`, provided we are in the proper Typer (not a ReTyper) */ + inline def migrate(inline migration: Unit): Unit = + if !this.isInstanceOf[ReTyper] then migration + + /** Flag & migrate `?` used as a higher-kinded type parameter + * Warning in 3.0-migration, error from 3.0 + */ + def kindProjectorQMark(tree: untpd.TypeDef, sym: Symbol)(using Context): Unit = + if tree.name eq tpnme.? then + val addendum = if sym.owner.is(TypeParam) + then ", use `_` to denote a higher-kinded type parameter" + else "" + val namePos = tree.sourcePos.withSpan(tree.nameSpan) + report.errorOrMigrationWarning( + em"`?` is not a valid type name$addendum", namePos, mv.Scala2to3) + + def typedAsFunction(tree: untpd.PostfixOp, pt: Type)(using Context): Tree = { + val untpd.PostfixOp(qual, Ident(nme.WILDCARD)) = tree: @unchecked + val pt1 = if (defn.isFunctionNType(pt)) pt else AnyFunctionProto + val nestedCtx = ctx.fresh.setNewTyperState() + val res = typed(qual, pt1)(using nestedCtx) + res match { + case closure(_, _, _) => + case _ => + val recovered = typed(qual)(using ctx.fresh.setExploreTyperState()) + val msg = OnlyFunctionsCanBeFollowedByUnderscore(recovered.tpe.widen, tree) + report.errorOrMigrationWarning(msg, tree.srcPos, mv.Scala2to3) + if mv.Scala2to3.needsPatch then + // Under -rewrite, patch `x _` to `(() => x)` + msg.actions + .headOption + .foreach(Rewrites.applyAction) + return typed(untpd.Function(Nil, qual), pt) + } + nestedCtx.typerState.commit() + + lazy val (prefix, suffix) = res match { + case Block(mdef @ DefDef(_, vparams :: Nil, _, _) :: Nil, _: Closure) => + val arity = vparams.length + if (arity > 0) ("", "") else ("(() => ", "())") + case _ => + ("(() => ", ")") + } + val mversion = mv.FunctionUnderscore + def remedy = + if ((prefix ++ suffix).isEmpty) "simply leave out the trailing ` _`" + else s"use `$prefix$suffix` instead" + def rewrite = Message.rewriteNotice("This construct", mversion.patchFrom) + report.errorOrMigrationWarning( + em"""The syntax ` _` is no longer supported; + |you can $remedy$rewrite""", + tree.srcPos, mversion) + if mversion.needsPatch then + patch(Span(tree.span.start), prefix) + patch(Span(qual.span.end, tree.span.end), suffix) + + res + } + + /** Flag & migrate explicit normal arguments to parameters coming from context bounds + * Warning in 3.4, error in 3.5, rewrite in 3.5-migration. + */ + def contextBoundParams(tree: Tree, tp: Type, pt: FunProto)(using Context): Unit = + val mversion = mv.ExplicitContextBoundArgument + def isContextBoundParams = tp.stripPoly match + case MethodType(ContextBoundParamName(_) :: _) => true + case _ => false + if sourceVersion.isAtLeast(`3.4`) + && isContextBoundParams + && pt.applyKind != ApplyKind.Using + then + def rewriteMsg = Message.rewriteNotice("This code", mversion.patchFrom) + report.errorOrMigrationWarning( + em"""Context bounds will map to context parameters. + |A `using` clause is needed to pass explicit arguments to them.$rewriteMsg""", + tree.srcPos, mversion) + if mversion.needsPatch then + patch(Span(pt.args.head.span.start), "using ") + end contextBoundParams + +end Migrations diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2f03c79754e8..1303b64cbd12 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -43,7 +43,7 @@ import config.Printers.{gadts, typr} import config.Feature import config.Feature.{sourceVersion, migrateTo3} import config.SourceVersion.* -import rewrites.Rewrites.patch +import rewrites.Rewrites, Rewrites.patch import staging.StagingLevel import reporting.* import Nullables.* @@ -53,7 +53,6 @@ import config.Config import config.MigrationVersion import scala.annotation.constructorOnly -import dotty.tools.dotc.rewrites.Rewrites object Typer { @@ -127,7 +126,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer with Dynamic with Checking with QuotesAndSplices - with Deriving { + with Deriving + with Migrations { import Typer.* import tpd.{cpy => _, _} @@ -2978,48 +2978,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else tree1 } - def typedAsFunction(tree: untpd.PostfixOp, pt: Type)(using Context): Tree = { - val untpd.PostfixOp(qual, Ident(nme.WILDCARD)) = tree: @unchecked - val pt1 = if (defn.isFunctionNType(pt)) pt else AnyFunctionProto - val nestedCtx = ctx.fresh.setNewTyperState() - val res = typed(qual, pt1)(using nestedCtx) - res match { - case closure(_, _, _) => - case _ => - val recovered = typed(qual)(using ctx.fresh.setExploreTyperState()) - val msg = OnlyFunctionsCanBeFollowedByUnderscore(recovered.tpe.widen, tree) - report.errorOrMigrationWarning(msg, tree.srcPos, MigrationVersion.Scala2to3) - if MigrationVersion.Scala2to3.needsPatch then - // Under -rewrite, patch `x _` to `(() => x)` - msg.actions - .headOption - .foreach(Rewrites.applyAction) - return typed(untpd.Function(Nil, qual), pt) - } - nestedCtx.typerState.commit() - - lazy val (prefix, suffix) = res match { - case Block(mdef @ DefDef(_, vparams :: Nil, _, _) :: Nil, _: Closure) => - val arity = vparams.length - if (arity > 0) ("", "") else ("(() => ", "())") - case _ => - ("(() => ", ")") - } - def remedy = - if ((prefix ++ suffix).isEmpty) "simply leave out the trailing ` _`" - else s"use `$prefix$suffix` instead" - def rewrite = Message.rewriteNotice("This construct", `3.4-migration`) - report.errorOrMigrationWarning( - em"""The syntax ` _` is no longer supported; - |you can $remedy$rewrite""", - tree.srcPos, - MigrationVersion.FunctionUnderscore) - if MigrationVersion.FunctionUnderscore.needsPatch then - patch(Span(tree.span.start), prefix) - patch(Span(qual.span.end, tree.span.end), suffix) - - res - } + override def typedAsFunction(tree: untpd.PostfixOp, pt: Type)(using Context): Tree = + migrate(super.typedAsFunction(tree, pt)) /** Translate infix operation expression `l op r` to * @@ -3137,13 +3097,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree: untpd.TypeDef => // separate method to keep dispatching method `typedNamed` short which might help the JIT def typedTypeOrClassDef: Tree = - if tree.name eq tpnme.? then - val addendum = if sym.owner.is(TypeParam) - then ", use `_` to denote a higher-kinded type parameter" - else "" - val namePos = tree.sourcePos.withSpan(tree.nameSpan) - report.errorOrMigrationWarning( - em"`?` is not a valid type name$addendum", namePos, MigrationVersion.Scala2to3) + migrate(kindProjectorQMark(tree, sym)) if tree.isClassDef then typedClassDef(tree, sym.asClass)(using ctx.localContext(tree, sym)) else @@ -3818,24 +3772,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { case wtp: MethodOrPoly => def methodStr = methPart(tree).symbol.showLocated - if (matchingApply(wtp, pt)) + if matchingApply(wtp, pt) then + migrate(contextBoundParams(tree, wtp, pt)) if needsTupledDual(wtp, pt) then adapt(tree, pt.tupledDual, locked) else tree else if wtp.isContextualMethod then - def isContextBoundParams = wtp.stripPoly match - case MethodType(ContextBoundParamName(_) :: _) => true - case _ => false - if sourceVersion == `future-migration` && isContextBoundParams && pt.args.nonEmpty - then // Under future-migration, don't infer implicit arguments yet for parameters - // coming from context bounds. Issue a warning instead and offer a patch. - def rewriteMsg = Message.rewriteNotice("This code", `future-migration`) - report.migrationWarning( - em"""Context bounds will map to context parameters. - |A `using` clause is needed to pass explicit arguments to them.$rewriteMsg""", tree.srcPos) - patch(Span(pt.args.head.span.start), "using ") - tree - else - adaptNoArgs(wtp) // insert arguments implicitly + adaptNoArgs(wtp) // insert arguments implicitly else if (tree.symbol.isPrimaryConstructor && tree.symbol.info.firstParamTypes.isEmpty) readapt(tree.appliedToNone) // insert () to primary constructors else @@ -4441,7 +4383,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer protected def matchingApply(methType: MethodOrPoly, pt: FunProto)(using Context): Boolean = val isUsingApply = pt.applyKind == ApplyKind.Using methType.isContextualMethod == isUsingApply - || methType.isImplicitMethod && isUsingApply // for a transition allow `with` arguments for regular implicit parameters + || methType.isImplicitMethod && isUsingApply // for a transition allow `using` arguments for regular implicit parameters /** Check that `tree == x: pt` is typeable. Used when checking a pattern * against a selector of type `pt`. This implementation accounts for diff --git a/tests/warn/context-bounds-migration.scala b/tests/warn/context-bounds-migration.scala new file mode 100644 index 000000000000..1094db68f41b --- /dev/null +++ b/tests/warn/context-bounds-migration.scala @@ -0,0 +1,10 @@ +//> using options -source 3.4 + +class C[T] +def foo[X: C] = () + +given [T]: C[T] = C[T]() + +def Test = + foo(C[Int]()) // warn + foo(using C[Int]()) // ok