Skip to content
Open
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
14 changes: 12 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Migrations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,13 @@ trait Migrations:
*/
def contextBoundParams(tree: Tree, tp: Type, pt: FunProto)(using Context): Unit =
val mversion = mv.ExplicitContextBoundArgument
// Context bounds in `-source:3.6+` already desugar to a `using` clause, so the
// method type is `ContextualMethodType`. The `ImplicitParamsWithoutUsing`
// migration (handled by `implicitParams` below) covers calling those positionally;
// emitting the older `ExplicitContextBoundArgument` migration here would be a
// hard error past `3.5` and prevent the rewrite from being applied. See #26003.
def isContextBoundParams = tp.stripPoly match
case MethodType(ContextBoundParamName(_) :: _) => true
case mt @ MethodType(ContextBoundParamName(_) :: _) if !mt.isContextualMethod => true
case _ => false
if sourceVersion.isAtLeast(`3.4`)
&& isContextBoundParams
Expand All @@ -134,7 +139,12 @@ trait Migrations:
/** Report implicit parameter lists and rewrite implicit parameter list to contextual params */
def implicitParams(tree: Tree, tp: MethodOrPoly, pt: FunProto)(using Context): Unit =
val mversion = mv.ImplicitParamsWithoutUsing
if tp.companion == ImplicitMethodType && pt.applyKind != ApplyKind.Using && pt.args.nonEmpty && pt.args.head.span.exists then
// Fire for old-style `implicit` parameter lists (ImplicitMethodType) and also
// for `using` clauses (ContextualMethodType) that are passed positional arguments.
// The latter covers methods with desugared context bounds (e.g. `def f[A: B]`)
// that under -source:3.6+ become `using` clauses but were previously legal to
// call without `using`. See issue #26003.
if tp.isImplicitMethod && pt.applyKind != ApplyKind.Using && pt.args.nonEmpty && pt.args.head.span.exists then
// The application can only be rewritten if it uses parentheses syntax.
// See issue #22927 and related tests.
val hasParentheses = checkParentheses(tree, pt)
Expand Down
16 changes: 13 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5235,15 +5235,25 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
* This is the case if
* - both are contextual, or
* - neither is contextual, or
* - the prototype is contextual and the method type is implicit.
* The last rule is there for a transition period; it allows to mix `with` applications
* with old-style context functions.
* - the prototype is contextual and the method type is implicit, or
* - the prototype passes positional arguments to a contextual method while
* the `ImplicitParamsWithoutUsing` migration is active (so we can warn and
* rewrite the call site to insert `using`, see issue #26003).
* The last two rules are there for a transition period; they allow to mix
* `with` applications with old-style context functions, respectively allow
* to migrate Scala 2 / pre-3.6 code that calls a method with context bound
* parameters positionally.
* Overridden in `ReTyper`, where all applications are treated the same
*/
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 `using` arguments for regular implicit parameters
|| methType.isContextualMethod && !isUsingApply
&& pt.args.nonEmpty && pt.args.head.span.exists
&& !methType.resType.isInstanceOf[MethodOrPoly] // only when the `using` clause is the final parameter list
&& methType.paramNames.forall(_.is(ContextBoundParamName)) // only context-bound parameters (legacy Scala 2 style)
&& MigrationVersion.ImplicitParamsWithoutUsing.needsPatch

/** Check that `tree == x: pt` is typeable. Used when checking a pattern
* against a selector of type `pt`. This implementation accounts for
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class CompilationTests {
compileFile("tests/rewrites/i24103b.scala", defaultOptions.and("-rewrite", "-source:3.4-migration")),
compileFile("tests/rewrites/i24213.scala", defaultOptions.and("-rewrite", "-source:3.4-migration")),
compileFile("tests/rewrites/i18234.scala", defaultOptions.and("-rewrite", "-source:3.8-migration")),
compileFile("tests/rewrites/i26003.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")),
)).checkRewrites()
}

Expand Down
23 changes: 23 additions & 0 deletions tests/rewrites/i26003.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//> using options -source 3.7-migration

// https://github.com/scala/scala3/issues/26003

trait Schema[A]
object Schema:
case class Tuple2[A, B](left: Schema[A], right: Schema[B]) extends Schema[(A, B)]

trait JdbcEncoder[-A]
object JdbcEncoder:
implicit val intEncoder: JdbcEncoder[Int] = new JdbcEncoder[Int] {}
implicit val stringEncoder: JdbcEncoder[String] = new JdbcEncoder[String] {}
implicit val byteChunkEncoder: JdbcEncoder[Array[Byte]] = new JdbcEncoder[Array[Byte]] {}
implicit val uuidEncoder: JdbcEncoder[java.util.UUID] = new JdbcEncoder[java.util.UUID] {}
implicit def tuple2Encoder[A: JdbcEncoder, B: JdbcEncoder]: JdbcEncoder[(A, B)] = new JdbcEncoder[(A, B)] {}

trait JdbcEncoder0LowPriorityImplicits:
self =>
def fromSchema[A](implicit schema: Schema[A]): JdbcEncoder[A] =
schema match
case Schema.Tuple2(left, right) =>
JdbcEncoder.tuple2Encoder(using self.fromSchema(using left), self.fromSchema(using right))
case _ => null
24 changes: 24 additions & 0 deletions tests/rewrites/i26003.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//> using options -source 3.7-migration

// https://github.com/scala/scala3/issues/26003

trait Schema[A]
object Schema:
case class Tuple2[A, B](left: Schema[A], right: Schema[B]) extends Schema[(A, B)]

trait JdbcEncoder[-A]
object JdbcEncoder:
implicit val intEncoder: JdbcEncoder[Int] = new JdbcEncoder[Int] {}
implicit val stringEncoder: JdbcEncoder[String] = new JdbcEncoder[String] {}
implicit val byteChunkEncoder: JdbcEncoder[Array[Byte]] = new JdbcEncoder[Array[Byte]] {}
implicit val uuidEncoder: JdbcEncoder[java.util.UUID] = new JdbcEncoder[java.util.UUID] {}
implicit def tuple2Encoder[A: JdbcEncoder, B: JdbcEncoder]: JdbcEncoder[(A, B)] = new JdbcEncoder[(A, B)] {}

trait JdbcEncoder0LowPriorityImplicits:
self =>
def fromSchema[A](implicit schema: Schema[A]): JdbcEncoder[A] =
schema match {
case Schema.Tuple2(left, right) =>
JdbcEncoder.tuple2Encoder(self.fromSchema(left), self.fromSchema(right))
case _ => null
}
Loading