Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make explicit arguments for context bounds an error from 3.5 #19316

Merged
merged 5 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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:
*
Expand Down
14 changes: 11 additions & 3 deletions compiler/src/dotty/tools/dotc/config/MigrationVersion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/config/SourceVersion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
118 changes: 118 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Migrations.scala
Original file line number Diff line number Diff line change
@@ -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 =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced we should make this one a mixin. The one use case right now for typedAsFunction, all other functions that are here and could go in here seem to be static.

I would suggest making Migrations an object and redefining Typer.typedAsFunction as

def typedAsFunction(tree: untpd.PostfixOp, pt: Type)(using Context): Tree =
    migrate(Migrations.typedAsFunction(tree, pt), throw new AssertionError("can't retype a PostfixOp"))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's nothing wrong with a mixin, and the hack of passing an explicit typer is worse. Right now it only applies tp typedAsFunction but I am sure that will not stay the only one.


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<function>$suffix` instead"
def rewrite = Message.rewriteNotice("This construct", mversion.patchFrom)
report.errorOrMigrationWarning(
em"""The syntax `<function> _` 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
78 changes: 10 additions & 68 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -53,7 +53,6 @@ import config.Config
import config.MigrationVersion

import scala.annotation.constructorOnly
import dotty.tools.dotc.rewrites.Rewrites

object Typer {

Expand Down Expand Up @@ -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 => _, _}
Expand Down Expand Up @@ -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<function>$suffix` instead"
def rewrite = Message.rewriteNotice("This construct", `3.4-migration`)
report.errorOrMigrationWarning(
em"""The syntax `<function> _` 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
*
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions tests/warn/context-bounds-migration.scala
Original file line number Diff line number Diff line change
@@ -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
Loading