From 02b4ec70422ff10f928cea8082de0d7f57fd4378 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 20 Dec 2023 20:08:57 +0100 Subject: [PATCH 1/5] Make explicit arguments for context bounds an error from 3.5 --- .../src/dotty/tools/dotc/ast/Desugar.scala | 3 +- .../dotty/tools/dotc/typer/Migrations.scala | 61 +++++++++++++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 29 ++------- tests/neg/context-bounds-migration.scala | 10 +++ tests/neg/hidden-type-errors.check | 26 ++++++++ 5 files changed, 105 insertions(+), 24 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/typer/Migrations.scala create mode 100644 tests/neg/context-bounds-migration.scala 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/typer/Migrations.scala b/compiler/src/dotty/tools/dotc/typer/Migrations.scala new file mode 100644 index 000000000000..284ec1d18799 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -0,0 +1,61 @@ +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 +import config.Feature.{sourceVersion, migrateTo3} +import config.SourceVersion.* +import reporting.* +import NameKinds.ContextBoundParamName +import rewrites.Rewrites.patch +import util.Spans.Span + +/** A utility module containing source-dependent deprecation messages + * and migrations + */ +object Migrations: + + import tpd.* + + /** Flag & migrate `?` used as a higher-kinded type parameter + * Warning in 3.0-migration, error from 3.0 + */ + def migrateKindProjectorQMark(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, MigrationVersion.Scala2to3) + + /** 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 migrateContextBoundParams(tree: Tree, tp: Type, pt: FunProto)(using Context): Unit = + 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", `3.5-migration`) + report.errorOrMigrationWarning( + em"""Context bounds will map to context parameters. + |A `using` clause is needed to pass explicit arguments to them.$rewriteMsg""", + tree.srcPos, MigrationVersion(`3.4`, `3.5`)) + if sourceVersion.isAtLeast(`3.5-migration`) then + patch(Span(pt.args.head.span.start), "using ") + end migrateContextBoundParams diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2f03c79754e8..50e5b22fabe0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -51,6 +51,7 @@ import NullOpsDecorator.* import cc.CheckCaptures import config.Config import config.MigrationVersion +import Migrations.* import scala.annotation.constructorOnly import dotty.tools.dotc.rewrites.Rewrites @@ -3137,13 +3138,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) + migrateKindProjectorQMark(tree, sym) if tree.isClassDef then typedClassDef(tree, sym.asClass)(using ctx.localContext(tree, sym)) else @@ -3818,24 +3813,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 + migrateContextBoundParams(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 +4424,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/neg/context-bounds-migration.scala b/tests/neg/context-bounds-migration.scala new file mode 100644 index 000000000000..b27dc884692c --- /dev/null +++ b/tests/neg/context-bounds-migration.scala @@ -0,0 +1,10 @@ +//> using options -Xfatal-warnings + +class C[T] +def foo[X: C] = () + +given [T]: C[T] = C[T]() + +def Test = + foo(C[Int]()) // error + foo(using C[Int]()) // ok diff --git a/tests/neg/hidden-type-errors.check b/tests/neg/hidden-type-errors.check index 2f4a1748dc67..2cf77134c2c5 100644 --- a/tests/neg/hidden-type-errors.check +++ b/tests/neg/hidden-type-errors.check @@ -1,3 +1,16 @@ +-- Warning: tests/neg/hidden-type-errors/Test.scala:8:24 --------------------------------------------------------------- + 8 | val x = X.doSomething("XXX") // error + | ^^^^^^^^^^^^^^^^^^^^ + | Context bounds will map to context parameters. + | A `using` clause is needed to pass explicit arguments to them. + | This code can be rewritten automatically under -rewrite -source 3.5-migration. + |-------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro.scala:15 +15 | doSomethingImpl('x) + | ^^^^^^^^^^^^^^^^^^^ + -------------------------------------------------------------------------------------------------------------------- -- [E007] Type Mismatch Error: tests/neg/hidden-type-errors/Test.scala:8:24 -------------------------------------------- 8 | val x = X.doSomething("XXX") // error | ^^^^^^^^^^^^^^^^^^^^ @@ -18,3 +31,16 @@ | | The tests were made under the empty constraint --------------------------------------------------------------------------------------------------------------------- +-- Warning: tests/neg/hidden-type-errors/Test.scala:8:24 --------------------------------------------------------------- + 8 | val x = X.doSomething("XXX") // error + | ^^^^^^^^^^^^^^^^^^^^ + | Context bounds will map to context parameters. + | A `using` clause is needed to pass explicit arguments to them. + | This code can be rewritten automatically under -rewrite -source 3.5-migration. + |-------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro.scala:15 +15 | doSomethingImpl('x) + | ^^^^^^^^^^^^^^^^^^^ + -------------------------------------------------------------------------------------------------------------------- From 41311bfa78078ea6859017010f4d992ce57e1f04 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 21 Dec 2023 16:49:30 +0100 Subject: [PATCH 2/5] Don't run migration operations in ReTypers --- .../dotty/tools/dotc/typer/Migrations.scala | 59 +++++++++++++++++-- .../src/dotty/tools/dotc/typer/ReTyper.scala | 1 + .../src/dotty/tools/dotc/typer/Typer.scala | 58 ++++-------------- 3 files changed, 64 insertions(+), 54 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Migrations.scala b/compiler/src/dotty/tools/dotc/typer/Migrations.scala index 284ec1d18799..9c038abbd851 100644 --- a/compiler/src/dotty/tools/dotc/typer/Migrations.scala +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -20,18 +20,20 @@ import reporting.* import NameKinds.ContextBoundParamName import rewrites.Rewrites.patch import util.Spans.Span +import rewrites.Rewrites -/** A utility module containing source-dependent deprecation messages - * and migrations +/** A utility trait containing source-dependent deprecation messages + * and migrations. */ -object Migrations: +trait Migrations: + this: Typer => import tpd.* /** Flag & migrate `?` used as a higher-kinded type parameter * Warning in 3.0-migration, error from 3.0 */ - def migrateKindProjectorQMark(tree: untpd.TypeDef, sym: Symbol)(using Context): Unit = + 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" @@ -40,10 +42,53 @@ object Migrations: report.errorOrMigrationWarning( em"`?` is not a valid type name$addendum", namePos, MigrationVersion.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, 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 + } + /** 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 migrateContextBoundParams(tree: Tree, tp: Type, pt: FunProto)(using Context): Unit = + def contextBoundParams(tree: Tree, tp: Type, pt: FunProto)(using Context): Unit = def isContextBoundParams = tp.stripPoly match case MethodType(ContextBoundParamName(_) :: _) => true case _ => false @@ -58,4 +103,6 @@ object Migrations: tree.srcPos, MigrationVersion(`3.4`, `3.5`)) if sourceVersion.isAtLeast(`3.5-migration`) then patch(Span(pt.args.head.span.start), "using ") - end migrateContextBoundParams + end contextBoundParams + +end Migrations diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index e152b5e6b9c7..253c4fda9396 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -189,4 +189,5 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking override protected def checkEqualityEvidence(tree: tpd.Tree, pt: Type)(using Context): Unit = () override protected def matchingApply(methType: MethodOrPoly, pt: FunProto)(using Context): Boolean = true override protected def typedScala2MacroBody(call: untpd.Tree)(using Context): Tree = promote(call) + override protected def migrate[T](migration: => T, disabled: => T = ()): T = disabled } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 50e5b22fabe0..5cb42f659551 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.* @@ -51,10 +51,8 @@ import NullOpsDecorator.* import cc.CheckCaptures import config.Config import config.MigrationVersion -import Migrations.* import scala.annotation.constructorOnly -import dotty.tools.dotc.rewrites.Rewrites object Typer { @@ -128,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 => _, _} @@ -159,6 +158,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Overridden in derived typers def newLikeThis(nestingLevel: Int): Typer = new Typer(nestingLevel) + // Overridden to do nothing in derived typers + protected def migrate[T](migration: => T, disabled: => T = ()): T = migration + /** Find the type of an identifier with given `name` in given context `ctx`. * @param name the name of the identifier * @param pt the expected type @@ -2979,48 +2981,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), throw new AssertionError("can't retype a PostfixOp")) /** Translate infix operation expression `l op r` to * @@ -3138,7 +3100,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 = - migrateKindProjectorQMark(tree, sym) + migrate(kindProjectorQMark(tree, sym)) if tree.isClassDef then typedClassDef(tree, sym.asClass)(using ctx.localContext(tree, sym)) else @@ -3814,7 +3776,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case wtp: MethodOrPoly => def methodStr = methPart(tree).symbol.showLocated if matchingApply(wtp, pt) then - migrateContextBoundParams(tree, wtp, pt) + migrate(contextBoundParams(tree, wtp, pt)) if needsTupledDual(wtp, pt) then adapt(tree, pt.tupledDual, locked) else tree else if wtp.isContextualMethod then From 697f107cec7608cc4c8d351ada885a312ad4a742 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 23 Dec 2023 16:14:56 +0100 Subject: [PATCH 3/5] Fix test --- tests/neg/hidden-type-errors.check | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/tests/neg/hidden-type-errors.check b/tests/neg/hidden-type-errors.check index 2cf77134c2c5..2f4a1748dc67 100644 --- a/tests/neg/hidden-type-errors.check +++ b/tests/neg/hidden-type-errors.check @@ -1,16 +1,3 @@ --- Warning: tests/neg/hidden-type-errors/Test.scala:8:24 --------------------------------------------------------------- - 8 | val x = X.doSomething("XXX") // error - | ^^^^^^^^^^^^^^^^^^^^ - | Context bounds will map to context parameters. - | A `using` clause is needed to pass explicit arguments to them. - | This code can be rewritten automatically under -rewrite -source 3.5-migration. - |-------------------------------------------------------------------------------------------------------------------- - |Inline stack trace - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from Macro.scala:15 -15 | doSomethingImpl('x) - | ^^^^^^^^^^^^^^^^^^^ - -------------------------------------------------------------------------------------------------------------------- -- [E007] Type Mismatch Error: tests/neg/hidden-type-errors/Test.scala:8:24 -------------------------------------------- 8 | val x = X.doSomething("XXX") // error | ^^^^^^^^^^^^^^^^^^^^ @@ -31,16 +18,3 @@ | | The tests were made under the empty constraint --------------------------------------------------------------------------------------------------------------------- --- Warning: tests/neg/hidden-type-errors/Test.scala:8:24 --------------------------------------------------------------- - 8 | val x = X.doSomething("XXX") // error - | ^^^^^^^^^^^^^^^^^^^^ - | Context bounds will map to context parameters. - | A `using` clause is needed to pass explicit arguments to them. - | This code can be rewritten automatically under -rewrite -source 3.5-migration. - |-------------------------------------------------------------------------------------------------------------------- - |Inline stack trace - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from Macro.scala:15 -15 | doSomethingImpl('x) - | ^^^^^^^^^^^^^^^^^^^ - -------------------------------------------------------------------------------------------------------------------- From 786852c9e6314d207cad9e25ae57dca60e7a7dd8 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 3 Jan 2024 09:27:56 +0100 Subject: [PATCH 4/5] Fix context bound application in enum desugaring --- compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: * From 13a71ef6075cc8acec3c2cb248251170d7203943 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 8 Jan 2024 19:34:33 +0100 Subject: [PATCH 5/5] Address review comments --- .../tools/dotc/config/MigrationVersion.scala | 14 ++++++-- .../tools/dotc/config/SourceVersion.scala | 3 ++ .../dotty/tools/dotc/typer/Migrations.scala | 32 ++++++++++++------- .../src/dotty/tools/dotc/typer/ReTyper.scala | 1 - .../src/dotty/tools/dotc/typer/Typer.scala | 5 +-- .../context-bounds-migration.scala | 4 +-- 6 files changed, 38 insertions(+), 21 deletions(-) rename tests/{neg => warn}/context-bounds-migration.scala (61%) 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 index 9c038abbd851..84db91f9dee9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Migrations.scala +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -13,7 +13,7 @@ import Symbols.* import Trees.* import ProtoTypes.* import Decorators.* -import config.MigrationVersion +import config.MigrationVersion as mv import config.Feature.{sourceVersion, migrateTo3} import config.SourceVersion.* import reporting.* @@ -30,6 +30,15 @@ trait Migrations: 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 */ @@ -40,7 +49,7 @@ trait Migrations: else "" val namePos = tree.sourcePos.withSpan(tree.nameSpan) report.errorOrMigrationWarning( - em"`?` is not a valid type name$addendum", namePos, MigrationVersion.Scala2to3) + 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 @@ -52,8 +61,8 @@ trait Migrations: 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 + report.errorOrMigrationWarning(msg, tree.srcPos, mv.Scala2to3) + if mv.Scala2to3.needsPatch then // Under -rewrite, patch `x _` to `(() => x)` msg.actions .headOption @@ -69,16 +78,16 @@ trait Migrations: 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", `3.4-migration`) + def rewrite = Message.rewriteNotice("This construct", mversion.patchFrom) report.errorOrMigrationWarning( em"""The syntax ` _` is no longer supported; |you can $remedy$rewrite""", - tree.srcPos, - MigrationVersion.FunctionUnderscore) - if MigrationVersion.FunctionUnderscore.needsPatch then + tree.srcPos, mversion) + if mversion.needsPatch then patch(Span(tree.span.start), prefix) patch(Span(qual.span.end, tree.span.end), suffix) @@ -89,6 +98,7 @@ trait Migrations: * 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 @@ -96,12 +106,12 @@ trait Migrations: && isContextBoundParams && pt.applyKind != ApplyKind.Using then - def rewriteMsg = Message.rewriteNotice("This code", `3.5-migration`) + 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, MigrationVersion(`3.4`, `3.5`)) - if sourceVersion.isAtLeast(`3.5-migration`) then + tree.srcPos, mversion) + if mversion.needsPatch then patch(Span(pt.args.head.span.start), "using ") end contextBoundParams diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 253c4fda9396..e152b5e6b9c7 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -189,5 +189,4 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking override protected def checkEqualityEvidence(tree: tpd.Tree, pt: Type)(using Context): Unit = () override protected def matchingApply(methType: MethodOrPoly, pt: FunProto)(using Context): Boolean = true override protected def typedScala2MacroBody(call: untpd.Tree)(using Context): Tree = promote(call) - override protected def migrate[T](migration: => T, disabled: => T = ()): T = disabled } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5cb42f659551..1303b64cbd12 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -158,9 +158,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Overridden in derived typers def newLikeThis(nestingLevel: Int): Typer = new Typer(nestingLevel) - // Overridden to do nothing in derived typers - protected def migrate[T](migration: => T, disabled: => T = ()): T = migration - /** Find the type of an identifier with given `name` in given context `ctx`. * @param name the name of the identifier * @param pt the expected type @@ -2982,7 +2979,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } override def typedAsFunction(tree: untpd.PostfixOp, pt: Type)(using Context): Tree = - migrate(super.typedAsFunction(tree, pt), throw new AssertionError("can't retype a PostfixOp")) + migrate(super.typedAsFunction(tree, pt)) /** Translate infix operation expression `l op r` to * diff --git a/tests/neg/context-bounds-migration.scala b/tests/warn/context-bounds-migration.scala similarity index 61% rename from tests/neg/context-bounds-migration.scala rename to tests/warn/context-bounds-migration.scala index b27dc884692c..1094db68f41b 100644 --- a/tests/neg/context-bounds-migration.scala +++ b/tests/warn/context-bounds-migration.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings +//> using options -source 3.4 class C[T] def foo[X: C] = () @@ -6,5 +6,5 @@ def foo[X: C] = () given [T]: C[T] = C[T]() def Test = - foo(C[Int]()) // error + foo(C[Int]()) // warn foo(using C[Int]()) // ok