From 7b9c5d862358780f62ff65476128e185f5a8f340 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Tue, 8 Apr 2025 12:39:17 +0200 Subject: [PATCH 1/2] Fix insertion of `using` in applications with trailing lambda syntax Co-Authored-By: Abdullah Arif Jafri <98524116+ajafri2001@users.noreply.github.com> --- .../dotty/tools/dotc/typer/Migrations.scala | 14 ++++- .../dotty/tools/dotc/CompilationTests.scala | 4 +- tests/rewrites/i22731.check | 11 ++++ tests/rewrites/i22731.scala | 13 +++++ tests/rewrites/i22731b.check | 49 +++++++++++++++++ tests/rewrites/i22731b.scala | 52 +++++++++++++++++++ 6 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 tests/rewrites/i22731.check create mode 100644 tests/rewrites/i22731.scala create mode 100644 tests/rewrites/i22731b.check create mode 100644 tests/rewrites/i22731b.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Migrations.scala b/compiler/src/dotty/tools/dotc/typer/Migrations.scala index 0e6dc27ecf7f..f216ef0f6fab 100644 --- a/compiler/src/dotty/tools/dotc/typer/Migrations.scala +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -138,7 +138,19 @@ trait Migrations: |""", pt.args.head.srcPos, mversion) if mversion.needsPatch then - patch(Span(pt.args.head.span.start), "using ") + // In order to insert a `using`, the application needs to be done with + // parentheses syntax. See issue #22927 and related tests. + patch(Span(tree.span.end, pt.args.head.span.start), "(using ") + // Tentative heuristic: the application was done with parentheses syntax + // if there is a `(` between the function and the first argument. + val hasParentheses = + ctx.source.content + .slice(tree.span.end, pt.args.head.span.start) + .exists(_ == '(') + if !hasParentheses then + // If the application wasn't done with the parentheses syntax, we need + // to add a trailing closing parenthesis. + patch(Span(pt.args.head.span.end), ")") end implicitParams end Migrations diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 47ed2aa6564d..2d6d78f47916 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -88,7 +88,9 @@ class CompilationTests { compileFile("tests/rewrites/ambiguous-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), compileFile("tests/rewrites/i21382.scala", defaultOptions.and("-indent", "-rewrite")), compileFile("tests/rewrites/unused.scala", defaultOptions.and("-rewrite", "-Wunused:all")), - compileFile("tests/rewrites/i22440.scala", defaultOptions.and("-rewrite")) + compileFile("tests/rewrites/i22440.scala", defaultOptions.and("-rewrite")), + compileFile("tests/rewrites/i22731.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")), + compileFile("tests/rewrites/i22731b.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")), ).checkRewrites() } diff --git a/tests/rewrites/i22731.check b/tests/rewrites/i22731.check new file mode 100644 index 000000000000..e3be2c4a6b7c --- /dev/null +++ b/tests/rewrites/i22731.check @@ -0,0 +1,11 @@ +class Input(x: String) + +trait Decoder[T] + +object Decoder: + inline def apply[T](implicit f: () => Unit): Decoder[T] = ??? + +object Input: + given Decoder[Input] = Decoder(using { () => + Input("") + }) diff --git a/tests/rewrites/i22731.scala b/tests/rewrites/i22731.scala new file mode 100644 index 000000000000..fbc23404bb8e --- /dev/null +++ b/tests/rewrites/i22731.scala @@ -0,0 +1,13 @@ +class Input(x: String) + +trait Decoder[T] + +object Decoder { + inline def apply[T](implicit f: () => Unit): Decoder[T] = ??? +} + +object Input { + given Decoder[Input] = Decoder { () => + Input("") + } +} diff --git a/tests/rewrites/i22731b.check b/tests/rewrites/i22731b.check new file mode 100644 index 000000000000..ff70383e31db --- /dev/null +++ b/tests/rewrites/i22731b.check @@ -0,0 +1,49 @@ +def foo(implicit f: () => Unit): Unit = ??? +def bar(a: Int)(implicit f: () => Unit): Unit = ??? + +@main def main = + // Trailing lambdas with braces: + foo(using { () => 43 }) + foo(using { () => val x = 42; 43 }) + foo(using { () => val x = 42; 43 }) + foo(using {() => val x = 42; 43}) + bar(1)(using { () => + val x = 42 + 43 }) + + // Parentheses: + foo(using () => 43 ) + foo(using () => + val x = 42 + 43 + ) + foo(using () => + val x = 42 + 43 + ) + foo(using () => + val x = 42 + 43 + ) + bar(1)(using () => + val x = 42 + 43 ) + + // Trailing lambdas with column and indentation: + foo(using () => + 43) + foo(using () => + val x = 42 + 43) + foo(using () => + val x = 42 + 43) + foo(using () => + val x = 42 + 43) + foo(using () => + val x = 42 + 43) + bar(1)(using () => + val x = 42 + 43) diff --git a/tests/rewrites/i22731b.scala b/tests/rewrites/i22731b.scala new file mode 100644 index 000000000000..2bdd8ba03722 --- /dev/null +++ b/tests/rewrites/i22731b.scala @@ -0,0 +1,52 @@ +def foo(implicit f: () => Unit): Unit = ??? +def bar(a: Int)(implicit f: () => Unit): Unit = ??? + +@main def main = + // Trailing lambdas with braces: + foo { () => 43 } + foo { () => val x = 42; 43 } + foo{ () => val x = 42; 43 } + foo {() => val x = 42; 43} + bar(1) { () => + val x = 42 + 43 } + + // Parentheses: + foo ( () => 43 ) + foo ( () => + val x = 42 + 43 + ) + foo( () => + val x = 42 + 43 + ) + foo (() => + val x = 42 + 43 + ) + bar(1) ( () => + val x = 42 + 43 ) + + // Trailing lambdas with column and indentation: + foo: () => + 43 + foo : () => + val x = 42 + 43 + foo :() => + val x = 42 + 43 + foo + : () => + val x = 42 + 43 + foo + : + () => + val x = 42 + 43 + bar(1) : () => + val x = 42 + 43 From b4a802aefb457d8e73d7d0bd0f3a921e764a989b Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Tue, 8 Apr 2025 13:48:04 +0200 Subject: [PATCH 2/2] Only insert `using` for applications with parentheses syntax --- .../dotty/tools/dotc/typer/Migrations.scala | 28 +++++---- tests/rewrites/i22731.check | 4 +- tests/rewrites/i22731b.check | 57 ++++++++++--------- tests/rewrites/i22731b.scala | 20 +++---- tests/warn/i22731.check | 13 +++++ tests/warn/i22731.scala | 7 +++ 6 files changed, 73 insertions(+), 56 deletions(-) create mode 100644 tests/warn/i22731.check create mode 100644 tests/warn/i22731.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Migrations.scala b/compiler/src/dotty/tools/dotc/typer/Migrations.scala index f216ef0f6fab..acf9e7668917 100644 --- a/compiler/src/dotty/tools/dotc/typer/Migrations.scala +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -130,27 +130,25 @@ trait Migrations: 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 then - val rewriteMsg = Message.rewriteNotice("This code", mversion.patchFrom) + // The application can only be rewritten if it uses parentheses syntax. + // See issue #22927 and related tests. + val hasParentheses = + ctx.source.content + .slice(tree.span.end, pt.args.head.span.start) + .exists(_ == '(') + val rewriteMsg = + if hasParentheses then + Message.rewriteNotice("This code", mversion.patchFrom) + else + "" report.errorOrMigrationWarning( em"""Implicit parameters should be provided with a `using` clause.$rewriteMsg |To disable the warning, please use the following option: | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" |""", pt.args.head.srcPos, mversion) - if mversion.needsPatch then - // In order to insert a `using`, the application needs to be done with - // parentheses syntax. See issue #22927 and related tests. - patch(Span(tree.span.end, pt.args.head.span.start), "(using ") - // Tentative heuristic: the application was done with parentheses syntax - // if there is a `(` between the function and the first argument. - val hasParentheses = - ctx.source.content - .slice(tree.span.end, pt.args.head.span.start) - .exists(_ == '(') - if !hasParentheses then - // If the application wasn't done with the parentheses syntax, we need - // to add a trailing closing parenthesis. - patch(Span(pt.args.head.span.end), ")") + if hasParentheses && mversion.needsPatch then + patch(Span(pt.args.head.span.start), "using ") end implicitParams end Migrations diff --git a/tests/rewrites/i22731.check b/tests/rewrites/i22731.check index e3be2c4a6b7c..366abac7f062 100644 --- a/tests/rewrites/i22731.check +++ b/tests/rewrites/i22731.check @@ -6,6 +6,6 @@ object Decoder: inline def apply[T](implicit f: () => Unit): Decoder[T] = ??? object Input: - given Decoder[Input] = Decoder(using { () => + given Decoder[Input] = Decoder { () => Input("") - }) + } diff --git a/tests/rewrites/i22731b.check b/tests/rewrites/i22731b.check index ff70383e31db..ae3c4660dc10 100644 --- a/tests/rewrites/i22731b.check +++ b/tests/rewrites/i22731b.check @@ -2,48 +2,49 @@ def foo(implicit f: () => Unit): Unit = ??? def bar(a: Int)(implicit f: () => Unit): Unit = ??? @main def main = - // Trailing lambdas with braces: - foo(using { () => 43 }) - foo(using { () => val x = 42; 43 }) - foo(using { () => val x = 42; 43 }) - foo(using {() => val x = 42; 43}) - bar(1)(using { () => - val x = 42 - 43 }) - - // Parentheses: - foo(using () => 43 ) - foo(using () => + // `using` can automatically be added when the application is done with parentheses + foo ( using () => 43 ) + foo ( using () => val x = 42 43 ) - foo(using () => + foo( using () => val x = 42 43 ) - foo(using () => + foo (using () => val x = 42 43 ) - bar(1)(using () => + bar(1) ( using () => val x = 42 43 ) - // Trailing lambdas with column and indentation: - foo(using () => - 43) - foo(using () => + // `using` cannot automatically be added when the application is done with trailing lambda syntax + foo { () => 43 } + foo { () => val x = 42; 43 } + foo{ () => val x = 42; 43 } + foo {() => val x = 42; 43} + bar(1) { () => + val x = 42 + 43 } + foo: () => + 43 + foo : () => val x = 42 - 43) - foo(using () => + 43 + foo :() => val x = 42 - 43) - foo(using () => + 43 + foo + : () => val x = 42 - 43) - foo(using () => + 43 + foo + : + () => val x = 42 - 43) - bar(1)(using () => + 43 + bar(1) : () => val x = 42 - 43) + 43 diff --git a/tests/rewrites/i22731b.scala b/tests/rewrites/i22731b.scala index 2bdd8ba03722..cd9de61fd719 100644 --- a/tests/rewrites/i22731b.scala +++ b/tests/rewrites/i22731b.scala @@ -2,16 +2,7 @@ def foo(implicit f: () => Unit): Unit = ??? def bar(a: Int)(implicit f: () => Unit): Unit = ??? @main def main = - // Trailing lambdas with braces: - foo { () => 43 } - foo { () => val x = 42; 43 } - foo{ () => val x = 42; 43 } - foo {() => val x = 42; 43} - bar(1) { () => - val x = 42 - 43 } - - // Parentheses: + // `using` can automatically be added when the application is done with parentheses foo ( () => 43 ) foo ( () => val x = 42 @@ -29,7 +20,14 @@ def bar(a: Int)(implicit f: () => Unit): Unit = ??? val x = 42 43 ) - // Trailing lambdas with column and indentation: + // `using` cannot automatically be added when the application is done with trailing lambda syntax + foo { () => 43 } + foo { () => val x = 42; 43 } + foo{ () => val x = 42; 43 } + foo {() => val x = 42; 43} + bar(1) { () => + val x = 42 + 43 } foo: () => 43 foo : () => diff --git a/tests/warn/i22731.check b/tests/warn/i22731.check new file mode 100644 index 000000000000..f7b25eb3b384 --- /dev/null +++ b/tests/warn/i22731.check @@ -0,0 +1,13 @@ +-- Warning: tests/warn/i22731.scala:5:11 ------------------------------------------------------------------------------- +5 | foo ( () => 43 ) // warn + | ^^^^^^^^ + | Implicit parameters should be provided with a `using` clause. + | This code can be rewritten automatically under -rewrite -source 3.7-migration. + | To disable the warning, please use the following option: + | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" +-- Warning: tests/warn/i22731.scala:7:6 -------------------------------------------------------------------------------- +7 | foo { () => 43 } // warn + | ^^^^^^^^^^^^ + | Implicit parameters should be provided with a `using` clause. + | To disable the warning, please use the following option: + | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" diff --git a/tests/warn/i22731.scala b/tests/warn/i22731.scala new file mode 100644 index 000000000000..388bbc8448b3 --- /dev/null +++ b/tests/warn/i22731.scala @@ -0,0 +1,7 @@ +def foo(implicit f: () => Unit): Unit = ??? + +@main def main = + // `using` can automatically be added when the application is done with parentheses + foo ( () => 43 ) // warn + // `using` cannot automatically be added when the application is done with trailing lambda syntax + foo { () => 43 } // warn