From e963a757bbaa59ae9a88dec42353c33d241364df Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 6 Aug 2025 16:11:14 +0200 Subject: [PATCH 001/111] Refine isEffectivelyFinal to avoid no-owner crash Fixes #23637 [Cherry-picked 310f239d876e9c3a2e1b5aea070cc79d2c094a0c] --- .../src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- tests/neg/i23637.check | 6 ++++++ tests/neg/i23637.scala | 12 ++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i23637.check create mode 100644 tests/neg/i23637.scala diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 8566ad2a6799..e17f127b7714 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1220,7 +1220,7 @@ object SymDenotations { || is(Inline, butNot = Deferred) || is(JavaDefinedVal, butNot = Method) || isConstructor - || !owner.isExtensibleClass && !is(Deferred) + || exists && !owner.isExtensibleClass && !is(Deferred) // Deferred symbols can arise through parent refinements under x.modularity. // For them, the overriding relationship reverses anyway, so // being in a final class does not mean the symbol cannot be diff --git a/tests/neg/i23637.check b/tests/neg/i23637.check new file mode 100644 index 000000000000..d568c04a31b7 --- /dev/null +++ b/tests/neg/i23637.check @@ -0,0 +1,6 @@ +-- [E083] Type Error: tests/neg/i23637.scala:6:9 ----------------------------------------------------------------------- +6 | export foo.pin.* // error: (because we need reflection to get at foo.pin) + | ^^^^^^^ + | (Test.foo.pin : Object) is not a valid export prefix, since it is not an immutable path + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i23637.scala b/tests/neg/i23637.scala new file mode 100644 index 000000000000..aac728f4fd99 --- /dev/null +++ b/tests/neg/i23637.scala @@ -0,0 +1,12 @@ +trait Foo extends reflect.Selectable +object Test: + val foo = new Foo: + object pin: + val x = 1 + export foo.pin.* // error: (because we need reflection to get at foo.pin) + +object OK: + object Foo: + object pin: + val x = 1 + export Foo.pin.* From 18913ee7441afc4de6a7cb9bb9e48dfbba505511 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 6 Aug 2025 14:41:42 +0200 Subject: [PATCH 002/111] Fix LiftToAnchors for higher-kinded type applications Fixes #21951 [Cherry-picked 2029803f7d4907df56436d06de5d828fbd1653b0] --- .../dotty/tools/dotc/typer/Implicits.scala | 4 +++ tests/pos/i21951.scala | 33 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 tests/pos/i21951.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index fa5b1cbfe19e..c47d12ba7e88 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -844,6 +844,10 @@ trait ImplicitRunInfo: case t: TypeVar => apply(t.underlying) case t: ParamRef => applyToUnderlying(t) case t: ConstantType => apply(t.underlying) + case t @ AppliedType(tycon, args) if !tycon.typeSymbol.isClass => + // To prevent arguments to be reduced away when re-applying the tycon bounds, + // we collect all parts as elements of a tuple. See i21951.scala for a test case. + apply(defn.tupleType(tycon :: args)) case t => mapOver(t) end liftToAnchors val liftedTp = liftToAnchors(tp) diff --git a/tests/pos/i21951.scala b/tests/pos/i21951.scala new file mode 100644 index 000000000000..5b2b5b932fab --- /dev/null +++ b/tests/pos/i21951.scala @@ -0,0 +1,33 @@ +class A +object A: + given g[F[_]]: F[A] = ??? + +object Test: + summon[List[A]] // ok + def foo[F[_]] = + summon[F[A]] // error + +final case class X(val i: Int) +object X { + implicit final class XOps[F[_]](xs: F[X]) { + def unpack(implicit ev: F[X] <:< Iterable[X]): Iterable[Int] = xs.map(_.i) + } +} + +object App extends App { + // good + val ys: List[X] = List(X(1)) + println(ys.unpack) + + // bad + def printPolymorphic[F[_]](xs: F[X])(implicit ev: F[X] <:< Iterable[X]) = { + locally { + // implicit XOps is correct + import X.XOps + println(xs.unpack) // found + } + // but it's not being searched for in the companion object of X + println(xs.unpack) // error: unpack is not a member of F[X] + } + printPolymorphic[List](ys) +} \ No newline at end of file From d95ce2cfc4ec3351ff2f67155fda8db0a6a4d887 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 8 Aug 2025 09:00:47 -0700 Subject: [PATCH 003/111] Use interpolation message kind also in s [Cherry-picked e6e549cffbdfba270d72fc17a9f68fac37f22c90] --- .../localopt/StringInterpolatorOpt.scala | 15 ++++++++++---- tests/run/i23693.check | 4 ++++ tests/run/i23693.scala | 17 ++++++++++++++++ tests/warn/i23693.check | 20 +++++++++++++++++++ tests/warn/i23693.scala | 19 ++++++++++++++++++ 5 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 tests/run/i23693.check create mode 100644 tests/run/i23693.scala create mode 100644 tests/warn/i23693.check create mode 100644 tests/warn/i23693.scala diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala b/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala index 804150eafc4e..dc810d37a17d 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala @@ -10,6 +10,8 @@ import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.printing.Formatting.* +import dotty.tools.dotc.reporting.BadFormatInterpolation import dotty.tools.dotc.transform.MegaPhase.MiniPhase import dotty.tools.dotc.typer.ConstFold @@ -22,8 +24,9 @@ import dotty.tools.dotc.typer.ConstFold */ class StringInterpolatorOpt extends MiniPhase: import tpd.* + import StringInterpolatorOpt.* - override def phaseName: String = StringInterpolatorOpt.name + override def phaseName: String = name override def description: String = StringInterpolatorOpt.description @@ -31,7 +34,7 @@ class StringInterpolatorOpt extends MiniPhase: tree match case tree: RefTree => val sym = tree.symbol - assert(!StringInterpolatorOpt.isCompilerIntrinsic(sym), + assert(!isCompilerIntrinsic(sym), i"$tree in ${ctx.owner.showLocated} should have been rewritten by phase $phaseName") case _ => @@ -117,10 +120,10 @@ class StringInterpolatorOpt extends MiniPhase: !(tp =:= defn.StringType) && { tp =:= defn.UnitType - && { report.warning("interpolated Unit value", t.srcPos); true } + && { report.warning(bfi"interpolated Unit value", t.srcPos); true } || !tp.isPrimitiveValueType - && { report.warning("interpolation uses toString", t.srcPos); true } + && { report.warning(bfi"interpolation uses toString", t.srcPos); true } } if ctx.settings.Whas.toStringInterpolated then checkIsStringify(t.tpe): Unit @@ -186,3 +189,7 @@ object StringInterpolatorOpt: sym == defn.StringContext_s || sym == defn.StringContext_f || sym == defn.StringContext_raw + + extension (sc: StringContext) + def bfi(args: Shown*)(using Context): BadFormatInterpolation = + BadFormatInterpolation(i(sc)(args*)) diff --git a/tests/run/i23693.check b/tests/run/i23693.check new file mode 100644 index 000000000000..dc8fba77450a --- /dev/null +++ b/tests/run/i23693.check @@ -0,0 +1,4 @@ +k == K(42) +\k == \K(42) +k == +k == K(42) diff --git a/tests/run/i23693.scala b/tests/run/i23693.scala new file mode 100644 index 000000000000..4bd255543a47 --- /dev/null +++ b/tests/run/i23693.scala @@ -0,0 +1,17 @@ +//> using options -Wtostring-interpolated + +// verify warning messages and runtime result +// never mind, the test rig doesn't log diagnostics! unlike beloved partest. + +case class K(i: Int) + +@main def Test = + val k = K(42) + println: + s"k == $k" + println: + raw"\k == \$k" + println: + f"k == $k" + println: + f"k == $k%s" diff --git a/tests/warn/i23693.check b/tests/warn/i23693.check new file mode 100644 index 000000000000..66b238de13a2 --- /dev/null +++ b/tests/warn/i23693.check @@ -0,0 +1,20 @@ +-- [E209] Interpolation Warning: tests/warn/i23693.scala:11:12 --------------------------------------------------------- +11 | s"k == $k" // warn + | ^ + | interpolation uses toString +-- [E209] Interpolation Warning: tests/warn/i23693.scala:13:16 --------------------------------------------------------- +13 | raw"\k == \$k" // warn + | ^ + | interpolation uses toString +-- [E209] Interpolation Warning: tests/warn/i23693.scala:15:12 --------------------------------------------------------- +15 | f"k == $k" // warn + | ^ + | interpolation uses toString +-- [E209] Interpolation Warning: tests/warn/i23693.scala:17:14 --------------------------------------------------------- +17 | f"k == $k%s" // warn + | ^ + | interpolation uses toString +-- [E209] Interpolation Warning: tests/warn/i23693.scala:19:18 --------------------------------------------------------- +19 | s"show == ${k.show}" // warn + | ^^^^^^ + | interpolated Unit value diff --git a/tests/warn/i23693.scala b/tests/warn/i23693.scala new file mode 100644 index 000000000000..79fb7ab155ce --- /dev/null +++ b/tests/warn/i23693.scala @@ -0,0 +1,19 @@ +//> using options -Wtostring-interpolated + +// verify warning messages and runtime result + +case class K(i: Int): + def show: Unit = () + +@main def Test = + val k = K(42) + println: + s"k == $k" // warn + println: + raw"\k == \$k" // warn + println: + f"k == $k" // warn + println: + f"k == $k%s" // warn + println: + s"show == ${k.show}" // warn From d9f17f0a111390223cf605b363b516bc57d52304 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 8 Aug 2025 09:59:32 -0700 Subject: [PATCH 004/111] Do not discard amended format on warning in f [Cherry-picked 547a186b537fd6f21fae6eb601264305c48608da] --- .../dotty/tools/dotc/transform/localopt/FormatChecker.scala | 3 +-- tests/run/i23693.check | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala b/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala index 00daefba3547..83ec7fb8399e 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala @@ -116,7 +116,7 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List case Nil => loop(parts, n = 0) - if reported then (Nil, Nil) + if reported then (Nil, Nil) // on error, Transform.checked will revert to unamended inputs else assert(argc == actuals.size, s"Expected ${argc} args but got ${actuals.size} for [${parts.mkString(", ")}]") (amended.toList, actuals.toList) @@ -320,5 +320,4 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List .tap(_ => reported = true) def partWarning(message: String, index: Int, offset: Int, end: Int): Unit = r.warning(BadFormatInterpolation(message), partPosAt(index, offset, end)) - .tap(_ => reported = true) end TypedFormatChecker diff --git a/tests/run/i23693.check b/tests/run/i23693.check index dc8fba77450a..32cd0bef7dee 100644 --- a/tests/run/i23693.check +++ b/tests/run/i23693.check @@ -1,4 +1,4 @@ k == K(42) \k == \K(42) -k == +k == K(42) k == K(42) From c2a58e3be97d2b7800f7909c20c804d6ad80fa96 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 8 Aug 2025 10:45:44 -0700 Subject: [PATCH 005/111] Inline extra object to StringInterpOpt [Cherry-picked ed63fe8dfa9e7535ece8ee38ba147bec963a8b93] --- .../FormatInterpolatorTransform.scala | 39 ------------------- .../localopt/StringInterpolatorOpt.scala | 30 +++++++++++++- 2 files changed, 29 insertions(+), 40 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/FormatInterpolatorTransform.scala diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/FormatInterpolatorTransform.scala b/compiler/src/dotty/tools/dotc/transform/localopt/FormatInterpolatorTransform.scala deleted file mode 100644 index 79d94c26c692..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/FormatInterpolatorTransform.scala +++ /dev/null @@ -1,39 +0,0 @@ -package dotty.tools.dotc -package transform.localopt - -import dotty.tools.dotc.ast.tpd.* -import dotty.tools.dotc.core.Constants.Constant -import dotty.tools.dotc.core.Contexts.* - -object FormatInterpolatorTransform: - - /** For f"${arg}%xpart", check format conversions and return (format, args) - * suitable for String.format(format, args). - */ - def checked(fun: Tree, args0: Tree)(using Context): (Tree, Tree) = - val (partsExpr, parts) = fun match - case TypeApply(Select(Apply(_, (parts: SeqLiteral) :: Nil), _), _) => - (parts.elems, parts.elems.map { case Literal(Constant(s: String)) => s }) - case _ => - report.error("Expected statically known StringContext", fun.srcPos) - (Nil, Nil) - val (args, elemtpt) = args0 match - case seqlit: SeqLiteral => (seqlit.elems, seqlit.elemtpt) - case _ => - report.error("Expected statically known argument list", args0.srcPos) - (Nil, EmptyTree) - - def literally(s: String) = Literal(Constant(s)) - if parts.lengthIs != args.length + 1 then - val badParts = - if parts.isEmpty then "there are no parts" - else s"too ${if parts.lengthIs > args.length + 1 then "few" else "many"} arguments for interpolated string" - report.error(badParts, fun.srcPos) - (literally(""), args0) - else - val checker = TypedFormatChecker(partsExpr, parts, args) - val (format, formatArgs) = checker.checked - if format.isEmpty then (literally(parts.mkString), args0) - else (literally(format.mkString), SeqLiteral(formatArgs.toList, elemtpt)) - end checked -end FormatInterpolatorTransform diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala b/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala index dc810d37a17d..db3a0c6c71f2 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala @@ -137,10 +137,38 @@ class StringInterpolatorOpt extends MiniPhase: case _ => false // Perform format checking and normalization, then make it StringOps(fmt).format(args1) with tweaked args def transformF(fun: Tree, args: Tree): Tree = - val (fmt, args1) = FormatInterpolatorTransform.checked(fun, args) + // For f"${arg}%xpart", check format conversions and return (format, args) for String.format(format, args). + def checked(args0: Tree)(using Context): (Tree, Tree) = + val (partsExpr, parts) = fun match + case TypeApply(Select(Apply(_, (parts: SeqLiteral) :: Nil), _), _) => + (parts.elems, parts.elems.map { case Literal(Constant(s: String)) => s }) + case _ => + report.error("Expected statically known StringContext", fun.srcPos) + (Nil, Nil) + val (args, elemtpt) = args0 match + case seqlit: SeqLiteral => (seqlit.elems, seqlit.elemtpt) + case _ => + report.error("Expected statically known argument list", args0.srcPos) + (Nil, EmptyTree) + + def literally(s: String) = Literal(Constant(s)) + if parts.lengthIs != args.length + 1 then + val badParts = + if parts.isEmpty then "there are no parts" + else s"too ${if parts.lengthIs > args.length + 1 then "few" else "many"} arguments for interpolated string" + report.error(badParts, fun.srcPos) + (literally(""), args0) + else + val checker = TypedFormatChecker(partsExpr, parts, args) + val (format, formatArgs) = checker.checked + if format.isEmpty then (literally(parts.mkString), args0) // on error just use unchecked inputs + else (literally(format.mkString), SeqLiteral(formatArgs.toList, elemtpt)) + end checked + val (fmt, args1) = checked(args) resolveConstructor(defn.StringOps.typeRef, List(fmt)) .select(nme.format) .appliedTo(args1) + end transformF // Starting with Scala 2.13, s and raw are macros in the standard // library, so we need to expand them manually. // sc.s(args) --> standardInterpolator(processEscapes, args, sc.parts) From 0caddc69c73862b2e08e139307e3aec72f50209a Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 11 Aug 2025 12:24:30 -0700 Subject: [PATCH 006/111] Tweak test per review; prefer assert to print [Cherry-picked f73185f8c61879ece6e809baa2321dc04a356d42] --- tests/run/i23693.check | 4 ---- tests/run/i23693.scala | 15 ++++++++++----- tests/warn/i23693.scala | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) delete mode 100644 tests/run/i23693.check diff --git a/tests/run/i23693.check b/tests/run/i23693.check deleted file mode 100644 index 32cd0bef7dee..000000000000 --- a/tests/run/i23693.check +++ /dev/null @@ -1,4 +0,0 @@ -k == K(42) -\k == \K(42) -k == K(42) -k == K(42) diff --git a/tests/run/i23693.scala b/tests/run/i23693.scala index 4bd255543a47..c77959d99263 100644 --- a/tests/run/i23693.scala +++ b/tests/run/i23693.scala @@ -1,17 +1,22 @@ //> using options -Wtostring-interpolated -// verify warning messages and runtime result +// verify ~warning messages and~ runtime result // never mind, the test rig doesn't log diagnostics! unlike beloved partest. +// Sadly, junit is not available. +//import org.junit.Assert.assertEquals as jassert + +def assertEquals(expected: String)(actual: String): Unit = assert(expected == actual) + case class K(i: Int) @main def Test = val k = K(42) - println: + assertEquals("k == K(42)"): s"k == $k" - println: + assertEquals("\\k == \\K(42)"): raw"\k == \$k" - println: + assertEquals("k == K(42)"): f"k == $k" - println: + assertEquals("k == K(42)"): f"k == $k%s" diff --git a/tests/warn/i23693.scala b/tests/warn/i23693.scala index 79fb7ab155ce..341977dc717c 100644 --- a/tests/warn/i23693.scala +++ b/tests/warn/i23693.scala @@ -1,6 +1,6 @@ //> using options -Wtostring-interpolated -// verify warning messages and runtime result +// verify warning messages; cf run test; we must verify runtime while warning. case class K(i: Int): def show: Unit = () From 36e264d54b3b1e0533064aae01cf75c86f1f7d1f Mon Sep 17 00:00:00 2001 From: Seyon Sivatharan Date: Thu, 24 Jul 2025 14:30:19 -0400 Subject: [PATCH 007/111] First draft of add nn quick fix [Cherry-picked f952dd2e2b2664d2a3a339b51c3b252e0ee9984d] --- .../dotty/tools/dotc/reporting/messages.scala | 27 +++++++++++++++++++ .../tools/dotc/reporting/CodeActionTest.scala | 14 ++++++++++ 2 files changed, 41 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 210322841158..ab554cf63e06 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -300,6 +300,10 @@ extends NotFoundMsg(MissingIdentID) { class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tree], addenda: => String*)(using Context) extends TypeMismatchMsg(found, expected)(TypeMismatchID): + private var shouldSuggestNN = false + // Ensures that shouldSuggestNN will always be correctly computed before `actions` is called + msg + def msg(using Context) = // replace constrained TypeParamRefs and their typevars by their bounds where possible // and the bounds are not f-bounds. @@ -344,6 +348,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre val (found2, expected2) = if (found1 frozen_<:< expected1) || reported.fbounded then (found, expected) else (found1, expected1) + if found2 frozen_<:< OrNull(expected) then shouldSuggestNN = true val (foundStr, expectedStr) = Formatting.typeDiff(found2.normalized, expected2.normalized) i"""|Found: $foundStr |Required: $expectedStr${reported.notes}""" @@ -360,6 +365,28 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre val treeStr = inTree.map(x => s"\nTree:\n\n${x.show}\n").getOrElse("") treeStr + "\n" + super.explain + override def actions(using Context) = + if shouldSuggestNN then + inTree match { + case Some(tree) if tree != null => + val content = tree.source.content().slice(tree.srcPos.startPos.start, tree.srcPos.endPos.end).mkString + val replacement = tree match + case a @ Apply(fun, args) => "(" + content + ").nn" + case _ => content + List( + CodeAction(title = """Add .nn""", + description = None, + patches = List( + ActionPatch(tree.srcPos.sourcePos, replacement) + ) + ) + ) + case _ => + List() + } + else + List() + end TypeMismatch class NotAMember(site: Type, val name: Name, selected: String, proto: Type, addendum: => String = "")(using Context) diff --git a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala index 91074110389e..14834b5d8760 100644 --- a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala +++ b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala @@ -179,6 +179,20 @@ class CodeActionTest extends DottyTest: ctxx = ctxx ) + @Test def addNN = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """val s: String|Null = ??? + | val t: String = s""".stripMargin, + title = "Add .nn", + expected = + """val s: String|Null = ??? + | val t: String = (s).nn""".stripMargin, + ctxx = ctxx + ) + // Make sure we're not using the default reporter, which is the ConsoleReporter, // meaning they will get reported in the test run and that's it. private def newContext = From 7a7b864c66d10ce7ca8919a4becaceddd2c16907 Mon Sep 17 00:00:00 2001 From: HarrisL2 Date: Fri, 25 Jul 2025 13:38:01 -0400 Subject: [PATCH 008/111] Only suggest .nn on value types [Cherry-picked 6c2147d6de4de8fffab4269edd9d42703c7d0f25] --- compiler/src/dotty/tools/dotc/reporting/messages.scala | 10 +++++----- .../dotty/tools/dotc/reporting/CodeActionTest.scala | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index ab554cf63e06..d145000aae61 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -300,9 +300,10 @@ extends NotFoundMsg(MissingIdentID) { class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tree], addenda: => String*)(using Context) extends TypeMismatchMsg(found, expected)(TypeMismatchID): - private var shouldSuggestNN = false - // Ensures that shouldSuggestNN will always be correctly computed before `actions` is called - msg + private val shouldSuggestNN = + if expected.isValueType then + found frozen_<:< OrNull(expected) + else false def msg(using Context) = // replace constrained TypeParamRefs and their typevars by their bounds where possible @@ -348,7 +349,6 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre val (found2, expected2) = if (found1 frozen_<:< expected1) || reported.fbounded then (found, expected) else (found1, expected1) - if found2 frozen_<:< OrNull(expected) then shouldSuggestNN = true val (foundStr, expectedStr) = Formatting.typeDiff(found2.normalized, expected2.normalized) i"""|Found: $foundStr |Required: $expectedStr${reported.notes}""" @@ -372,7 +372,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre val content = tree.source.content().slice(tree.srcPos.startPos.start, tree.srcPos.endPos.end).mkString val replacement = tree match case a @ Apply(fun, args) => "(" + content + ").nn" - case _ => content + case _ => content + ".nn" List( CodeAction(title = """Add .nn""", description = None, diff --git a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala index 14834b5d8760..ddbdb6b3531a 100644 --- a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala +++ b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala @@ -189,7 +189,7 @@ class CodeActionTest extends DottyTest: title = "Add .nn", expected = """val s: String|Null = ??? - | val t: String = (s).nn""".stripMargin, + | val t: String = s.nn""".stripMargin, ctxx = ctxx ) From 7d5f5cb7b6b2e38aa483c7221b0d21266ce3b233 Mon Sep 17 00:00:00 2001 From: Seyon Sivatharan Date: Fri, 25 Jul 2025 14:23:09 -0400 Subject: [PATCH 009/111] Remove unnecessary pattern binding and add more tests [Cherry-picked 8229c8b01fe23a0798641319ba356fa2975068d0] --- .../dotty/tools/dotc/reporting/messages.scala | 2 +- .../tools/dotc/reporting/CodeActionTest.scala | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index d145000aae61..80862637f63e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -371,7 +371,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre case Some(tree) if tree != null => val content = tree.source.content().slice(tree.srcPos.startPos.start, tree.srcPos.endPos.end).mkString val replacement = tree match - case a @ Apply(fun, args) => "(" + content + ").nn" + case Apply(fun, args) => "(" + content + ").nn" case _ => content + ".nn" List( CodeAction(title = """Add .nn""", diff --git a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala index ddbdb6b3531a..c9c83c1a4d2f 100644 --- a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala +++ b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala @@ -193,6 +193,65 @@ class CodeActionTest extends DottyTest: ctxx = ctxx ) + @Test def addNN2 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """implicit class infixOpTest(val s1: String) extends AnyVal { + | def q(s2: String): String | Null = null + |} + | val s: String = ??? + | val t: String = s q s""".stripMargin, + title = "Add .nn", + expected = + """implicit class infixOpTest(val s1: String) extends AnyVal { + | def q(s2: String): String | Null = null + |} + | val s: String = ??? + | val t: String = (s q s).nn""".stripMargin, + ctxx = ctxx + ) + + @Test def addNN3 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """implicit class infixOpTest(val s1: String) extends AnyVal { + | def q(s2: String, s3: String): String | Null = null + |} + | val s: String = ??? + | val t: String = s q (s, s)""".stripMargin, + title = "Add .nn", + expected = + """implicit class infixOpTest(val s1: String) extends AnyVal { + | def q(s2: String, s3: String): String | Null = null + |} + | val s: String = ??? + | val t: String = (s q (s, s)).nn""".stripMargin, + ctxx = ctxx + ) + + @Test def addNN4 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """implicit class infixOpTest(val s1: String) extends AnyVal { + | def q(s2: String, s3: String): String | Null = null + |} + | val s: String = ??? + | val t: String = s.q(s, s)""".stripMargin, + title = "Add .nn", + expected = + """implicit class infixOpTest(val s1: String) extends AnyVal { + | def q(s2: String, s3: String): String | Null = null + |} + | val s: String = ??? + | val t: String = (s.q(s, s)).nn""".stripMargin, + ctxx = ctxx + ) // Make sure we're not using the default reporter, which is the ConsoleReporter, // meaning they will get reported in the test run and that's it. private def newContext = From 8ac8086b852c425e3a80bd6a0a24484bd2d8f88c Mon Sep 17 00:00:00 2001 From: Seyon Sivatharan Date: Fri, 1 Aug 2025 16:11:27 -0400 Subject: [PATCH 010/111] Add SafeNulls check before computing subtyping relation, fix edge cases for in-line match and if, improve formatting, and add more test Signed-off-by: Seyon Sivatharan [Cherry-picked 70a169a9dffce37664deef8d3e53f7aefdae6c5b] --- .../dotty/tools/dotc/reporting/messages.scala | 32 ++++++----- .../tools/dotc/reporting/CodeActionTest.scala | 53 ++++++++++++++++++- 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 80862637f63e..51baccf064a4 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -301,7 +301,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre extends TypeMismatchMsg(found, expected)(TypeMismatchID): private val shouldSuggestNN = - if expected.isValueType then + if ctx.mode.is(Mode.SafeNulls) && expected.isValueType then found frozen_<:< OrNull(expected) else false @@ -366,27 +366,25 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre treeStr + "\n" + super.explain override def actions(using Context) = - if shouldSuggestNN then - inTree match { - case Some(tree) if tree != null => - val content = tree.source.content().slice(tree.srcPos.startPos.start, tree.srcPos.endPos.end).mkString - val replacement = tree match - case Apply(fun, args) => "(" + content + ").nn" - case _ => content + ".nn" - List( - CodeAction(title = """Add .nn""", + inTree match { + case Some(tree) if shouldSuggestNN => + val content = tree.source.content().slice(tree.srcPos.startPos.start, tree.srcPos.endPos.end).mkString + val replacement = tree match + case a @ Apply(_, _) if a.applyKind == ApplyKind.Using => + content + ".nn" + case _ @ (Select(_, _) | Ident(_)) => content + ".nn" + case _ => "(" + content + ").nn" + List( + CodeAction(title = """Add .nn""", description = None, patches = List( ActionPatch(tree.srcPos.sourcePos, replacement) ) - ) ) - case _ => - List() - } - else - List() - + ) + case _ => + List() + } end TypeMismatch class NotAMember(site: Type, val name: Name, selected: String, proto: Type, addendum: => String = "")(using Context) diff --git a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala index c9c83c1a4d2f..17282532f801 100644 --- a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala +++ b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala @@ -179,7 +179,7 @@ class CodeActionTest extends DottyTest: ctxx = ctxx ) - @Test def addNN = + @Test def addNN1 = val ctxx = newContext ctxx.setSetting(ctxx.settings.YexplicitNulls, true) checkCodeAction( @@ -252,6 +252,57 @@ class CodeActionTest extends DottyTest: | val t: String = (s.q(s, s)).nn""".stripMargin, ctxx = ctxx ) + + @Test def addNN5 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """val s: String | Null = ??? + |val t: String = s match { + | case _: String => "foo" + | case _ => s + |}""".stripMargin, + title = "Add .nn", + expected = + """val s: String | Null = ??? + |val t: String = s match { + | case _: String => "foo" + | case _ => s.nn + |}""".stripMargin, + ctxx = ctxx + ) + + @Test def addNN6 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """val s: String | Null = ??? + |val t: String = if (s != null) "foo" else s""".stripMargin, + title = "Add .nn", + expected = + """val s: String | Null = ??? + |val t: String = if (s != null) "foo" else s.nn""".stripMargin, + ctxx = ctxx + ) + + @Test def addNN7 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """given ctx: String | Null = null + |def f(using c: String): String = c + |val s: String = f(using ctx)""".stripMargin, + title = "Add .nn", + expected = + """given ctx: String | Null = null + |def f(using c: String): String = c + |val s: String = f(using ctx.nn)""".stripMargin, + ctxx = ctxx + ) + // Make sure we're not using the default reporter, which is the ConsoleReporter, // meaning they will get reported in the test run and that's it. private def newContext = From deab8022011f8d6f56b3fb20218b916818f93fe2 Mon Sep 17 00:00:00 2001 From: Seyon Sivatharan Date: Tue, 12 Aug 2025 15:40:25 -0400 Subject: [PATCH 011/111] Add WasTypedInfix sticky attachment and push it to Apply nodes that are typed infix [Cherry-picked bda8348d8e64e00e5527cacd2a9d3d875dcd93d6] --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 6 +++++- compiler/src/dotty/tools/dotc/reporting/messages.scala | 3 ++- .../test/dotty/tools/dotc/reporting/CodeActionTest.scala | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 8184f18a8733..cd86e064cfb4 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -67,6 +67,8 @@ object desugar { */ val TrailingForMap: Property.Key[Unit] = Property.StickyKey() + val WasTypedInfix: Property.Key[Unit] = Property.StickyKey() + /** What static check should be applied to a Match? */ enum MatchCheck { case None, Exhaustive, IrrefutablePatDef, IrrefutableGenFrom @@ -1720,10 +1722,12 @@ object desugar { case _ => Apply(sel, arg :: Nil) - if op.name.isRightAssocOperatorName then + val apply = if op.name.isRightAssocOperatorName then makeOp(right, left, Span(op.span.start, right.span.end)) else makeOp(left, right, Span(left.span.start, op.span.end, op.span.start)) + apply.pushAttachment(WasTypedInfix, ()) + return apply } /** Translate throws type `A throws E1 | ... | En` to diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 51baccf064a4..68a5ce21f42b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -14,6 +14,7 @@ import printing.Highlighting.* import printing.Formatting import ErrorMessageID.* import ast.Trees +import ast.desugar import config.{Feature, MigrationVersion, ScalaVersion} import transform.patmat.Space import transform.patmat.SpaceEngine @@ -370,7 +371,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre case Some(tree) if shouldSuggestNN => val content = tree.source.content().slice(tree.srcPos.startPos.start, tree.srcPos.endPos.end).mkString val replacement = tree match - case a @ Apply(_, _) if a.applyKind == ApplyKind.Using => + case a @ Apply(_, _) if !a.hasAttachment(desugar.WasTypedInfix) => content + ".nn" case _ @ (Select(_, _) | Ident(_)) => content + ".nn" case _ => "(" + content + ").nn" diff --git a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala index 17282532f801..91ff958a7889 100644 --- a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala +++ b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala @@ -249,7 +249,7 @@ class CodeActionTest extends DottyTest: | def q(s2: String, s3: String): String | Null = null |} | val s: String = ??? - | val t: String = (s.q(s, s)).nn""".stripMargin, + | val t: String = s.q(s, s).nn""".stripMargin, ctxx = ctxx ) From 3c1c0477af2b1123c409aa1bf4ff1fff22f6aea7 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Mon, 11 Aug 2025 15:13:51 +0200 Subject: [PATCH 012/111] Additional test for named tuple custom extractor with mismatched type [Cherry-picked f1f80c1671c0dfbbe8fe7ce84f5e139fa9bb1778] --- tests/neg/i23552.scala | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/neg/i23552.scala diff --git a/tests/neg/i23552.scala b/tests/neg/i23552.scala new file mode 100644 index 000000000000..ea3d0ff7f874 --- /dev/null +++ b/tests/neg/i23552.scala @@ -0,0 +1,32 @@ +import NamedTuple.AnyNamedTuple + +sealed trait Z +sealed trait S[n] + +type TupleList[+A, N] <: AnyNamedTuple = + N match + case Z => NamedTuple.Empty + case S[n] => (head: A, tail: TupleList[A, n]) + +sealed trait Vect[+A, N]: + def ::[A1 >: A](a: A1): Vect[A1, S[N]] = + Vect.Cons(a, this) + + def toTupleList: TupleList[A, N] + +object Vect: + case object Empty extends Vect[Nothing, Z]: + override def toTupleList: TupleList[Nothing, Z] = NamedTuple.Empty + + case class Cons[A, N](head: A, tail: Vect[A, N]) extends Vect[A, S[N]]: + override def toTupleList: TupleList[A, S[N]] = (head, tail.toTupleList) + +object Foo: + def unapply[A, N](as: Vect[A, N]): Some[TupleList[A, N]] = + Some(as.toTupleList) + +@main +def test: Unit = + (1 :: 2 :: 3 :: Vect.Empty) match + // missing parens around named tuple inside Foo causes compiler crash + case Foo(head = h, tail = t) => ??? // error \ No newline at end of file From 6b6a3e6eb7539ff976068785f51013d759c53f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Sat, 26 Jul 2025 16:42:27 +0200 Subject: [PATCH 013/111] Scaladoc support for capture checking and separation checking [Cherry-picked 8ddd0e1b00fe4859a807d62ec1a74ef37673db9d] --- scaladoc/src/dotty/tools/scaladoc/api.scala | 6 +- .../dotty/tools/scaladoc/cc/CaptureOps.scala | 191 ++++++++++++++++++ .../tools/scaladoc/tasty/BasicSupport.scala | 5 +- .../scaladoc/tasty/ClassLikeSupport.scala | 15 +- .../tools/scaladoc/tasty/NameNormalizer.scala | 6 +- .../tools/scaladoc/tasty/PackageSupport.scala | 7 + .../dotty/tools/scaladoc/tasty/SymOps.scala | 1 + .../tools/scaladoc/tasty/TastyParser.scala | 3 + .../tools/scaladoc/tasty/TypesSupport.scala | 176 ++++++++++++---- .../translators/ScalaSignatureProvider.scala | 2 +- .../translators/ScalaSignatureUtils.scala | 10 +- 11 files changed, 375 insertions(+), 47 deletions(-) create mode 100644 scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index b39fdf157347..41ccd8fb2280 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -44,6 +44,7 @@ enum Modifier(val name: String, val prefix: Boolean): case Transparent extends Modifier("transparent", true) case Infix extends Modifier("infix", true) case AbsOverride extends Modifier("abstract override", true) + case Update extends Modifier("update", true) case class ExtensionTarget(name: String, typeParams: Seq[TypeParameter], argsLists: Seq[TermParameterList], signature: Signature, dri: DRI, position: Long) case class ImplicitConversion(from: DRI, to: DRI) @@ -69,7 +70,7 @@ enum Kind(val name: String): case Var extends Kind("var") case Val extends Kind("val") case Exported(base: Kind) extends Kind("export") - case Type(concreate: Boolean, opaque: Boolean, typeParams: Seq[TypeParameter]) + case Type(concreate: Boolean, opaque: Boolean, typeParams: Seq[TypeParameter], isCaptureVar: Boolean = false) extends Kind("type") // should we handle opaque as modifier? case Given(kind: Def | Class | Val.type, as: Option[Signature], conversion: Option[ImplicitConversion]) extends Kind("given") with ImplicitConversionProvider @@ -120,7 +121,8 @@ case class TypeParameter( variance: "" | "+" | "-", name: String, dri: DRI, - signature: Signature + signature: Signature, + isCaptureVar: Boolean = false // under capture checking ) case class Link(name: String, dri: DRI) diff --git a/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala new file mode 100644 index 000000000000..bd55798d000c --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala @@ -0,0 +1,191 @@ +package dotty.tools.scaladoc + +package cc + +import scala.quoted._ + +object CaptureDefs: + // these should become part of the reflect API in the distant future + def retains(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.retains") + def retainsCap(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.retainsCap") + def retainsByName(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.retainsByName") + def CapsModule(using qctx: Quotes) = + qctx.reflect.Symbol.requiredPackage("scala.caps") + def captureRoot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredPackage("scala.caps.cap") + def Caps_Capability(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.Capability") + def Caps_CapSet(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.CapSet") + def Caps_Mutable(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.Mutable") + def Caps_SharedCapability(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.SharedCapability") + def UseAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.use") + def ConsumeAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.consume") + def ReachCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.internal.reachCapability") + def RootCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.internal.rootCapability") + def ReadOnlyCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.internal.readOnlyCapability") + def RequiresCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.internal.requiresCapability") + + def LanguageExperimental(using qctx: Quotes) = + qctx.reflect.Symbol.requiredPackage("scala.language.experimental") + + def ImpureFunction1(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.ImpureFunction1") + + def ImpureContextFunction1(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.ImpureContextFunction1") + + def Function1(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.Function1") + + def ContextFunction1(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.ContextFunction1") + + val useAnnotFullName: String = "scala.caps.use." + val consumeAnnotFullName: String = "scala.caps.consume." + val ccImportSelector = "captureChecking" +end CaptureDefs + +extension (using qctx: Quotes)(ann: qctx.reflect.Symbol) + /** This symbol is one of `retains` or `retainsCap` */ + def isRetains: Boolean = + ann == CaptureDefs.retains || ann == CaptureDefs.retainsCap + + /** This symbol is one of `retains`, `retainsCap`, or `retainsByName` */ + def isRetainsLike: Boolean = + ann.isRetains || ann == CaptureDefs.retainsByName + + def isReachCapabilityAnnot: Boolean = + ann == CaptureDefs.ReachCapabilityAnnot + + def isReadOnlyCapabilityAnnot: Boolean = + ann == CaptureDefs.ReadOnlyCapabilityAnnot +end extension + +extension (using qctx: Quotes)(tpe: qctx.reflect.TypeRepr) // FIXME clean up and have versions on Symbol for those + def isCaptureRoot: Boolean = + import qctx.reflect.* + tpe match + case TermRef(ThisType(TypeRef(NoPrefix(), "caps")), "cap") => true + case TermRef(TermRef(ThisType(TypeRef(NoPrefix(), "scala")), "caps"), "cap") => true + case TermRef(TermRef(TermRef(TermRef(NoPrefix(), "_root_"), "scala"), "caps"), "cap") => true + case _ => false + + // NOTE: There's something horribly broken with Symbols, and we can't rely on tests like .isContextFunctionType either, + // so we do these lame string comparisons instead. + def isImpureFunction1: Boolean = tpe.typeSymbol.fullName == "scala.ImpureFunction1" + + def isImpureContextFunction1: Boolean = tpe.typeSymbol.fullName == "scala.ImpureContextFunction1" + + def isFunction1: Boolean = tpe.typeSymbol.fullName == "scala.Function1" + + def isContextFunction1: Boolean = tpe.typeSymbol.fullName == "scala.ContextFunction1" + + def isAnyImpureFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.ImpureFunction") + + def isAnyImpureContextFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.ImpureContextFunction") + + def isAnyFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.Function") + + def isAnyContextFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.ContextFunction") + + def isCapSet: Boolean = tpe.typeSymbol == CaptureDefs.Caps_CapSet + + def isCapSetPure: Boolean = + tpe.isCapSet && tpe.match + case CapturingType(_, refs) => refs.isEmpty + case _ => true + + def isCapSetCap: Boolean = + tpe.isCapSet && tpe.match + case CapturingType(_, List(ref)) => ref.isCaptureRoot + case _ => false +end extension + +extension (using qctx: Quotes)(typedef: qctx.reflect.TypeDef) + def derivesFromCapSet: Boolean = + import qctx.reflect.* + typedef.rhs.match + case t: TypeTree => t.tpe.derivesFrom(CaptureDefs.Caps_CapSet) + case t: TypeBoundsTree => t.tpe.derivesFrom(CaptureDefs.Caps_CapSet) + case _ => false +end extension + +/** Matches `import scala.language.experimental.captureChecking` */ +object CCImport: + def unapply(using qctx: Quotes)(tree: qctx.reflect.Tree): Boolean = + import qctx.reflect._ + tree match + case imprt: Import if imprt.expr.tpe.termSymbol == CaptureDefs.LanguageExperimental => + imprt.selectors.exists { + case SimpleSelector(s) if s == CaptureDefs.ccImportSelector => true + case _ => false + } + case _ => false + end unapply +end CCImport + +object ReachCapability: + def unapply(using qctx: Quotes)(ty: qctx.reflect.TypeRepr): Option[qctx.reflect.TypeRepr] = + import qctx.reflect._ + ty match + case AnnotatedType(base, Apply(Select(New(annot), _), Nil)) if annot.symbol.isReachCapabilityAnnot => + Some(base) + case _ => None +end ReachCapability + +object ReadOnlyCapability: + def unapply(using qctx: Quotes)(ty: qctx.reflect.TypeRepr): Option[qctx.reflect.TypeRepr] = + import qctx.reflect._ + ty match + case AnnotatedType(base, Apply(Select(New(annot), _), Nil)) if annot.symbol.isReadOnlyCapabilityAnnot => + Some(base) + case _ => None +end ReadOnlyCapability + +/** Decompose capture sets in the union-type-encoding into the sequence of atomic `TypeRepr`s. + * Returns `None` if the type is not a capture set. +*/ +def decomposeCaptureRefs(using qctx: Quotes)(typ0: qctx.reflect.TypeRepr): Option[List[qctx.reflect.TypeRepr]] = + import qctx.reflect._ + val buffer = collection.mutable.ListBuffer.empty[TypeRepr] + def include(t: TypeRepr): Boolean = { buffer += t; true } + def traverse(typ: TypeRepr): Boolean = + typ match + case t if t.typeSymbol == defn.NothingClass => true + case OrType(t1, t2) => traverse(t1) && traverse(t2) + case t @ ThisType(_) => include(t) + case t @ TermRef(_, _) => include(t) + case t @ ParamRef(_, _) => include(t) + case t @ ReachCapability(_) => include(t) + case t @ ReadOnlyCapability(_) => include(t) + case t : TypeRef => include(t) // FIXME: does this need a more refined check? + case _ => report.warning(s"Unexpected type tree $typ while trying to extract capture references from $typ0"); false // TODO remove warning eventually + if traverse(typ0) then Some(buffer.toList) else None +end decomposeCaptureRefs + +object CaptureSetType: + def unapply(using qctx: Quotes)(tt: qctx.reflect.TypeTree): Option[List[qctx.reflect.TypeRepr]] = decomposeCaptureRefs(tt.tpe) +end CaptureSetType + +object CapturingType: + def unapply(using qctx: Quotes)(typ: qctx.reflect.TypeRepr): Option[(qctx.reflect.TypeRepr, List[qctx.reflect.TypeRepr])] = + import qctx.reflect._ + typ match + case AnnotatedType(base, Apply(TypeApply(Select(New(annot), _), List(CaptureSetType(refs))), Nil)) if annot.symbol.isRetainsLike => + Some((base, refs)) + case AnnotatedType(base, Apply(Select(New(annot), _), Nil)) if annot.symbol == CaptureDefs.retainsCap => + Some((base, List(CaptureDefs.captureRoot.termRef))) + case _ => None +end CapturingType diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala index 81415377beeb..81309018718c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala @@ -3,6 +3,7 @@ package tasty import scala.jdk.CollectionConverters._ import dotty.tools.scaladoc._ +import dotty.tools.scaladoc.cc.CaptureDefs import scala.quoted._ import SymOps._ @@ -52,7 +53,9 @@ trait BasicSupport: "scala.annotation.static", "scala.annotation.targetName", "scala.annotation.threadUnsafe", - "scala.annotation.varargs" + "scala.annotation.varargs", + CaptureDefs.useAnnotFullName, + CaptureDefs.consumeAnnotFullName, ) val documentedSymbol = summon[Quotes].reflect.Symbol.requiredClass("java.lang.annotation.Documented") val annotations = sym.annotations.filter { a => diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 99aac7010d8b..00635951fb69 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -3,6 +3,8 @@ package dotty.tools.scaladoc.tasty import dotty.tools.scaladoc._ import dotty.tools.scaladoc.{Signature => DSignature} +import dotty.tools.scaladoc.cc.* + import scala.quoted._ import SymOps._ @@ -465,6 +467,8 @@ trait ClassLikeSupport: else "" val name = symbol.normalizedName + val isCaptureVar = ccEnabled && argument.derivesFromCapSet + val normalizedName = if name.matches("_\\$\\d*") then "_" else name val boundsSignature = argument.rhs.asSignature(classDef, symbol.owner) val signature = boundsSignature ++ contextBounds.flatMap(tr => @@ -479,7 +483,8 @@ trait ClassLikeSupport: variancePrefix, normalizedName, symbol.dri, - signature + signature, + isCaptureVar, ) def parseTypeDef(typeDef: TypeDef, classDef: ClassDef): Member = @@ -489,6 +494,9 @@ trait ClassLikeSupport: case LambdaTypeTree(params, body) => isTreeAbstract(body) case _ => false } + + val isCaptureVar = ccEnabled && typeDef.derivesFromCapSet + val (generics, tpeTree) = typeDef.rhs match case LambdaTypeTree(params, body) => (params.map(mkTypeArgument(_, classDef)), body) case tpe => (Nil, tpe) @@ -528,7 +536,10 @@ trait ClassLikeSupport: case _ => symbol.getExtraModifiers() mkMember(symbol, kind, sig)( - modifiers = modifiers, + // Due to how capture checking encodes update methods (recycling the mutable flag for methods), + // we need to filter out the update modifier here. Otherwise, mutable fields will + // be documented as having the update modifier, which is not correct. + modifiers = modifiers.filterNot(_ == Modifier.Update), deprecated = symbol.isDeprecated(), experimental = symbol.isExperimental() ) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala index 196c3e056b36..8f42a28c2c35 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala @@ -17,7 +17,7 @@ object NameNormalizer { val escaped = escapedName(constructorNormalizedName) escaped } - + def ownerNameChain: List[String] = { import reflect.* if s.isNoSymbol then List.empty @@ -25,8 +25,8 @@ object NameNormalizer { else if s == defn.RootPackage then List.empty else if s == defn.RootClass then List.empty else s.owner.ownerNameChain :+ s.normalizedName - } - + } + def normalizedFullName: String = s.ownerNameChain.mkString(".") diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala index c0308336a2bf..8de2ab6b8539 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala @@ -5,6 +5,8 @@ import scala.jdk.CollectionConverters._ import SymOps._ +import dotty.tools.scaladoc.cc.CCImport + trait PackageSupport: self: TastyParser => import qctx.reflect._ @@ -13,6 +15,11 @@ trait PackageSupport: def parsePackage(pck: PackageClause): (String, Member) = val name = pck.symbol.fullName + ccFlag = false // FIXME: would be better if we had access to the tasty attribute + pck.stats.foreach { + case CCImport() => ccFlag = true + case _ => + } (name, Member(name, "", pck.symbol.dri, Kind.Package)) def parsePackageObject(pckObj: ClassDef): (String, Member) = diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala index 969b1d6462c2..0464da450f05 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala @@ -100,6 +100,7 @@ object SymOps: Flags.Case -> Modifier.Case, Flags.Opaque -> Modifier.Opaque, Flags.AbsOverride -> Modifier.AbsOverride, + Flags.Mutable -> Modifier.Update, // under CC ).collect { case (flag, mod) if sym.flags.is(flag) => mod } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala index 1a8337e0c6b7..741147ebfe2e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala @@ -187,6 +187,9 @@ case class TastyParser( private given qctx.type = qctx + protected var ccFlag: Boolean = false + def ccEnabled: Boolean = ccFlag + val intrinsicClassDefs = Set( defn.AnyClass, defn.MatchableClass, diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 24473c874c96..c45d4268b144 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -6,8 +6,10 @@ import scala.jdk.CollectionConverters.* import scala.quoted.* import scala.util.control.NonFatal -import NameNormalizer.* -import SyntheticsSupport.* +import dotty.tools.scaladoc.cc.* + +import NameNormalizer._ +import SyntheticsSupport._ trait TypesSupport: self: TastyParser => @@ -19,7 +21,7 @@ trait TypesSupport: def asSignature(elideThis: reflect.ClassDef, originalOwner: reflect.Symbol, skipThisTypePrefix: Boolean): SSignature = import reflect._ tpeTree match - case TypeBoundsTree(low, high) => typeBoundsTreeOfHigherKindedType(low.tpe, high.tpe, skipThisTypePrefix)(using elideThis, originalOwner) + case TypeBoundsTree(low, high) => typeBoundsTreeOfHigherKindedType(low.tpe, high.tpe, skipThisTypePrefix)(using elideThis, originalOwner, inCC = None) case tpeTree: TypeTree => topLevelProcess(tpeTree.tpe, skipThisTypePrefix)(using elideThis, originalOwner) case term: Term => topLevelProcess(term.tpe, skipThisTypePrefix)(using elideThis, originalOwner) def asSignature(elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = @@ -37,19 +39,30 @@ trait TypesSupport: private def keyword(str: String): SignaturePart = Keyword(str) - private def tpe(str: String, dri: DRI): SignaturePart = dotty.tools.scaladoc.Type(str, Some(dri)) + private def tpe(str: String, dri: DRI)(using inCC: Option[Any]): SignaturePart = + if inCC.isDefined then + dotty.tools.scaladoc.Plain(str) + else + dotty.tools.scaladoc.Type(str, Some(dri)) - private def tpe(str: String): SignaturePart = dotty.tools.scaladoc.Type(str, None) + private def tpe(str: String)(using inCC: Option[Any]): SignaturePart = + if inCC.isDefined then + dotty.tools.scaladoc.Plain(str) + else + dotty.tools.scaladoc.Type(str, None) protected def inParens(s: SSignature, wrap: Boolean = true) = if wrap then plain("(").l ++ s ++ plain(")").l else s extension (on: SignaturePart) def l: List[SignaturePart] = List(on) - private def tpe(using Quotes)(symbol: reflect.Symbol): SSignature = + private def tpe(using Quotes)(symbol: reflect.Symbol)(using inCC: Option[Any]): SSignature = import SymOps._ val dri: Option[DRI] = Option(symbol).filterNot(_.isHiddenByVisibility).map(_.dri) - dotty.tools.scaladoc.Type(symbol.normalizedName, dri).l + if inCC.isDefined then // we are in the context of a capture set and want paths to be rendered plainly + dotty.tools.scaladoc.Plain(symbol.normalizedName).l + else + dotty.tools.scaladoc.Type(symbol.normalizedName, dri).l private def commas(lists: List[SSignature]) = lists match case List(single) => single @@ -82,7 +95,7 @@ trait TypesSupport: // TODO #23 add support for all types signatures that make sense private def inner( - using Quotes, + using qctx: Quotes, )( tp: reflect.TypeRepr, skipThisTypePrefix: Boolean @@ -91,6 +104,8 @@ trait TypesSupport: originalOwner: reflect.Symbol, indent: Int = 0, skipTypeSuffix: Boolean = false, + // inCC means in capture-checking context. If defined, it carries the current capture-set contents. + inCC: Option[List[reflect.TypeRepr]] = None, ): SSignature = import reflect._ def noSupported(name: String): SSignature = @@ -105,7 +120,10 @@ trait TypesSupport: inParens(inner(left, skipThisTypePrefix), shouldWrapInParens(left, tp, true)) ++ keyword(" & ").l ++ inParens(inner(right, skipThisTypePrefix), shouldWrapInParens(right, tp, false)) - case ByNameType(tpe) => keyword("=> ") :: inner(tpe, skipThisTypePrefix) + case ByNameType(CapturingType(tpe, refs)) => + emitByNameArrow(using qctx)(Some(refs), skipThisTypePrefix) ++ (plain(" ") :: inner(tpe, skipThisTypePrefix)) + case ByNameType(tpe) => + emitByNameArrow(using qctx)(None, skipThisTypePrefix) ++ (plain(" ") :: inner(tpe, skipThisTypePrefix)) case ConstantType(constant) => plain(constant.show).l case ThisType(tpe) => @@ -116,6 +134,14 @@ trait TypesSupport: inner(tpe, skipThisTypePrefix) :+ plain("*") case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) => inner(tpe, skipThisTypePrefix) :+ plain("*") + case CapturingType(base, refs) => + base match + case t @ AppliedType(base, args) if t.isFunctionType => + functionType(base, args, skipThisTypePrefix)(using inCC = Some(refs)) + case t : Refinement if t.isFunctionType => + inner(base, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = skipTypeSuffix, inCC = Some(refs)) + case t if t.isCapSet => emitCaptureSet(refs, skipThisTypePrefix, omitCap = false) + case t => inner(base, skipThisTypePrefix) ++ emitCapturing(refs, skipThisTypePrefix) case AnnotatedType(tpe, _) => inner(tpe, skipThisTypePrefix) case tl @ TypeLambda(params, paramBounds, AppliedType(tpe, args)) @@ -127,7 +153,8 @@ trait TypesSupport: case tl @ TypeLambda(params, paramBounds, resType) => plain("[").l ++ commas(params.zip(paramBounds).map { (name, typ) => val normalizedName = if name.matches("_\\$\\d*") then "_" else name - tpe(normalizedName).l ++ inner(typ, skipThisTypePrefix) + val suffix = if ccEnabled && typ.derivesFrom(CaptureDefs.Caps_CapSet) then List(Keyword("^")) else Nil + tpe(normalizedName).l ++ suffix ++ inner(typ, skipThisTypePrefix) }) ++ plain("]").l ++ keyword(" =>> ").l ++ inner(resType, skipThisTypePrefix) @@ -139,14 +166,19 @@ trait TypesSupport: inner(Refinement(at, "apply", mt), skipThisTypePrefix) case r: Refinement => { //(parent, name, info) + val inCC0 = inCC + given Option[List[TypeRepr]] = None // do not propagate capture set beyond this point def getRefinementInformation(t: TypeRepr): List[TypeRepr] = t match { case r: Refinement => getRefinementInformation(r.parent) :+ r case t => List(t) } def getParamBounds(t: PolyType): SSignature = commas( - t.paramNames.zip(t.paramBounds.map(inner(_, skipThisTypePrefix))) - .map(b => tpe(b(0)).l ++ b(1)) + t.paramNames.zip(t.paramBounds.map(inner(_, skipThisTypePrefix))).zipWithIndex + .map { case ((name, bound), idx) => + val suffix = if ccEnabled && t.param(idx).derivesFrom(CaptureDefs.Caps_CapSet) then List(Keyword("^")) else Nil + tpe(name).l ++ suffix ++ bound + } ) def getParamList(m: MethodType): SSignature = @@ -189,9 +221,16 @@ trait TypesSupport: val isCtx = isContextualMethod(m) if isDependentMethod(m) then val paramList = getParamList(m) - val arrow = keyword(if isCtx then " ?=> " else " => ").l + val arrPrefix = if isCtx then "?" else "" + val arrow = + if ccEnabled then + inCC0 match + case None | Some(Nil) => keyword(arrPrefix + "->").l + case Some(List(c)) if c.isCaptureRoot => keyword(arrPrefix + "=>").l + case Some(refs) => keyword(arrPrefix + "->") :: emitCaptureSet(refs, skipThisTypePrefix) + else keyword(arrPrefix + "=>").l val resType = inner(m.resType, skipThisTypePrefix) - paramList ++ arrow ++ resType + paramList ++ (plain(" ") :: arrow) ++ (plain(" ") :: resType) else val sym = defn.FunctionClass(m.paramTypes.length, isCtx) inner(sym.typeRef.appliedTo(m.paramTypes :+ m.resType), skipThisTypePrefix) @@ -234,18 +273,7 @@ trait TypesSupport: ++ inParens(inner(rhs, skipThisTypePrefix), shouldWrapInParens(rhs, t, false)) case t @ AppliedType(tpe, args) if t.isFunctionType => - val arrow = if t.isContextFunctionType then " ?=> " else " => " - args match - case Nil => Nil - case List(rtpe) => plain("()").l ++ keyword(arrow).l ++ inner(rtpe, skipThisTypePrefix) - case List(arg, rtpe) => - val wrapInParens = stripAnnotated(arg) match - case _: TermRef | _: TypeRef | _: ConstantType | _: ParamRef => false - case at: AppliedType if !isInfix(at) && !at.isFunctionType && !at.isTupleN => false - case _ => true - inParens(inner(arg, skipThisTypePrefix), wrapInParens) ++ keyword(arrow).l ++ inner(rtpe, skipThisTypePrefix) - case _ => - plain("(").l ++ commas(args.init.map(inner(_, skipThisTypePrefix))) ++ plain(")").l ++ keyword(arrow).l ++ inner(args.last, skipThisTypePrefix) + functionType(tpe, args, skipThisTypePrefix) case t @ AppliedType(tpe, typeList) => inner(tpe, skipThisTypePrefix) ++ plain("[").l ++ commas(typeList.map { t => t match @@ -253,6 +281,8 @@ trait TypesSupport: case _ => topLevelProcess(t, skipThisTypePrefix) }) ++ plain("]").l + case t : TypeRef if t.isCapSet => emitCaptureSet(Nil, skipThisTypePrefix) + case tp @ TypeRef(qual, typeName) => inline def wrapping = shouldWrapInParens(inner = qual, outer = tp, isLeft = true) qual match { @@ -272,7 +302,7 @@ trait TypesSupport: tpe(tp.typeSymbol) else val sig = inParens( - inner(qual, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true), wrapping) + inner(qual, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true, inCC = inCC), wrapping) sig ++ plain(".").l ++ tpe(tp.typeSymbol) @@ -281,7 +311,7 @@ trait TypesSupport: tpe(tp.typeSymbol) case _: TermRef | _: ParamRef => val suffix = if tp.typeSymbol == Symbol.noSymbol then tpe(typeName).l else tpe(tp.typeSymbol) - inner(qual, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true) + inner(qual, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true, inCC = inCC) ++ plain(".").l ++ suffix case _ => @@ -292,7 +322,7 @@ trait TypesSupport: case tr @ TermRef(qual, typeName) => val prefix = qual match case t if skipPrefix(t, elideThis, originalOwner, skipThisTypePrefix) => Nil - case tp => inner(tp, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true) ++ plain(".").l + case tp => inner(tp, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true, inCC = inCC) ++ plain(".").l val suffix = if skipTypeSuffix then Nil else List(plain("."), keyword("type")) val typeSig = tr.termSymbol.tree match case vd: ValDef if tr.termSymbol.flags.is(Flags.Module) => @@ -314,13 +344,13 @@ trait TypesSupport: keyword(caseSpaces + "case ").l ++ inner(from, skipThisTypePrefix) ++ keyword(" => ").l - ++ inner(to, skipThisTypePrefix)(using indent = indent + 2, skipTypeSuffix = skipTypeSuffix) + ++ inner(to, skipThisTypePrefix)(using indent = indent + 2, skipTypeSuffix = skipTypeSuffix, inCC = inCC) ++ plain("\n").l case TypeLambda(_, _, MatchCase(from, to)) => keyword(caseSpaces + "case ").l ++ inner(from, skipThisTypePrefix) ++ keyword(" => ").l - ++ inner(to, skipThisTypePrefix)(using indent = indent + 2, skipTypeSuffix = skipTypeSuffix) + ++ inner(to, skipThisTypePrefix)(using indent = indent + 2, skipTypeSuffix = skipTypeSuffix, inCC = inCC) ++ plain("\n").l } inner(sc, skipThisTypePrefix) ++ keyword(" match ").l ++ plain("{\n").l ++ casesTexts ++ plain(spaces + "}").l @@ -347,9 +377,34 @@ trait TypesSupport: s"${tpe.show(using Printer.TypeReprStructure)}" throw MatchError(msg) + private def functionType(using qctx: Quotes)(funTy: reflect.TypeRepr, args: List[reflect.TypeRepr], skipThisTypePrefix: Boolean)(using + elideThis: reflect.ClassDef, + originalOwner: reflect.Symbol, + indent: Int, + skipTypeSuffix: Boolean, + inCC: Option[List[reflect.TypeRepr]], + ): SSignature = + import reflect._ + val arrow = plain(" ") :: (emitFunctionArrow(using qctx)(funTy, inCC, skipThisTypePrefix) ++ plain(" ").l) + given Option[List[TypeRepr]] = None // do not propagate capture set beyond this point + args match + case Nil => Nil + case List(rtpe) => plain("()").l ++ arrow ++ inner(rtpe, skipThisTypePrefix) + case List(arg, rtpe) => + val wrapInParens = stripAnnotated(arg) match + case _: TermRef | _: TypeRef | _: ConstantType | _: ParamRef => false + case at: AppliedType if !isInfix(at) && !at.isFunctionType && !at.isTupleN => false + case _ => true + inParens(inner(arg, skipThisTypePrefix), wrapInParens) ++ arrow ++ inner(rtpe, skipThisTypePrefix) + case _ => + plain("(").l ++ commas(args.init.map(inner(_, skipThisTypePrefix))) ++ plain(")").l ++ arrow ++ inner(args.last, skipThisTypePrefix) + private def typeBound(using Quotes)(t: reflect.TypeRepr, low: Boolean, skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol) = import reflect._ - val ignore = if (low) t.typeSymbol == defn.NothingClass else t.typeSymbol == defn.AnyClass + val ignore = low && (ccEnabled && t.isCapSetPure + || t.typeSymbol == defn.NothingClass) + || !low && (ccEnabled && t.isCapSetCap + || t.typeSymbol == defn.AnyClass) val prefix = keyword(if low then " >: " else " <: ") t match { case l: TypeLambda => prefix :: inParens(inner(l, skipThisTypePrefix)(using elideThis, originalOwner)) @@ -359,18 +414,18 @@ trait TypesSupport: } private def typeBoundsTreeOfHigherKindedType(using Quotes)(low: reflect.TypeRepr, high: reflect.TypeRepr, skipThisTypePrefix: Boolean)( - using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol + using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol, inCC: Option[List[reflect.TypeRepr]] ) = import reflect._ def regularTypeBounds(low: TypeRepr, high: TypeRepr) = - if low == high then keyword(" = ").l ++ inner(low, skipThisTypePrefix)(using elideThis, originalOwner) + if low == high then keyword(" = ").l ++ inner(low, skipThisTypePrefix)(using elideThis, originalOwner, inCC = inCC) else typeBound(low, low = true, skipThisTypePrefix)(using elideThis, originalOwner) ++ typeBound(high, low = false, skipThisTypePrefix)(using elideThis, originalOwner) high.match case TypeLambda(params, paramBounds, resType) => if resType.typeSymbol == defn.AnyClass then plain("[").l ++ commas(params.zip(paramBounds).map { (name, typ) => val normalizedName = if name.matches("_\\$\\d*") then "_" else name - tpe(normalizedName).l ++ inner(typ, skipThisTypePrefix)(using elideThis, originalOwner) + tpe(normalizedName)(using inCC).l ++ inner(typ, skipThisTypePrefix)(using elideThis, originalOwner, inCC = inCC) }) ++ plain("]").l else regularTypeBounds(low, high) @@ -452,3 +507,54 @@ trait TypesSupport: tr match case AnnotatedType(tr, _) => stripAnnotated(tr) case other => other + + private def emitCapability(using Quotes)(ref: reflect.TypeRepr, skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + import reflect._ + ref match + case ReachCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword("*") + case ReadOnlyCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword(".rd") + case ThisType(_) => List(Keyword("this")) + case t => inner(t, skipThisTypePrefix)(using skipTypeSuffix = true, inCC = Some(Nil)) + + private def emitCaptureSet(using Quotes)(refs: List[reflect.TypeRepr], skipThisTypePrefix: Boolean, omitCap: Boolean = true)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + import reflect._ + refs match + case List(ref) if omitCap && ref.isCaptureRoot => Nil + case refs => + val res0 = refs.map(x => emitCapability(x, skipThisTypePrefix)) + val res1 = res0 match + case Nil => Nil + case other => other.reduce((r, e) => r ++ (List(Plain(", ")) ++ e)) + Plain("{") :: (res1 ++ List(Plain("}"))) + + private def emitCapturing(using Quotes)(refs: List[reflect.TypeRepr], skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + import reflect._ + Keyword("^") :: emitCaptureSet(refs, skipThisTypePrefix) + + private def emitFunctionArrow(using Quotes)(funTy: reflect.TypeRepr, captures: Option[List[reflect.TypeRepr]], skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + import reflect._ + val isContextFun = funTy.isAnyContextFunction || funTy.isAnyImpureContextFunction + val prefix = if isContextFun then "?" else "" + if !ccEnabled then + List(Keyword(prefix + "=>")) + else + val isPureFun = funTy.isAnyFunction || funTy.isAnyContextFunction + val isImpureFun = funTy.isAnyImpureFunction || funTy.isAnyImpureContextFunction + captures match + case None => // means an explicit retains* annotation is missing + if isPureFun then + List(Keyword(prefix + "->")) + else if isImpureFun then + List(Keyword(prefix + "=>")) + else + report.error(s"Cannot emit function arrow: expected a (Context)Function* or Impure(Context)Function*, but got: ${funTy.show}") + Nil + case Some(refs) => + // there is some capture set + refs match + case Nil => List(Keyword(prefix + "->")) + case List(ref) if ref.isCaptureRoot => List(Keyword(prefix + "=>")) + case refs => Keyword(prefix + "->") :: emitCaptureSet(refs, skipThisTypePrefix) + + private def emitByNameArrow(using Quotes)(captures: Option[List[reflect.TypeRepr]], skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + emitFunctionArrow(CaptureDefs.Function1.typeRef, captures, skipThisTypePrefix) \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala index a3ce15d70c64..d62ce4693575 100644 --- a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala +++ b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala @@ -149,7 +149,7 @@ class ScalaSignatureProvider: MemberSignature( builder.modifiersAndVisibility(typeDef), builder.kind(tpe), - builder.name(typeDef.name, typeDef.dri), + builder.name(typeDef.name, typeDef.dri, isCaptureVar = tpe.isCaptureVar), builder.typeParamList(tpe.typeParams).pipe { bdr => if (!tpe.opaque) { (if tpe.concreate then bdr.plain(" = ") else bdr) diff --git a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala index d28dd6ca18fe..7b3f2fa44acf 100644 --- a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala +++ b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala @@ -3,8 +3,12 @@ package translators case class SignatureBuilder(content: Signature = Nil) extends ScalaSignatureUtils: def plain(str: String): SignatureBuilder = copy(content = content :+ Plain(str)) - def name(str: String, dri: DRI): SignatureBuilder = copy(content = content :+ Name(str, dri)) - def tpe(text: String, dri: Option[DRI]): SignatureBuilder = copy(content = content :+ Type(text, dri)) + def name(str: String, dri: DRI, isCaptureVar: Boolean = false/*under CC*/): SignatureBuilder = + val suffix = if isCaptureVar then List(Keyword("^")) else Nil + copy(content = content ++ (Name(str, dri) :: suffix)) + def tpe(text: String, dri: Option[DRI], isCaptureVar: Boolean = false/*under CC*/): SignatureBuilder = + val suffix = if isCaptureVar then List(Keyword("^")) else Nil + copy(content = content ++ (Type(text, dri) :: suffix)) def keyword(str: String): SignatureBuilder = copy(content = content :+ Keyword(str)) def tpe(text: String, dri: DRI): SignatureBuilder = copy(content = content :+ Type(text, Some(dri))) def signature(s: Signature): SignatureBuilder = copy(content = content ++ s) @@ -90,7 +94,7 @@ case class SignatureBuilder(content: Signature = Nil) extends ScalaSignatureUtil } def typeParamList(on: TypeParameterList) = list(on.toList, List(Plain("[")), List(Plain("]"))){ (bdr, e) => - bdr.annotationsInline(e).keyword(e.variance).tpe(e.name, Some(e.dri)).signature(e.signature) + bdr.annotationsInline(e).keyword(e.variance).tpe(e.name, Some(e.dri), e.isCaptureVar).signature(e.signature) } def functionTermParameters(paramss: Seq[TermParameterList]) = From 087ffae806cec0395456fcbf20d0c043b3b2f5f5 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 4 Aug 2025 18:12:50 +0200 Subject: [PATCH 014/111] isPureClass for checking whether a TypeRepr is pure from a given context [Cherry-picked d716d90c2801c3ac46a9ce42317a0cbac190e0fa] --- .../dotty/tools/scaladoc/cc/CaptureOps.scala | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala index bd55798d000c..7e8f9e8bf6a2 100644 --- a/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala @@ -111,6 +111,23 @@ extension (using qctx: Quotes)(tpe: qctx.reflect.TypeRepr) // FIXME clean up and tpe.isCapSet && tpe.match case CapturingType(_, List(ref)) => ref.isCaptureRoot case _ => false + + def isPureClass(from: qctx.reflect.ClassDef): Boolean = + import qctx.reflect._ + def check(sym: Tree): Boolean = sym match + case ClassDef(name, _, _, Some(ValDef(_, tt, _)), _) => tt.tpe match + case CapturingType(_, refs) => refs.isEmpty + case _ => true + case _ => false + + // Horrible hack to basically grab tpe1.asSeenFrom(from) + val tpe1 = from.symbol.typeRef.select(tpe.typeSymbol).simplified + val tpe2 = tpe1.classSymbol.map(_.typeRef).getOrElse(tpe1) + + // println(s"${tpe.show} -> (${tpe.typeSymbol} from ${from.symbol}) ${tpe1.show} -> ${tpe2} -> ${tpe2.baseClasses.filter(_.isClassDef)}") + val res = tpe2.baseClasses.exists(c => c.isClassDef && check(c.tree)) + // println(s"${tpe.show} is pure class = $res") + res end extension extension (using qctx: Quotes)(typedef: qctx.reflect.TypeDef) From bbcb4e67dcf60eb85c4f45f0ec492ea7670cd07d Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 4 Aug 2025 18:13:22 +0200 Subject: [PATCH 015/111] Drop captures from the capture set if the ref is pure, or the shape type is pure [Cherry-picked ef2aa9de811400b03533b7e738ed00372ca32ca0] --- .../dotty/tools/scaladoc/tasty/TypesSupport.scala | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index c45d4268b144..4105a39ac2a1 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -141,6 +141,7 @@ trait TypesSupport: case t : Refinement if t.isFunctionType => inner(base, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = skipTypeSuffix, inCC = Some(refs)) case t if t.isCapSet => emitCaptureSet(refs, skipThisTypePrefix, omitCap = false) + case t if t.isPureClass(elideThis) => inner(base, skipThisTypePrefix) case t => inner(base, skipThisTypePrefix) ++ emitCapturing(refs, skipThisTypePrefix) case AnnotatedType(tpe, _) => inner(tpe, skipThisTypePrefix) @@ -527,9 +528,19 @@ trait TypesSupport: case other => other.reduce((r, e) => r ++ (List(Plain(", ")) ++ e)) Plain("{") :: (res1 ++ List(Plain("}"))) + // Within the context of `elideThis`, some capabilities can actually be pure. + private def isCapturedInContext(using Quotes)(ref: reflect.TypeRepr)(using elideThis: reflect.ClassDef): Boolean = + import reflect._ + ref match + case ReachCapability(c) => isCapturedInContext(c) + case ReadOnlyCapability(c) => isCapturedInContext(c) + case ThisType(tr) => !elideThis.symbol.typeRef.isPureClass(elideThis) /* is the current class pure? */ + case t => !t.isPureClass(elideThis) + private def emitCapturing(using Quotes)(refs: List[reflect.TypeRepr], skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = import reflect._ - Keyword("^") :: emitCaptureSet(refs, skipThisTypePrefix) + val refs0 = refs.filter(isCapturedInContext) + if refs0.isEmpty then Nil else Keyword("^") :: emitCaptureSet(refs0, skipThisTypePrefix) private def emitFunctionArrow(using Quotes)(funTy: reflect.TypeRepr, captures: Option[List[reflect.TypeRepr]], skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = import reflect._ @@ -557,4 +568,4 @@ trait TypesSupport: case refs => Keyword(prefix + "->") :: emitCaptureSet(refs, skipThisTypePrefix) private def emitByNameArrow(using Quotes)(captures: Option[List[reflect.TypeRepr]], skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = - emitFunctionArrow(CaptureDefs.Function1.typeRef, captures, skipThisTypePrefix) \ No newline at end of file + emitFunctionArrow(CaptureDefs.Function1.typeRef, captures, skipThisTypePrefix) From 8b4220eba0986e3efc648a6f04d19eb8137e7a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Mon, 11 Aug 2025 13:08:24 +0200 Subject: [PATCH 016/111] Render classifier capabilities in scaladoc [Cherry-picked b74408192e55c960ea360d54615aa6d2afdc3111] --- .../dotty/tools/scaladoc/cc/CaptureOps.scala | 21 +++++++++++++++++-- .../tools/scaladoc/tasty/TypesSupport.scala | 10 +++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala index 7e8f9e8bf6a2..626162db55ec 100644 --- a/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala @@ -36,6 +36,8 @@ object CaptureDefs: qctx.reflect.Symbol.requiredClass("scala.annotation.internal.readOnlyCapability") def RequiresCapabilityAnnot(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass("scala.annotation.internal.requiresCapability") + def OnlyCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.internal.onlyCapability") def LanguageExperimental(using qctx: Quotes) = qctx.reflect.Symbol.requiredPackage("scala.language.experimental") @@ -71,6 +73,9 @@ extension (using qctx: Quotes)(ann: qctx.reflect.Symbol) def isReadOnlyCapabilityAnnot: Boolean = ann == CaptureDefs.ReadOnlyCapabilityAnnot + + def isOnlyCapabilityAnnot: Boolean = + ann == CaptureDefs.OnlyCapabilityAnnot end extension extension (using qctx: Quotes)(tpe: qctx.reflect.TypeRepr) // FIXME clean up and have versions on Symbol for those @@ -171,6 +176,17 @@ object ReadOnlyCapability: case _ => None end ReadOnlyCapability +object OnlyCapability: + def unapply(using qctx: Quotes)(ty: qctx.reflect.TypeRepr): Option[(qctx.reflect.TypeRepr, qctx.reflect.Symbol)] = + import qctx.reflect._ + ty match + case AnnotatedType(base, app @ Apply(TypeApply(Select(New(annot), _), _), Nil)) if annot.tpe.typeSymbol.isOnlyCapabilityAnnot => + app.tpe.typeArgs.head.classSymbol.match + case Some(clazzsym) => Some((base, clazzsym)) + case None => None + case _ => None +end OnlyCapability + /** Decompose capture sets in the union-type-encoding into the sequence of atomic `TypeRepr`s. * Returns `None` if the type is not a capture set. */ @@ -187,8 +203,9 @@ def decomposeCaptureRefs(using qctx: Quotes)(typ0: qctx.reflect.TypeRepr): Optio case t @ ParamRef(_, _) => include(t) case t @ ReachCapability(_) => include(t) case t @ ReadOnlyCapability(_) => include(t) - case t : TypeRef => include(t) // FIXME: does this need a more refined check? - case _ => report.warning(s"Unexpected type tree $typ while trying to extract capture references from $typ0"); false // TODO remove warning eventually + case t @ OnlyCapability(_, _) => include(t) + case t : TypeRef => include(t) + case _ => report.warning(s"Unexpected type tree $typ while trying to extract capture references from $typ0"); false if traverse(typ0) then Some(buffer.toList) else None end decomposeCaptureRefs diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 4105a39ac2a1..e929bb75f760 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -10,6 +10,7 @@ import dotty.tools.scaladoc.cc.* import NameNormalizer._ import SyntheticsSupport._ +import java.awt.RenderingHints.Key trait TypesSupport: self: TastyParser => @@ -512,10 +513,11 @@ trait TypesSupport: private def emitCapability(using Quotes)(ref: reflect.TypeRepr, skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = import reflect._ ref match - case ReachCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword("*") - case ReadOnlyCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword(".rd") - case ThisType(_) => List(Keyword("this")) - case t => inner(t, skipThisTypePrefix)(using skipTypeSuffix = true, inCC = Some(Nil)) + case ReachCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword("*") + case ReadOnlyCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword(".rd") + case OnlyCapability(c, cls) => emitCapability(c, skipThisTypePrefix) ++ List(Plain("."), Keyword("only"), Plain("[")) ++ inner(cls.typeRef, skipThisTypePrefix) :+ Plain("]") + case ThisType(_) => List(Keyword("this")) + case t => inner(t, skipThisTypePrefix)(using skipTypeSuffix = true, inCC = Some(Nil)) private def emitCaptureSet(using Quotes)(refs: List[reflect.TypeRepr], skipThisTypePrefix: Boolean, omitCap: Boolean = true)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = import reflect._ From b89cf4f441dea3429e66502180d140b51407f6cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Mon, 11 Aug 2025 19:03:44 +0200 Subject: [PATCH 017/111] Scaladoc: Option to fully disable capture checking [Cherry-picked 4ba1571589238402bee06a39574f034cee2e5622] --- project/ScaladocGeneration.scala | 4 ++++ scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala | 2 ++ .../src/dotty/tools/scaladoc/ScaladocSettings.scala | 5 ++++- .../tools/scaladoc/renderers/MemberRenderer.scala | 3 ++- .../dotty/tools/scaladoc/renderers/Resources.scala | 2 +- .../src/dotty/tools/scaladoc/tasty/BasicSupport.scala | 8 +++++--- .../dotty/tools/scaladoc/tasty/ClassLikeSupport.scala | 9 +++++++++ .../dotty/tools/scaladoc/tasty/PackageSupport.scala | 2 +- scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala | 1 - .../src/dotty/tools/scaladoc/tasty/TastyParser.scala | 2 +- .../src/dotty/tools/scaladoc/tasty/TypesSupport.scala | 11 +++++------ .../scaladoc/translators/ScalaSignatureProvider.scala | 10 +++++----- 12 files changed, 39 insertions(+), 20 deletions(-) diff --git a/project/ScaladocGeneration.scala b/project/ScaladocGeneration.scala index aac9f187a888..c06122f5fadf 100644 --- a/project/ScaladocGeneration.scala +++ b/project/ScaladocGeneration.scala @@ -141,6 +141,10 @@ object ScaladocGeneration { def key: String = "-dynamic-side-menu" } + case class SuppressCC(value: Boolean) extends Arg[Boolean] { + def key: String = "-suppressCC" + } + import _root_.scala.reflect._ trait GenerationConfig { diff --git a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala index a2485085a927..50e7589d92fe 100644 --- a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala +++ b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala @@ -47,6 +47,7 @@ object Scaladoc: defaultTemplate: Option[String] = None, quickLinks: List[QuickLink] = List.empty, dynamicSideMenu: Boolean = false, + suppressCC: Boolean = false, // suppress rendering anything related to experimental capture checking ) def run(args: Array[String], rootContext: CompilerContext): Reporter = @@ -231,6 +232,7 @@ object Scaladoc: defaultTemplate.nonDefault, quickLinksParsed, dynamicSideMenu.get, + suppressCC.get, ) (Some(docArgs), newContext) } diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala index 5acfac03d52c..8189d4f45286 100644 --- a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala +++ b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala @@ -144,5 +144,8 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings: val dynamicSideMenu: Setting[Boolean] = BooleanSetting(RootSetting, "dynamic-side-menu", "Generate side menu via JS instead of embedding it in every html file", false) + val suppressCC: Setting[Boolean] = + BooleanSetting(RootSetting, "suppressCC", "Suppress rendering anything related to experimental capture checking", false) + def scaladocSpecificSettings: Set[Setting[?]] = - Set(sourceLinks, legacySourceLink, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, generateInkuire, defaultTemplate, scastieConfiguration, quickLinks, dynamicSideMenu) + Set(sourceLinks, legacySourceLink, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, generateInkuire, defaultTemplate, scastieConfiguration, quickLinks, dynamicSideMenu, suppressCC) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala index af39870d87b5..119ec4fca3f8 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala @@ -177,7 +177,8 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext cls := s"documentableName $depStyle", ) - val signature: MemberSignature = signatureProvider.rawSignature(member)() + val ctx = summon[DocContext] + val signature: MemberSignature = signatureProvider.rawSignature(member)(!ctx.args.suppressCC)() val isSubtype = signature.suffix.exists { case Keyword(keyword) => keyword.contains("extends") case _ => false diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala index 3e49af2e0576..af30c3479d81 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala @@ -213,7 +213,7 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: val (res, pageName) = page.content match case m: Member if m.kind != Kind.RootPackage => def processMember(member: Member, fqName: List[String]): Seq[(JSON, Seq[String])] = - val signature: MemberSignature = signatureProvider.rawSignature(member)() + val signature: MemberSignature = signatureProvider.rawSignature(member)(!ctx.args.suppressCC)() val sig = Signature(Plain(member.name)) ++ signature.suffix val descr = if member.kind == Kind.Package then "" else fqName.mkString(".") val extraDescr = member.docs.map(d => docPartRenderPlain(d.body)).getOrElse("") diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala index 81309018718c..06fe658b850e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala @@ -43,7 +43,7 @@ trait BasicSupport: def getAnnotations(): List[Annotation] = // Custom annotations should be documented only if annotated by @java.lang.annotation.Documented // We allow also some special cases - val fqNameAllowlist = Set( + val fqNameAllowlist0 = Set( "scala.specialized", "scala.throws", "scala.transient", @@ -54,9 +54,11 @@ trait BasicSupport: "scala.annotation.targetName", "scala.annotation.threadUnsafe", "scala.annotation.varargs", - CaptureDefs.useAnnotFullName, - CaptureDefs.consumeAnnotFullName, ) + val fqNameAllowlist = + if ccEnabled then + fqNameAllowlist0 + CaptureDefs.useAnnotFullName + CaptureDefs.consumeAnnotFullName + else fqNameAllowlist0 val documentedSymbol = summon[Quotes].reflect.Symbol.requiredClass("java.lang.annotation.Documented") val annotations = sym.annotations.filter { a => a.tpe.typeSymbol.hasAnnotation(documentedSymbol) || fqNameAllowlist.contains(a.symbol.fullName) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 00635951fb69..690a19c6e78b 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -21,6 +21,15 @@ trait ClassLikeSupport: private given qctx.type = qctx + extension (symbol: Symbol) { + def getExtraModifiers(): Seq[Modifier] = + val mods = SymOps.getExtraModifiers(symbol)() + if ccEnabled && symbol.flags.is(Flags.Mutable)then + mods :+ Modifier.Update + else + mods + } + private def bareClasslikeKind(using Quotes)(symbol: reflect.Symbol): Kind = import reflect._ if symbol.flags.is(Flags.Module) then Kind.Object diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala index 8de2ab6b8539..234c2c3cc4f2 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala @@ -15,7 +15,7 @@ trait PackageSupport: def parsePackage(pck: PackageClause): (String, Member) = val name = pck.symbol.fullName - ccFlag = false // FIXME: would be better if we had access to the tasty attribute + ccFlag = false pck.stats.foreach { case CCImport() => ccFlag = true case _ => diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala index 0464da450f05..969b1d6462c2 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala @@ -100,7 +100,6 @@ object SymOps: Flags.Case -> Modifier.Case, Flags.Opaque -> Modifier.Opaque, Flags.AbsOverride -> Modifier.AbsOverride, - Flags.Mutable -> Modifier.Update, // under CC ).collect { case (flag, mod) if sym.flags.is(flag) => mod } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala index 741147ebfe2e..f7cdf62c5458 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala @@ -188,7 +188,7 @@ case class TastyParser( private given qctx.type = qctx protected var ccFlag: Boolean = false - def ccEnabled: Boolean = ccFlag + def ccEnabled: Boolean = !ctx.args.suppressCC && ccFlag val intrinsicClassDefs = Set( defn.AnyClass, diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index e929bb75f760..f3b6f32b6638 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -10,7 +10,6 @@ import dotty.tools.scaladoc.cc.* import NameNormalizer._ import SyntheticsSupport._ -import java.awt.RenderingHints.Key trait TypesSupport: self: TastyParser => @@ -41,13 +40,13 @@ trait TypesSupport: private def keyword(str: String): SignaturePart = Keyword(str) private def tpe(str: String, dri: DRI)(using inCC: Option[Any]): SignaturePart = - if inCC.isDefined then + if ccEnabled && inCC.isDefined then dotty.tools.scaladoc.Plain(str) else dotty.tools.scaladoc.Type(str, Some(dri)) private def tpe(str: String)(using inCC: Option[Any]): SignaturePart = - if inCC.isDefined then + if ccEnabled && inCC.isDefined then dotty.tools.scaladoc.Plain(str) else dotty.tools.scaladoc.Type(str, None) @@ -60,7 +59,7 @@ trait TypesSupport: private def tpe(using Quotes)(symbol: reflect.Symbol)(using inCC: Option[Any]): SSignature = import SymOps._ val dri: Option[DRI] = Option(symbol).filterNot(_.isHiddenByVisibility).map(_.dri) - if inCC.isDefined then // we are in the context of a capture set and want paths to be rendered plainly + if ccEnabled && inCC.isDefined then // we are in the context of a capture set and want paths to be rendered plainly dotty.tools.scaladoc.Plain(symbol.normalizedName).l else dotty.tools.scaladoc.Type(symbol.normalizedName, dri).l @@ -135,7 +134,7 @@ trait TypesSupport: inner(tpe, skipThisTypePrefix) :+ plain("*") case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) => inner(tpe, skipThisTypePrefix) :+ plain("*") - case CapturingType(base, refs) => + case CapturingType(base, refs) if ccEnabled => base match case t @ AppliedType(base, args) if t.isFunctionType => functionType(base, args, skipThisTypePrefix)(using inCC = Some(refs)) @@ -283,7 +282,7 @@ trait TypesSupport: case _ => topLevelProcess(t, skipThisTypePrefix) }) ++ plain("]").l - case t : TypeRef if t.isCapSet => emitCaptureSet(Nil, skipThisTypePrefix) + case t : TypeRef if ccEnabled && t.isCapSet => emitCaptureSet(Nil, skipThisTypePrefix) case tp @ TypeRef(qual, typeName) => inline def wrapping = shouldWrapInParens(inner = qual, outer = tp, isLeft = true) diff --git a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala index d62ce4693575..31d57fe9a697 100644 --- a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala +++ b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala @@ -6,12 +6,12 @@ import scala.util.chaining._ class ScalaSignatureProvider: val builder = SignatureBuilder() given Conversion[SignatureBuilder, Signature] = bdr => bdr.content - def rawSignature(documentable: Member)(kind: Kind = documentable.kind): MemberSignature = + def rawSignature(documentable: Member)(allowCC: Boolean /*capture checking enabled?*/)(kind: Kind = documentable.kind): MemberSignature = kind match case Kind.Extension(_, m) => extensionSignature(documentable, m) case Kind.Exported(d) => - rawSignature(documentable)(d) + rawSignature(documentable)(allowCC)(d) case d: Kind.Def => methodSignature(documentable, d) case Kind.Constructor(d) => @@ -39,7 +39,7 @@ class ScalaSignatureProvider: case Kind.Val | Kind.Var | Kind.Implicit(Kind.Val, _) => fieldSignature(documentable, kind) case tpe: Kind.Type => - typeSignature(tpe, documentable) + typeSignature(tpe, documentable)(allowCC) case Kind.Package => MemberSignature( Nil, @@ -145,11 +145,11 @@ class ScalaSignatureProvider: case _ => fieldLikeSignature(field, field.kind, None) - private def typeSignature(tpe: Kind.Type, typeDef: Member): MemberSignature = + private def typeSignature(tpe: Kind.Type, typeDef: Member)(allowCC: Boolean = false): MemberSignature = MemberSignature( builder.modifiersAndVisibility(typeDef), builder.kind(tpe), - builder.name(typeDef.name, typeDef.dri, isCaptureVar = tpe.isCaptureVar), + builder.name(typeDef.name, typeDef.dri, isCaptureVar = allowCC && tpe.isCaptureVar), builder.typeParamList(tpe.typeParams).pipe { bdr => if (!tpe.opaque) { (if tpe.concreate then bdr.plain(" = ") else bdr) From 73c3654091b19cefa3c5c49f6cbfc8209fe7dcdc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:50:46 +0000 Subject: [PATCH 018/111] Bump actions/download-artifact from 4 to 5 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] [Cherry-picked 6c680615e5dde6e43a4f229a816027dd52e880f7] --- .github/workflows/ci.yaml | 2 +- .github/workflows/publish-chocolatey.yml | 2 +- .github/workflows/test-chocolatey.yml | 2 +- .github/workflows/test-msi.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c8b5617fc534..c6d6fe539e62 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -824,7 +824,7 @@ jobs: prepareSDK "-x86_64-pc-win32" "dist-win-x86_64" "./dist/win-x86_64/" - name: Download MSI package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: scala.msi path: . diff --git a/.github/workflows/publish-chocolatey.yml b/.github/workflows/publish-chocolatey.yml index 88a8a7913188..62f0fb864c21 100644 --- a/.github/workflows/publish-chocolatey.yml +++ b/.github/workflows/publish-chocolatey.yml @@ -31,7 +31,7 @@ jobs: runs-on: windows-latest steps: - name: Fetch the Chocolatey package from GitHub - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: scala.nupkg - name: Publish the package to Chocolatey diff --git a/.github/workflows/test-chocolatey.yml b/.github/workflows/test-chocolatey.yml index e302968b9129..0ccfa2b9ac1b 100644 --- a/.github/workflows/test-chocolatey.yml +++ b/.github/workflows/test-chocolatey.yml @@ -35,7 +35,7 @@ jobs: distribution: temurin java-version: ${{ inputs.java-version }} - name: Download the 'nupkg' from GitHub Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: scala.nupkg path: ${{ env.CHOCOLATEY-REPOSITORY }} diff --git a/.github/workflows/test-msi.yml b/.github/workflows/test-msi.yml index 1299c3d55061..e9b5490549f2 100644 --- a/.github/workflows/test-msi.yml +++ b/.github/workflows/test-msi.yml @@ -29,7 +29,7 @@ jobs: distribution: temurin java-version: ${{ inputs.java-version }} - name: Download MSI artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: scala.msi path: . From 0865ae9b4e92c90becbb52cb34207bd100856a8e Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 14 Aug 2025 15:27:25 +0200 Subject: [PATCH 019/111] Don't check bounds in match type cases at CC For soundness it's enough to check bounds in reduced match types. [Cherry-picked 1231728af7a01cd8c5b8b8cb7505252307e18bf2] --- .../src/dotty/tools/dotc/typer/Checking.scala | 43 ++++++++++--------- .../captures/tuple-ops-2.scala | 19 ++++++++ 2 files changed, 41 insertions(+), 21 deletions(-) create mode 100644 tests/pos-custom-args/captures/tuple-ops-2.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index ecbb34ea2949..4e148cc8f52b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -144,29 +144,30 @@ object Checking { def checkAppliedTypesIn(tpt: TypeTree)(using Context): Unit = val checker = new TypeTraverser: def traverse(tp: Type) = - tp match + tp.normalized match case tp @ AppliedType(tycon, argTypes) => - // Should the type be re-checked in the CC phase? - // Exempted are types that are not themselves capture-checked. - // Since the type constructor could not foresee possible capture sets, - // it's better to be lenient for backwards compatibility. - // Also exempted are match aliases. See tuple-ops.scala for an example that - // would fail otherwise. - def checkableUnderCC = - tycon.typeSymbol.is(CaptureChecked) && !tp.isMatchAlias - if !(tycon.typeSymbol.is(JavaDefined) && ctx.compilationUnit.isJava) - // Don't check bounds in Java units that refer to Java type constructors. - // Scala is not obliged to do Java type checking and in fact i17763 goes wrong - // if we attempt to check bounds of F-bounded mutually recursive Java interfaces. - // Do check all bounds in Scala units and those bounds in Java units that - // occur in applications of Scala type constructors. - && (!isCaptureChecking || checkableUnderCC) then - checkAppliedType( - untpd.AppliedTypeTree(TypeTree(tycon), argTypes.map(TypeTree(_))) - .withType(tp).withSpan(tpt.span.toSynthetic), - tpt) + if !(isCaptureChecking && defn.MatchCase.isInstance(tp)) then + // Don't check match type cases under cc. For soundness it's enough + // to check bounds in reduced match types. + // See tuple-ops.scala and tuple-ops-2.scala for examples that would fail otherwise. + if !(tycon.typeSymbol.is(JavaDefined) && ctx.compilationUnit.isJava) + // Don't check bounds in Java units that refer to Java type constructors. + // Scala is not obliged to do Java type checking and in fact i17763 goes wrong + // if we attempt to check bounds of F-bounded mutually recursive Java interfaces. + // Do check all bounds in Scala units and those bounds in Java units that + // occur in applications of Scala type constructors. + && tycon.typeSymbol.is(CaptureChecked) + // Exempted are types that are not themselves capture-checked. + // Since the type constructor could not foresee possible capture sets, + // it's better to be lenient for backwards compatibility. + then + checkAppliedType( + untpd.AppliedTypeTree(TypeTree(tycon), argTypes.map(TypeTree(_))) + .withType(tp).withSpan(tpt.span.toSynthetic), + tpt) + traverseChildren(tp) case _ => - traverseChildren(tp) + traverseChildren(tp) checker.traverse(tpt.tpe) def checkNoWildcard(tree: Tree)(using Context): Tree = tree.tpe match { diff --git a/tests/pos-custom-args/captures/tuple-ops-2.scala b/tests/pos-custom-args/captures/tuple-ops-2.scala new file mode 100644 index 000000000000..322a52feaaab --- /dev/null +++ b/tests/pos-custom-args/captures/tuple-ops-2.scala @@ -0,0 +1,19 @@ +sealed trait Tup +case object Emp extends Tup +type Emp = Emp.type +case class Cons[h, t <: Tup](hh: h, tt: t) extends Tup + +type Union[T <: Tup] = T match + case Emp => Nothing + case Cons[h, t] => h | Union[t] + +type Concat[T <: Tup, U <: Tup] <: Tup = T match + case Emp => U + case Cons[h, t] => Cons[h, Concat[t, U]] + +type FlatMap[T <: Tup, F[_ <: Union[T]] <: Tup] <: Tup = T match + case Emp => Emp + case Cons[h, t] => Concat[F[h], FlatMap[t, F]] + +type A = + FlatMap[Cons[Boolean, Cons[String, Emp]], [T] =>> Cons[T, Cons[List[T], Emp]]] \ No newline at end of file From df6eeb5f9373bf20c8dcf726aae95ffe1c714ded Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 14 Aug 2025 22:26:16 +0200 Subject: [PATCH 020/111] Fix checking condition [Cherry-picked c54c5386b6c870a333ca65996e94b5c10097627f] --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 4e148cc8f52b..990eb01b999e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -156,10 +156,10 @@ object Checking { // if we attempt to check bounds of F-bounded mutually recursive Java interfaces. // Do check all bounds in Scala units and those bounds in Java units that // occur in applications of Scala type constructors. - && tycon.typeSymbol.is(CaptureChecked) - // Exempted are types that are not themselves capture-checked. - // Since the type constructor could not foresee possible capture sets, - // it's better to be lenient for backwards compatibility. + && (!isCaptureChecking || tycon.typeSymbol.is(CaptureChecked)) + // When capture checking, types that are not themselves capture-checked + // are exempted. Since the type constructor could not foresee possible + // capture sets, it's better to be lenient for backwards compatibility. then checkAppliedType( untpd.AppliedTypeTree(TypeTree(tycon), argTypes.map(TypeTree(_))) From 9f93b81919745df13e1c30305ac73b428cee2aea Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 14 Aug 2025 16:53:21 +0200 Subject: [PATCH 021/111] Update superCallContext to include dummy capture parameters in scope [Cherry-picked 456b13e9ba3894f1d4f9188944d2aa261d222ee1] --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 6 +++--- tests/pos-custom-args/captures/i23737.scala | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 tests/pos-custom-args/captures/i23737.scala diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 9de714be8c37..5a0e03330ef2 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -402,8 +402,8 @@ object Contexts { * * - as owner: The primary constructor of the class * - as outer context: The context enclosing the class context - * - as scope: type parameters, the parameter accessors, and - * the context bound companions in the class context, + * - as scope: type parameters, the parameter accessors, + * the dummy capture parameters and the context bound companions in the class context, * * The reasons for this peculiar choice of attributes are as follows: * @@ -420,7 +420,7 @@ object Contexts { def superCallContext: Context = val locals = owner.typeParams ++ owner.asClass.unforcedDecls.filter: sym => - sym.is(ParamAccessor) || sym.isContextBoundCompanion + sym.is(ParamAccessor) || sym.isContextBoundCompanion || sym.isDummyCaptureParam superOrThisCallContext(owner.primaryConstructor, newScopeWith(locals*)) /** The context for the arguments of a this(...) constructor call. diff --git a/tests/pos-custom-args/captures/i23737.scala b/tests/pos-custom-args/captures/i23737.scala new file mode 100644 index 000000000000..d2d3f6d0cb4a --- /dev/null +++ b/tests/pos-custom-args/captures/i23737.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking + +class C + +trait A[T] + +trait B[CC^] extends A[C^{CC}] // error: CC not found + +trait D[CC^]: + val x: Object^{CC} = ??? \ No newline at end of file From ed8918e565f41d44fda18d224aa4e462070f001e Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 14 Aug 2025 17:52:15 +0200 Subject: [PATCH 022/111] Fix condition of checking dummy capture params [Cherry-picked ae2a5dc7d2eda5485a81847ddf99700aa21feb6f] --- compiler/src/dotty/tools/dotc/core/SymUtils.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymUtils.scala b/compiler/src/dotty/tools/dotc/core/SymUtils.scala index 34908a2df6d6..f22002495bb3 100644 --- a/compiler/src/dotty/tools/dotc/core/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/SymUtils.scala @@ -91,7 +91,7 @@ class SymUtils: self.is(Synthetic) && self.infoOrCompleter.typeSymbol == defn.CBCompanion def isDummyCaptureParam(using Context): Boolean = - self.isAllOf(CaptureParam) && !(self.isClass || self.is(Method)) + self.is(PhantomSymbol) && self.infoOrCompleter.typeSymbol != defn.CBCompanion /** Is this a case class for which a product mirror is generated? * Excluded are value classes, abstract classes and case classes with more than one From 2ffdb0283c4a8902ec2e24076c1e2ffed00b234c Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 15 Aug 2025 10:27:34 +0200 Subject: [PATCH 023/111] Update test [Cherry-picked 7e5321079f4d6572245a9e4481d4f3107d4891e6] --- tests/pos-custom-args/captures/i23737.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/pos-custom-args/captures/i23737.scala b/tests/pos-custom-args/captures/i23737.scala index d2d3f6d0cb4a..3d2bb4f6791b 100644 --- a/tests/pos-custom-args/captures/i23737.scala +++ b/tests/pos-custom-args/captures/i23737.scala @@ -7,4 +7,8 @@ trait A[T] trait B[CC^] extends A[C^{CC}] // error: CC not found trait D[CC^]: - val x: Object^{CC} = ??? \ No newline at end of file + val x: Object^{CC} = ??? + +def f(c: C^) = + val b = new B[{c}] {} + val a: A[C^{c}] = b \ No newline at end of file From d04f4b52001648b48b71a086b3e42db8d88c32a9 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 14 Jul 2025 16:24:26 +0200 Subject: [PATCH 024/111] Enhance pattern matching with capturing types; fix stdlib-cc [Cherry-picked e0256bdc0d8cb473b670a350d8263c8ee9cafe32] --- .../tools/dotc/transform/PatternMatcher.scala | 7 ++++++- .../src/scala/collection/IndexedSeqView.scala | 2 +- .../mutable/CheckedIndexedSeqView.scala | 2 +- tests/neg-custom-args/captures/match.scala | 21 +++++++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 tests/neg-custom-args/captures/match.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 8bf88a0027c4..b4d766a1bd24 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -14,9 +14,11 @@ import Flags.*, Constants.* import Decorators.* import NameKinds.{PatMatStdBinderName, PatMatAltsName, PatMatResultName} import config.Printers.patmatch +import config.Feature import reporting.* import ast.* import util.Property.* +import cc.{CapturingType, Capabilities} import scala.annotation.tailrec import scala.collection.mutable @@ -427,8 +429,11 @@ object PatternMatcher { && !hasExplicitTypeArgs(extractor) case _ => false } + val castTp = if Feature.ccEnabled + then CapturingType(tpt.tpe, scrutinee.termRef.singletonCaptureSet) + else tpt.tpe TestPlan(TypeTest(tpt, isTrusted), scrutinee, tree.span, - letAbstract(ref(scrutinee).cast(tpt.tpe)) { casted => + letAbstract(ref(scrutinee).cast(castTp)) { casted => nonNull += casted patternPlan(casted, pat, onSuccess) }) diff --git a/scala2-library-cc/src/scala/collection/IndexedSeqView.scala b/scala2-library-cc/src/scala/collection/IndexedSeqView.scala index 78f8abb8e327..07698bc09951 100644 --- a/scala2-library-cc/src/scala/collection/IndexedSeqView.scala +++ b/scala2-library-cc/src/scala/collection/IndexedSeqView.scala @@ -160,7 +160,7 @@ object IndexedSeqView { @SerialVersionUID(3L) class Reverse[A](underlying: SomeIndexedSeqOps[A]^) extends SeqView.Reverse[A](underlying) with IndexedSeqView[A] { - override def reverse: IndexedSeqView[A] = underlying match { + override def reverse: IndexedSeqView[A]^{underlying} = underlying match { case x: IndexedSeqView[A] => x case _ => super.reverse } diff --git a/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala b/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala index 1c3f669f5358..89e3dfb78d8e 100644 --- a/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala +++ b/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala @@ -101,7 +101,7 @@ private[mutable] object CheckedIndexedSeqView { @SerialVersionUID(3L) class Reverse[A](underlying: SomeIndexedSeqOps[A]^)(protected val mutationCount: () ->{cap.rd} Int) extends IndexedSeqView.Reverse[A](underlying) with CheckedIndexedSeqView[A] { - override def reverse: IndexedSeqView[A] = underlying match { + override def reverse: IndexedSeqView[A]^{underlying} = underlying match { case x: IndexedSeqView[A] => x case _ => super.reverse } diff --git a/tests/neg-custom-args/captures/match.scala b/tests/neg-custom-args/captures/match.scala new file mode 100644 index 000000000000..9f7c9c6d8e74 --- /dev/null +++ b/tests/neg-custom-args/captures/match.scala @@ -0,0 +1,21 @@ +import language.experimental.captureChecking + +trait A + +case class B(x: AnyRef^) extends A + +def test = + val x: AnyRef^ = new AnyRef + val a: A^{x} = B(x) + + val y1: A = a match + case b: B => b // error: (b: B) becomes B^{x} implicitly + + val y2: A^{x} = a match + case b: B => b // ok + + val x3: AnyRef = a match + case B(x2: AnyRef) => x2 // error: we lose some information about field x, but it still cannot be pure + + val x4: AnyRef = a match + case b: B => b.x // error From 01c59ad6339b0bf69818304464287e5503121c20 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 15 Aug 2025 13:57:17 +0200 Subject: [PATCH 025/111] Try to bypass separation check in case body [Cherry-picked 484e97a68c778b10e260cfbf4797ddca9b77dc58] --- scala2-library-cc/src/scala/collection/View.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scala2-library-cc/src/scala/collection/View.scala b/scala2-library-cc/src/scala/collection/View.scala index 72a073836e77..d93ccaa89f20 100644 --- a/scala2-library-cc/src/scala/collection/View.scala +++ b/scala2-library-cc/src/scala/collection/View.scala @@ -152,8 +152,9 @@ object View extends IterableFactory[View] { def apply[A](underlying: Iterable[A]^, p: A => Boolean, isFlipped: Boolean): Filter[A]^{underlying, p} = underlying match { case filter: Filter[A] if filter.isFlipped == isFlipped => - new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) - .asInstanceOf[Filter[A]^{underlying, p}] + unsafeAssumeSeparate: + new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) + .asInstanceOf[Filter[A]^{underlying, p}] // !!! asInstanceOf needed once paths were added, see path-patmat-should-be-pos.scala for minimization //case filter: Filter[A]^{underlying} if filter.isFlipped == isFlipped => // unsafeAssumeSeparate: From 80eea30dea030b93418019a47ccb517eef9ca1b0 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 15 Aug 2025 15:02:28 +0200 Subject: [PATCH 026/111] Update test [Cherry-picked d140083bb8ba3605499aa8fbd3215d7dba4c0184] --- scala2-library-cc/src/scala/collection/View.scala | 3 --- tests/neg-custom-args/captures/match.scala | 5 ++++- .../captures/colltest5/CollectionStrawManCC5_1.scala | 7 +++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/scala2-library-cc/src/scala/collection/View.scala b/scala2-library-cc/src/scala/collection/View.scala index d93ccaa89f20..b8de97159f06 100644 --- a/scala2-library-cc/src/scala/collection/View.scala +++ b/scala2-library-cc/src/scala/collection/View.scala @@ -156,12 +156,9 @@ object View extends IterableFactory[View] { new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) .asInstanceOf[Filter[A]^{underlying, p}] // !!! asInstanceOf needed once paths were added, see path-patmat-should-be-pos.scala for minimization - //case filter: Filter[A]^{underlying} if filter.isFlipped == isFlipped => - // unsafeAssumeSeparate: // See filter-iterable.scala for a test where a variant of Filter // works without the unsafeAssumeSeparate. But it requires significant // changes compared to the version here. See also Filter in colltest5.CollectionStrawManCC5_1. - // new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) case _ => new Filter(underlying, p, isFlipped) } } diff --git a/tests/neg-custom-args/captures/match.scala b/tests/neg-custom-args/captures/match.scala index 9f7c9c6d8e74..cf0c0f1a3377 100644 --- a/tests/neg-custom-args/captures/match.scala +++ b/tests/neg-custom-args/captures/match.scala @@ -12,7 +12,10 @@ def test = case b: B => b // error: (b: B) becomes B^{x} implicitly val y2: A^{x} = a match - case b: B => b // ok + case b: B => + val bb: B^{b} = b + val aa: A^{a} = bb + b // ok val x3: AnyRef = a match case B(x2: AnyRef) => x2 // error: we lose some information about field x, but it still cannot be pure diff --git a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala index ffb68c8d0d60..ea0bdc240e0c 100644 --- a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala +++ b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala @@ -457,13 +457,12 @@ object CollectionStrawMan5 { def apply[A](underlying: Iterable[A]^, pp: A => Boolean, isFlipped: Boolean): Filter[A]^{underlying, pp} = underlying match case filter: Filter[A] => - new Filter(filter.underlying, a => filter.p(a) && pp(a)) - .asInstanceOf[Filter[A]^{underlying, pp}] - //unsafeAssumeSeparate: + unsafeAssumeSeparate: + new Filter(filter.underlying, a => filter.p(a) && pp(a)) + .asInstanceOf[Filter[A]^{underlying, pp}] // See filter-iterable.scala for a test where a variant of Filter // works without the unsafeAssumeSeparate. But it requires significant // changes compared to the version here. - //new Filter(filter.underlying, a => filter.p(a) && pp(a)) case _ => new Filter(underlying, pp) case class Partition[A](val underlying: Iterable[A]^, p: A => Boolean) { From 3101ac70dd6ed99d00019e4070487bb43ef7a4bf Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 16 Aug 2025 02:12:34 +0200 Subject: [PATCH 027/111] Scaladoc fix: don't drop caps on parameters [Cherry-picked 705fe5f21ec6ece4f06fc322cc815e3ca84088c7] --- scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index f3b6f32b6638..482807010d40 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -533,6 +533,7 @@ trait TypesSupport: private def isCapturedInContext(using Quotes)(ref: reflect.TypeRepr)(using elideThis: reflect.ClassDef): Boolean = import reflect._ ref match + case t if t.isCaptureRoot => true case ReachCapability(c) => isCapturedInContext(c) case ReadOnlyCapability(c) => isCapturedInContext(c) case ThisType(tr) => !elideThis.symbol.typeRef.isPureClass(elideThis) /* is the current class pure? */ From cc088840e0a3171811a0015d8349a44e90468c54 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 18 Aug 2025 14:19:24 +0200 Subject: [PATCH 028/111] Update to sbt-develocity 1.3.1 [Cherry-picked 8766c91f684fc12b12526f23e8f004a09cbf52ad] --- project/plugins.sbt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 3e1ccf5e8710..510afef8d8aa 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -20,10 +20,7 @@ addSbtPlugin("ch.epfl.scala" % "sbt-tasty-mima" % "1.0.0") addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.0") -resolvers += - "Develocity Artifactory" at "https://repo.grdev.net/artifactory/public/" - -addSbtPlugin("com.gradle" % "sbt-develocity" % "1.2.2-rc-1") +addSbtPlugin("com.gradle" % "sbt-develocity" % "1.3.1") addSbtPlugin("com.gradle" % "sbt-develocity-common-custom-user-data" % "1.1") From b779a877b353bd59b9f4b6db4b939759254d9bc5 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Tue, 19 Aug 2025 13:55:36 +0200 Subject: [PATCH 029/111] chore: add regression test for #23776 [Cherry-picked f581944e76db0d4fcd52a61e537b74dd6181f1d8] --- tests/run/i23776.check | 1 + tests/run/i23776.scala | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 tests/run/i23776.check create mode 100644 tests/run/i23776.scala diff --git a/tests/run/i23776.check b/tests/run/i23776.check new file mode 100644 index 000000000000..f2b2a017d25a --- /dev/null +++ b/tests/run/i23776.check @@ -0,0 +1 @@ +a = 0, b = 1, c = 2 diff --git a/tests/run/i23776.scala b/tests/run/i23776.scala new file mode 100644 index 000000000000..9d4a98144f2e --- /dev/null +++ b/tests/run/i23776.scala @@ -0,0 +1,7 @@ +inline def f(t0: Int, t1: Int, t2: Int) = { + inline (t0, t1, t2) match { + case (a: Int, b: Int, c: Int) => println(s"a = $a, b = $b, c = $c") + } +} + +@main def Test = f(0, 1, 2) From 6c50c8e07f70d593f86345a118479ab19fbe2464 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Mon, 25 Aug 2025 00:22:13 +0200 Subject: [PATCH 030/111] chore: bump sbt to 1.11.5 [Cherry-picked 0a93944cee756a14e9b1efe0227d149af7d7dad6] --- community-build/src/scala/dotty/communitybuild/projects.scala | 2 +- project/build.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/community-build/src/scala/dotty/communitybuild/projects.scala b/community-build/src/scala/dotty/communitybuild/projects.scala index b575bd2eadf8..df0793b6fb2a 100644 --- a/community-build/src/scala/dotty/communitybuild/projects.scala +++ b/community-build/src/scala/dotty/communitybuild/projects.scala @@ -128,7 +128,7 @@ final case class SbtCommunityProject( case Some(ivyHome) => List(s"-Dsbt.ivy.home=$ivyHome") case _ => Nil extraSbtArgs ++ sbtProps ++ List( - "-sbt-version", "1.10.7", + "-sbt-version", "1.11.5", "-Dsbt.supershell=false", s"-Ddotty.communitybuild.dir=$communitybuildDir", s"--addPluginSbtFile=$sbtPluginFilePath" diff --git a/project/build.properties b/project/build.properties index 6520f6981d5a..e480c675f2fd 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.11.0 +sbt.version=1.11.5 From 7d004890b10b97e953944e6653972324cd621323 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Sat, 20 Sep 2025 23:14:16 +0200 Subject: [PATCH 031/111] Towards version to 3.7.4-RC1 --- project/Build.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index cd45bdf4ceda..d5e55e24dbda 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -63,7 +63,7 @@ object Build { * * Warning: Change of this variable might require updating `expectedTastyVersion` */ - val developedVersion = "3.7.3" + val developedVersion = "3.7.4" /** The version of the compiler including the RC prefix. * Defined as common base before calculating environment specific suffixes in `dottyVersion` @@ -72,7 +72,7 @@ object Build { * During release candidate cycle incremented by the release officer before publishing a subsequent RC version; * During final, stable release is set exactly to `developedVersion`. */ - val baseVersion = developedVersion + val baseVersion = s"$developedVersion-RC1" /** The version of TASTY that should be emitted, checked in runtime test * For defails on how TASTY version should be set see related discussions: From 60259e11a345b293f65a5090ca4a3357a645bdaa Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Sat, 20 Sep 2025 23:14:28 +0200 Subject: [PATCH 032/111] Update refernce compiler to 3.7.3 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index d5e55e24dbda..ac09da0326da 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -52,7 +52,7 @@ object Build { * * Warning: Change of this variable needs to be consulted with `expectedTastyVersion` */ - val referenceVersion = "3.7.2" + val referenceVersion = "3.7.3" /** Version of the Scala compiler targeted in the current release cycle * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes From efef7476bf4be39725a868ed8a7d7d1baf8fbd6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 01:21:29 +0000 Subject: [PATCH 033/111] chore(deps): bump actions/setup-java from 4 to 5 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-msi.yml | 4 +- .github/workflows/build-sdk.yml | 4 +- .github/workflows/language-reference.yaml | 2 +- .github/workflows/scaladoc.yaml | 4 +- .github/workflows/stdlib.yaml | 64 +++++++++++------------ .github/workflows/test-chocolatey.yml | 2 +- .github/workflows/test-launchers.yml | 10 ++-- .github/workflows/test-msi.yml | 2 +- 8 files changed, 46 insertions(+), 46 deletions(-) diff --git a/.github/workflows/build-msi.yml b/.github/workflows/build-msi.yml index 14838c589d6a..ca6100179776 100644 --- a/.github/workflows/build-msi.yml +++ b/.github/workflows/build-msi.yml @@ -21,8 +21,8 @@ jobs: build: runs-on: windows-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-java@v5 with: distribution: 'adopt' java-version: '8' diff --git a/.github/workflows/build-sdk.yml b/.github/workflows/build-sdk.yml index cd111df1a083..8248f10ae824 100644 --- a/.github/workflows/build-sdk.yml +++ b/.github/workflows/build-sdk.yml @@ -55,8 +55,8 @@ jobs: env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ inputs.java-version }} diff --git a/.github/workflows/language-reference.yaml b/.github/workflows/language-reference.yaml index 61a2768c51da..6d6a5fd904db 100644 --- a/.github/workflows/language-reference.yaml +++ b/.github/workflows/language-reference.yaml @@ -31,7 +31,7 @@ jobs: ssh-key: ${{ secrets.DOCS_KEY }} - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 diff --git a/.github/workflows/scaladoc.yaml b/.github/workflows/scaladoc.yaml index d2e3071e765b..6b79e1037e47 100644 --- a/.github/workflows/scaladoc.yaml +++ b/.github/workflows/scaladoc.yaml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -80,7 +80,7 @@ jobs: uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 diff --git a/.github/workflows/stdlib.yaml b/.github/workflows/stdlib.yaml index 47984f8d15ad..8a6c4fba2383 100644 --- a/.github/workflows/stdlib.yaml +++ b/.github/workflows/stdlib.yaml @@ -14,10 +14,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -32,10 +32,10 @@ jobs: ##needs: [scala-library-nonbootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -50,10 +50,10 @@ jobs: needs : [scala3-compiler-nonbootstrapped, scala3-sbt-bridge-nonbootstrapped, scala-library-nonbootstrapped, scala3-library-nonbootstrapped] steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -68,10 +68,10 @@ jobs: ##needs: [scala-library-bootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -86,10 +86,10 @@ jobs: ##needs: [scala3-library-nonbootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -103,10 +103,10 @@ jobs: ##needs: [tasty-core-nonbootstrapped, scala3-library-nonbootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -120,10 +120,10 @@ jobs: ##needs: [scala3-compiler-nonbootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -137,10 +137,10 @@ jobs: ##needs: [scala3-library-bootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -154,10 +154,10 @@ jobs: ##needs: [tasty-core-bootstrapped, scala3-library-bootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -171,10 +171,10 @@ jobs: ##needs: [scala3-compiler-bootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -188,10 +188,10 @@ jobs: ##needs: [scala3-compiler-bootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -205,10 +205,10 @@ jobs: ##needs: [scala3-compiler-bootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -228,10 +228,10 @@ jobs: ##needs: [scala3-sbt-bridge-nonbootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -245,10 +245,10 @@ jobs: ##needs: [scala3-sbt-bridge-bootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -262,10 +262,10 @@ jobs: ##needs: [tasty-core-nonbootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -279,10 +279,10 @@ jobs: ##needs: [tasty-core-bootstrapped] Add when we add support for caching here steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 diff --git a/.github/workflows/test-chocolatey.yml b/.github/workflows/test-chocolatey.yml index 0ccfa2b9ac1b..212af8dae50d 100644 --- a/.github/workflows/test-chocolatey.yml +++ b/.github/workflows/test-chocolatey.yml @@ -30,7 +30,7 @@ jobs: test: runs-on: windows-latest steps: - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ inputs.java-version }} diff --git a/.github/workflows/test-launchers.yml b/.github/workflows/test-launchers.yml index 25bd5a4bf42f..e15f4da3bdfc 100644 --- a/.github/workflows/test-launchers.yml +++ b/.github/workflows/test-launchers.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' @@ -32,7 +32,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' @@ -51,7 +51,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' @@ -70,7 +70,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' @@ -89,7 +89,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' diff --git a/.github/workflows/test-msi.yml b/.github/workflows/test-msi.yml index e9b5490549f2..4ced29a61cf0 100644 --- a/.github/workflows/test-msi.yml +++ b/.github/workflows/test-msi.yml @@ -24,7 +24,7 @@ jobs: test: runs-on: windows-latest steps: - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ inputs.java-version }} From e033598bf6c77fbc2b806da7840f7fde2c26dfb4 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Sun, 21 Sep 2025 00:01:57 +0200 Subject: [PATCH 034/111] chore(deps): bump actions/setup-java from 4 to 5 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] [Cherry-picked b1d3f18a436b75951f00b4a52e1e9d7546f5c5d8][modified] From 770820a16c34c9e7f0908e880cbc32a7dad2183c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 01:25:04 +0000 Subject: [PATCH 035/111] chore(deps): bump sdkman/sdkman-release-action Bumps [sdkman/sdkman-release-action](https://github.com/sdkman/sdkman-release-action) from 2800d4359ae097a99afea7e0370f0c6e726182a4 to c70225d437d17182d19476702b671513dc8bf048. - [Release notes](https://github.com/sdkman/sdkman-release-action/releases) - [Commits](https://github.com/sdkman/sdkman-release-action/compare/2800d4359ae097a99afea7e0370f0c6e726182a4...c70225d437d17182d19476702b671513dc8bf048) --- updated-dependencies: - dependency-name: sdkman/sdkman-release-action dependency-version: c70225d437d17182d19476702b671513dc8bf048 dependency-type: direct:production ... Signed-off-by: dependabot[bot] [Cherry-picked dd3740827febc26acf0a16afada39dfdd711bf10] --- .github/workflows/publish-sdkman.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-sdkman.yml b/.github/workflows/publish-sdkman.yml index fbbada2a1a70..a3643aa59331 100644 --- a/.github/workflows/publish-sdkman.yml +++ b/.github/workflows/publish-sdkman.yml @@ -46,7 +46,7 @@ jobs: - platform: WINDOWS_64 archive : 'scala3-${{ inputs.version }}-x86_64-pc-win32.zip' steps: - - uses: sdkman/sdkman-release-action@2800d4359ae097a99afea7e0370f0c6e726182a4 + - uses: sdkman/sdkman-release-action@c70225d437d17182d19476702b671513dc8bf048 with: CONSUMER-KEY : ${{ secrets.CONSUMER-KEY }} CONSUMER-TOKEN : ${{ secrets.CONSUMER-TOKEN }} From 874dfa914f9af52eaecf596c1935fc63f271f0d0 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Thu, 28 Aug 2025 20:15:16 +0200 Subject: [PATCH 036/111] Fix `derivesFrom` false negative in `provablyDisjointClasses` Before the addition of `SymDenotation#mayDeriveFrom`, tests/neg/i17132.min.scala was unsoundly accepted without a type error because `R[T]` is reduced to `Any` where `T <: P[Any]` by testing `provablyDisjointClasses` before `P` is added as a baseClass of `Q`. Now, a recursion overflows occurs because: - reducing `R[T] := T match case Q[t] => R[t]; case ...` requires - proving `T` disjoint from `Q[t]` where `T <: P[Any]`, which asks - whether existsCommonBaseTypeWithDisjointArguments, which requires - normalizing the type arg to the base class P for the class Q, i.e. R[t] - ... In short, despite the use of the pending set in provablyDisjoint, diverging is still possible when there is "cycle" in the type arguments in the base classes, (and where the pending set is thus reset for a separate normalization problem). One could attempt to add some more logic to detect these loops s.t. they are considered "stuck", as opposed to reporting an error. But it isn't clear that there are any concrete motivations for this. [Cherry-picked 5975a0692b29e06f9905b382e5e175dcd88ce2dc] --- .../tools/dotc/core/SymDenotations.scala | 11 +++++++ .../dotty/tools/dotc/core/TypeComparer.scala | 2 +- tests/neg/i17132.min.check | 31 +++++++++++++++++++ tests/neg/i17132.min.scala | 9 ++++++ 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i17132.min.check create mode 100644 tests/neg/i17132.min.scala diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index e17f127b7714..268f1d621815 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -865,9 +865,20 @@ object SymDenotations { * and is the denoting symbol also different from `Null` or `Nothing`? * @note erroneous classes are assumed to derive from all other classes * and all classes derive from them. + * @note may return a false negative when `this.info.isInstanceOf[TempClassInfo]`. */ def derivesFrom(base: Symbol)(using Context): Boolean = false + /** Could `this` derive from `base` now or in the future. + * For concistency with derivesFrom, The info is only forced when this is a ClassDenotation. + * If the info is a TempClassInfo then the baseClassSet may be temporarily approximated as empty. + * This is problematic when stability of `!derivesFrom(base)` is assumed for soundness, + * e.g., in `TypeComparer#provablyDisjointClasses`. + * @note may return a false positive when `this.info.isInstanceOf[TempClassInfo]`. + */ + final def mayDeriveFrom(base: Symbol)(using Context): Boolean = + this.isInstanceOf[ClassDenotation] && (info.isInstanceOf[TempClassInfo] || derivesFrom(base)) + /** Is this a Scala or Java annotation ? */ def isAnnotation(using Context): Boolean = isClass && (derivesFrom(defn.AnnotationClass) || is(JavaAnnotation)) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 7f8f8a34c171..cc9ba4396fc5 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3230,7 +3230,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling .filter(child => child.exists && child != cls) def eitherDerivesFromOther(cls1: Symbol, cls2: Symbol): Boolean = - cls1.derivesFrom(cls2) || cls2.derivesFrom(cls1) + cls1.mayDeriveFrom(cls2) || cls2.mayDeriveFrom(cls1) def smallestNonTraitBase(cls: Symbol): Symbol = val classes = if cls.isClass then cls.asClass.baseClasses else cls.info.classSymbols diff --git a/tests/neg/i17132.min.check b/tests/neg/i17132.min.check new file mode 100644 index 000000000000..f23d3e91549a --- /dev/null +++ b/tests/neg/i17132.min.check @@ -0,0 +1,31 @@ +-- Error: tests/neg/i17132.min.scala:4:7 ------------------------------------------------------------------------------- +4 |class Q[T <: P[Any]] extends P[R[T]] // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Recursion limit exceeded. + | Maybe there is an illegal cyclic reference? + | If that's not the case, you could also try to increase the stacksize using the -Xss JVM option. + | For the unprocessed stack trace, compile with -Xno-enrich-error-messages. + | A recurring operation is (inner to outer): + | + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | ... + | + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type T match ... diff --git a/tests/neg/i17132.min.scala b/tests/neg/i17132.min.scala new file mode 100644 index 000000000000..903b19e5cfed --- /dev/null +++ b/tests/neg/i17132.min.scala @@ -0,0 +1,9 @@ + +class P[T] +//class Q[T] extends P[R[T]] // ok +class Q[T <: P[Any]] extends P[R[T]] // error +//type Q[T <: P[Any]] <: P[R[T]] // ok + +type R[U] = U match + case Q[t] => R[t] + case P[t] => t From e3c9bb1e379cb5b51b929ab70e4cd241e6ef60ef Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Thu, 28 Aug 2025 20:16:21 +0200 Subject: [PATCH 037/111] Close #17132 as neg test For some reason, the derivesFrom issue was only observable in the minimization. The original test case diverges similarly to the previous description, but only when attempting normalizing during CodeGen... [Cherry-picked 43af5114bdc969a6f5fb77d3357928cfb24e8355] --- compiler/src/dotty/tools/backend/jvm/CodeGen.scala | 3 +-- tests/neg/i17132.scala | 11 +++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 tests/neg/i17132.scala diff --git a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala index be86f704fa41..71d25d6f0cf2 100644 --- a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala +++ b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala @@ -85,8 +85,7 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( case ex: InterruptedException => throw ex case ex: CompilationUnit.SuspendException => throw ex case ex: Throwable => - ex.printStackTrace() - report.error(s"Error while emitting ${unit.source}\n${ex.getMessage}", NoSourcePosition) + report.error(s"Error while emitting ${unit.source}\n${ex.getMessage}", cd.sourcePos) def genTastyAndSetAttributes(claszSymbol: Symbol, store: ClassNode): Unit = diff --git a/tests/neg/i17132.scala b/tests/neg/i17132.scala new file mode 100644 index 000000000000..d4b97e54293a --- /dev/null +++ b/tests/neg/i17132.scala @@ -0,0 +1,11 @@ + +sealed trait Transformation[T] + +case object Count extends Transformation[Int] +case class MultiTransformation[T1 <: Transformation[?], T2 <: Transformation[?]](t1: T1, t2: T2) // error cyclic + extends Transformation[MultiTransformationResult[T1, T2]] + +type MultiTransformationResult[T1 <: Transformation[?], T2 <: Transformation[?]] <: Tuple = (T1, T2) match { + case (Transformation[t], MultiTransformation[t1, t2]) => t *: MultiTransformationResult[t1, t2] + case (Transformation[t1], Transformation[t2]) => (t1, t2) +} From e8f8c2be73d33e42018254470bbac817e2369e69 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Thu, 28 Aug 2025 20:43:11 +0200 Subject: [PATCH 038/111] Use more `mayDeriveFrom` where appropriate in `provablyDisjoint` [Cherry-picked 470be4705071e8a3b146f6f8aa259eb1bcd6581c] --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 3 +++ compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 6 +++--- compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 268f1d621815..2561aee03842 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -879,6 +879,9 @@ object SymDenotations { final def mayDeriveFrom(base: Symbol)(using Context): Boolean = this.isInstanceOf[ClassDenotation] && (info.isInstanceOf[TempClassInfo] || derivesFrom(base)) + final def derivesFrom(base: Symbol, defaultIfUnknown: Boolean)(using Context): Boolean = + if defaultIfUnknown/*== true*/ then mayDeriveFrom(base) else derivesFrom(base) + /** Is this a Scala or Java annotation ? */ def isAnnotation(using Context): Boolean = isClass && (derivesFrom(defn.AnnotationClass) || is(JavaAnnotation)) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index cc9ba4396fc5..d420590d0c40 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3131,9 +3131,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * unique value derives from the class. */ case (tp1: SingletonType, tp2) => - !tp1.derivesFrom(tp2.classSymbol) + !tp1.derivesFrom(tp2.classSymbol, defaultIfUnknown = true) case (tp1, tp2: SingletonType) => - !tp2.derivesFrom(tp1.classSymbol) + !tp2.derivesFrom(tp1.classSymbol, defaultIfUnknown = true) /* Now both sides are possibly-parameterized class types `p.C[Ts]` and `q.D[Us]`. * @@ -3189,7 +3189,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling val cls2BaseClassSet = SymDenotations.BaseClassSet(cls2.classDenot.baseClasses) val commonBaseClasses = cls1.classDenot.baseClasses.filter(cls2BaseClassSet.contains(_)) def isAncestorOfOtherBaseClass(cls: ClassSymbol): Boolean = - commonBaseClasses.exists(other => (other ne cls) && other.derivesFrom(cls)) + commonBaseClasses.exists(other => (other ne cls) && other.mayDeriveFrom(cls)) val result = commonBaseClasses.exists { baseClass => !isAncestorOfOtherBaseClass(baseClass) && isBaseTypeWithDisjointArguments(baseClass, innerPending) } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 7fac8c818a1a..5b195802f969 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -270,7 +270,7 @@ object Types extends TypeUtils { /** True if this type is an instance of the given `cls` or an instance of * a non-bottom subclass of `cls`. */ - final def derivesFrom(cls: Symbol)(using Context): Boolean = { + final def derivesFrom(cls: Symbol, defaultIfUnknown: Boolean = false)(using Context): Boolean = { def isLowerBottomType(tp: Type) = tp.isBottomType && (tp.hasClassSymbol(defn.NothingClass) @@ -278,7 +278,7 @@ object Types extends TypeUtils { def loop(tp: Type): Boolean = try tp match case tp: TypeRef => val sym = tp.symbol - if (sym.isClass) sym.derivesFrom(cls) else loop(tp.superType) + if (sym.isClass) sym.derivesFrom(cls, defaultIfUnknown) else loop(tp.superType) case tp: AppliedType => tp.superType.derivesFrom(cls) case tp: MatchType => From 13d0a613c611fee47e8421cdb8271e73a279f5df Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 29 Aug 2025 14:42:47 +0200 Subject: [PATCH 039/111] Address review comments [Cherry-picked 546fbd1d18a813bab024aa8fb4a6d9253a229ec9] --- compiler/src/dotty/tools/backend/jvm/CodeGen.scala | 1 + compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala index 71d25d6f0cf2..1e2abbcff866 100644 --- a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala +++ b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala @@ -85,6 +85,7 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( case ex: InterruptedException => throw ex case ex: CompilationUnit.SuspendException => throw ex case ex: Throwable => + if !ex.isInstanceOf[TypeError] then ex.printStackTrace() report.error(s"Error while emitting ${unit.source}\n${ex.getMessage}", cd.sourcePos) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 2561aee03842..9fe08c27a159 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -870,7 +870,7 @@ object SymDenotations { def derivesFrom(base: Symbol)(using Context): Boolean = false /** Could `this` derive from `base` now or in the future. - * For concistency with derivesFrom, The info is only forced when this is a ClassDenotation. + * For concistency with derivesFrom, the info is only forced when this is a ClassDenotation. * If the info is a TempClassInfo then the baseClassSet may be temporarily approximated as empty. * This is problematic when stability of `!derivesFrom(base)` is assumed for soundness, * e.g., in `TypeComparer#provablyDisjointClasses`. From f051252c28d22b9bb4f5a5219a7bfade99421ccf Mon Sep 17 00:00:00 2001 From: zielinsky Date: Sun, 24 Aug 2025 15:48:44 +0200 Subject: [PATCH 040/111] add guard for doing rhs decompostion in computing subspaces [Cherry-picked f3db07a2937b4db2c47e87194363c55e99951726] --- .../dotty/tools/dotc/transform/patmat/Space.scala | 10 +++++++++- tests/pos/i20225.scala | 12 ++++++++++++ tests/pos/i20395.scala | 13 +++++++++++++ tests/run/i20225.check | 1 + tests/run/i20225.scala | 12 ++++++++++++ tests/run/i20395.check | 1 + tests/run/i20395.scala | 13 +++++++++++++ 7 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i20225.scala create mode 100644 tests/pos/i20395.scala create mode 100644 tests/run/i20225.check create mode 100644 tests/run/i20225.scala create mode 100644 tests/run/i20395.check create mode 100644 tests/run/i20395.scala diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index b7e1f349a377..40054d73a357 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -171,6 +171,14 @@ object SpaceEngine { /** Is `a` a subspace of `b`? Equivalent to `simplify(simplify(a) - simplify(b)) == Empty`, but faster */ def computeIsSubspace(a: Space, b: Space)(using Context): Boolean = trace(i"isSubspace($a, $b)") { + /** Is decomposition allowed on the right-hand side of a pattern? */ + /** We only allow decomposition on the right-hand side of a pattern if the type is not a type parameter, a type parameter reference, or a deferred type reference */ + /** This is because decomposition on the right-hand side of a pattern can lead to false positive warnings */ + inline def rhsDecompositionAllowed(tp: Type): Boolean = tp.dealias match + case _: TypeParamRef => false + case tr: TypeRef if tr.symbol.is(TypeParam) || (tr.symbol.is(Deferred) && !tr.symbol.isClass) => false + case _ => true + val a2 = simplify(a) val b2 = simplify(b) if (a ne a2) || (b ne b2) then isSubspace(a2, b2) @@ -185,7 +193,7 @@ object SpaceEngine { case (a @ Typ(tp1, _), b @ Typ(tp2, _)) => isSubType(tp1, tp2) || canDecompose(a) && isSubspace(Or(decompose(a)), b) - || canDecompose(b) && isSubspace(a, Or(decompose(b))) + || (canDecompose(b) && rhsDecompositionAllowed(tp2)) && isSubspace(a, Or(decompose(b))) case (Prod(tp1, _, _), Typ(tp2, _)) => isSubType(tp1, tp2) case (a @ Typ(tp1, _), Prod(tp2, fun, ss)) => diff --git a/tests/pos/i20225.scala b/tests/pos/i20225.scala new file mode 100644 index 000000000000..3704aa2034b5 --- /dev/null +++ b/tests/pos/i20225.scala @@ -0,0 +1,12 @@ +sealed abstract class Parent +class A extends Parent +class B extends Parent + +inline def matchAs[T <: Parent](p: Parent): Unit = p match + case _: T => () + case _ => () + +object Test: + def main(args: Array[String]): Unit = + matchAs[A](new B) + diff --git a/tests/pos/i20395.scala b/tests/pos/i20395.scala new file mode 100644 index 000000000000..3b0be1e31b5a --- /dev/null +++ b/tests/pos/i20395.scala @@ -0,0 +1,13 @@ +sealed trait NodeId +case object Hi extends NodeId +case object Hello extends NodeId + +extension (value: NodeId) + inline def parse[T <: NodeId] = value match + case _: T => () + case _ => () + +object Test: + def main(args: Array[String]): Unit = + Hi.parse[Hello.type] + diff --git a/tests/run/i20225.check b/tests/run/i20225.check new file mode 100644 index 000000000000..0f06315755de --- /dev/null +++ b/tests/run/i20225.check @@ -0,0 +1 @@ +unreachable case reached diff --git a/tests/run/i20225.scala b/tests/run/i20225.scala new file mode 100644 index 000000000000..ad9babf832f7 --- /dev/null +++ b/tests/run/i20225.scala @@ -0,0 +1,12 @@ +sealed abstract class Parent +class A extends Parent +class B extends Parent + +inline def matchAs[T <: Parent](p: Parent): Unit = p match + case _: T => () + case _ => println("unreachable case reached") + +object Test: + def main(args: Array[String]): Unit = + matchAs[A](new B) + diff --git a/tests/run/i20395.check b/tests/run/i20395.check new file mode 100644 index 000000000000..60653bdaa7f2 --- /dev/null +++ b/tests/run/i20395.check @@ -0,0 +1 @@ +not match diff --git a/tests/run/i20395.scala b/tests/run/i20395.scala new file mode 100644 index 000000000000..e432ea4bca6b --- /dev/null +++ b/tests/run/i20395.scala @@ -0,0 +1,13 @@ +sealed trait NodeId +case object Hi extends NodeId +case object Hello extends NodeId + +extension (value: NodeId) + inline def parse[T <: NodeId]: Unit = value match + case _: T => println("match") + case _ => println("not match") + +object Test: + def main(args: Array[String]): Unit = + Hi.parse[Hello.type] + From 7cc34bb8fa6dd99851eacc646c9458f82392e7a6 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 28 Aug 2025 10:53:37 -0700 Subject: [PATCH 041/111] Mention named givens in double def explainer Print info at typer in example code. Could be automatic. [Cherry-picked 912fce1104bc18c93bcb580477a7dada28c1d68a] --- .../dotty/tools/dotc/reporting/messages.scala | 27 +++++++---- tests/neg/i23350.check | 3 +- tests/neg/i23402.check | 3 +- tests/neg/i23832a.check | 46 +++++++++++++++++++ tests/neg/i23832a.scala | 9 ++++ tests/neg/i23832b.check | 46 +++++++++++++++++++ tests/neg/i23832b.scala | 9 ++++ 7 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 tests/neg/i23832a.check create mode 100644 tests/neg/i23832a.scala create mode 100644 tests/neg/i23832b.check create mode 100644 tests/neg/i23832b.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 68a5ce21f42b..75e74cf16e16 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2364,7 +2364,7 @@ class SymbolIsNotAValue(symbol: Symbol)(using Context) extends TypeMsg(SymbolIsN } class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(using Context) -extends NamingMsg(DoubleDefinitionID) { +extends NamingMsg(DoubleDefinitionID): import Signature.MatchDegree.* private def erasedType: Type = @@ -2426,6 +2426,16 @@ extends NamingMsg(DoubleDefinitionID) { } + details } def explain(using Context) = + def givenAddendum = + def isGivenName(sym: Symbol) = sym.name.startsWith("given_") // Desugar.inventGivenName + if decl.is(Given) && previousDecl.is(Given) && isGivenName(decl) && isGivenName(previousDecl) then + i""" + |3. Provide an explicit, unique name to given definitions, since the names + | assigned to anonymous givens may clash. For example: + | + | given myGiven: ${atPhase(typerPhase)(decl.info)} + |""" + else "" decl.signature.matchDegree(previousDecl.signature) match case FullMatch => i""" @@ -2439,8 +2449,8 @@ extends NamingMsg(DoubleDefinitionID) { | |In your code the two declarations | - | ${previousDecl.showDcl} - | ${decl.showDcl} + | ${atPhase(typerPhase)(previousDecl.showDcl)} + | ${atPhase(typerPhase)(decl.showDcl)} | |erase to the identical signature | @@ -2452,17 +2462,16 @@ extends NamingMsg(DoubleDefinitionID) { | |1. Rename one of the definitions, or |2. Keep the same names in source but give one definition a distinct - | bytecode-level name via `@targetName` for example: + | bytecode-level name via `@targetName`; for example: | | @targetName("${decl.name.show}_2") - | ${decl.showDcl} - | + | ${atPhase(typerPhase)(decl.showDcl)} + |$givenAddendum |Choose the `@targetName` argument carefully: it is the name that will be used |when calling the method externally, so it should be unique and descriptive. - """ + |""" case _ => "" - -} +end DoubleDefinition class ImportedTwice(sel: Name)(using Context) extends SyntaxMsg(ImportedTwiceID) { def msg(using Context) = s"${sel.show} is imported twice on the same import line." diff --git a/tests/neg/i23350.check b/tests/neg/i23350.check index d9ae6a99cdca..801b13aeec77 100644 --- a/tests/neg/i23350.check +++ b/tests/neg/i23350.check @@ -35,12 +35,11 @@ | | 1. Rename one of the definitions, or | 2. Keep the same names in source but give one definition a distinct - | bytecode-level name via `@targetName` for example: + | bytecode-level name via `@targetName`; for example: | | @targetName("apply_2") | def apply(a: UndefOr2[String]): Unit | | Choose the `@targetName` argument carefully: it is the name that will be used | when calling the method externally, so it should be unique and descriptive. - | --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23402.check b/tests/neg/i23402.check index 4a98af863348..a23221e660ed 100644 --- a/tests/neg/i23402.check +++ b/tests/neg/i23402.check @@ -35,12 +35,11 @@ | | 1. Rename one of the definitions, or | 2. Keep the same names in source but give one definition a distinct - | bytecode-level name via `@targetName` for example: + | bytecode-level name via `@targetName`; for example: | | @targetName("apply_2") | def apply(p1: String)(p2: Int): A | | Choose the `@targetName` argument carefully: it is the name that will be used | when calling the method externally, so it should be unique and descriptive. - | --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23832a.check b/tests/neg/i23832a.check new file mode 100644 index 000000000000..aa432a836fe3 --- /dev/null +++ b/tests/neg/i23832a.check @@ -0,0 +1,46 @@ +-- [E120] Naming Error: tests/neg/i23832a.scala:9:8 -------------------------------------------------------------------- +9 | given Special[Option[Int]] = ??? // error + | ^ + | Conflicting definitions: + | final lazy given val given_Special_Option: Special[Option[Long]] in object syntax at line 8 and + | final lazy given val given_Special_Option: Special[Option[Int]] in object syntax at line 9 + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | + | As part of the Scala compilation pipeline every type is reduced to its erased + | (runtime) form. In this phase, among other transformations, generic parameters + | disappear and separate parameter-list boundaries are flattened. + | + | For example, both `f[T](x: T)(y: String): Unit` and `f(x: Any, z: String): Unit` + | erase to the same runtime signature `f(x: Object, y: String): Unit`. Note that + | parameter names are irrelevant. + | + | In your code the two declarations + | + | final lazy given val given_Special_Option: Special[Option[Long]] + | final lazy given val given_Special_Option: Special[Option[Int]] + | + | erase to the identical signature + | + | Special + | + | so the compiler cannot keep both: the generated bytecode symbols would collide. + | + | To fix this error, you need to disambiguate the two definitions. You can either: + | + | 1. Rename one of the definitions, or + | 2. Keep the same names in source but give one definition a distinct + | bytecode-level name via `@targetName`; for example: + | + | @targetName("given_Special_Option_2") + | final lazy given val given_Special_Option: Special[Option[Int]] + | + | 3. Provide an explicit, unique name to given definitions, since the names + | assigned to anonymous givens may clash. For example: + | + | given myGiven: Special[Option[Int]] + | + | Choose the `@targetName` argument carefully: it is the name that will be used + | when calling the method externally, so it should be unique and descriptive. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23832a.scala b/tests/neg/i23832a.scala new file mode 100644 index 000000000000..5020c998ee96 --- /dev/null +++ b/tests/neg/i23832a.scala @@ -0,0 +1,9 @@ +//> using options -explain + +// follow-up to neg/i23402*.scala + +trait Special[A] + +object syntax: + given Special[Option[Long]] = ??? + given Special[Option[Int]] = ??? // error diff --git a/tests/neg/i23832b.check b/tests/neg/i23832b.check new file mode 100644 index 000000000000..32772cbb9b2f --- /dev/null +++ b/tests/neg/i23832b.check @@ -0,0 +1,46 @@ +-- [E120] Naming Error: tests/neg/i23832b.scala:9:8 -------------------------------------------------------------------- +9 | given [A] => Special[Option[A]] = ??? // error + | ^ + | Conflicting definitions: + | final lazy given val given_Special_Option: Special[Option[Long]] in object syntax at line 8 and + | final given def given_Special_Option[A]: Special[Option[A]] in object syntax at line 9 + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | + | As part of the Scala compilation pipeline every type is reduced to its erased + | (runtime) form. In this phase, among other transformations, generic parameters + | disappear and separate parameter-list boundaries are flattened. + | + | For example, both `f[T](x: T)(y: String): Unit` and `f(x: Any, z: String): Unit` + | erase to the same runtime signature `f(x: Object, y: String): Unit`. Note that + | parameter names are irrelevant. + | + | In your code the two declarations + | + | final lazy given val given_Special_Option: Special[Option[Long]] + | final given def given_Special_Option[A]: Special[Option[A]] + | + | erase to the identical signature + | + | (): Special + | + | so the compiler cannot keep both: the generated bytecode symbols would collide. + | + | To fix this error, you need to disambiguate the two definitions. You can either: + | + | 1. Rename one of the definitions, or + | 2. Keep the same names in source but give one definition a distinct + | bytecode-level name via `@targetName`; for example: + | + | @targetName("given_Special_Option_2") + | final given def given_Special_Option[A]: Special[Option[A]] + | + | 3. Provide an explicit, unique name to given definitions, since the names + | assigned to anonymous givens may clash. For example: + | + | given myGiven: [A]: Special[Option[A]] + | + | Choose the `@targetName` argument carefully: it is the name that will be used + | when calling the method externally, so it should be unique and descriptive. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23832b.scala b/tests/neg/i23832b.scala new file mode 100644 index 000000000000..6e43ed008047 --- /dev/null +++ b/tests/neg/i23832b.scala @@ -0,0 +1,9 @@ +//> using options -explain + +// follow-up to neg/i23402*.scala + +trait Special[A] + +object syntax: + given Special[Option[Long]] = ??? + given [A] => Special[Option[A]] = ??? // error From a6115ba89ba461039ff1b17dadb513272ef6a055 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 3 Sep 2025 08:32:34 -0700 Subject: [PATCH 042/111] Naming anon givens is a fix by renaming [Cherry-picked 6fe3abcd417e32bc22e7651360ce6d39c7c44e7a] --- .../dotty/tools/dotc/reporting/messages.scala | 27 ++++++++++++------- tests/neg/i23350.check | 4 +-- tests/neg/i23402.check | 4 +-- tests/neg/i23832a.check | 13 +++++---- tests/neg/i23832b.check | 13 +++++---- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 75e74cf16e16..5a9d5e8b5cd4 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2428,13 +2428,22 @@ extends NamingMsg(DoubleDefinitionID): def explain(using Context) = def givenAddendum = def isGivenName(sym: Symbol) = sym.name.startsWith("given_") // Desugar.inventGivenName + def print(tpe: Type): String = + def addParams(tpe: Type): List[String] = tpe match + case tpe: MethodType => + val s = if tpe.isContextualMethod then i"(${tpe.paramInfos}%, %) =>" else "" + s :: addParams(tpe.resType) + case tpe: PolyType => + i"[${tpe.paramNames}%, %] =>" :: addParams(tpe.resType) + case tpe => + i"$tpe" :: Nil + addParams(tpe).mkString(" ") if decl.is(Given) && previousDecl.is(Given) && isGivenName(decl) && isGivenName(previousDecl) then - i""" - |3. Provide an explicit, unique name to given definitions, since the names - | assigned to anonymous givens may clash. For example: - | - | given myGiven: ${atPhase(typerPhase)(decl.info)} - |""" + i"""| Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: ${print(atPhase(typerPhase)(decl.info))} + |""" else "" decl.signature.matchDegree(previousDecl.signature) match case FullMatch => @@ -2458,15 +2467,15 @@ extends NamingMsg(DoubleDefinitionID): | |so the compiler cannot keep both: the generated bytecode symbols would collide. | - |To fix this error, you need to disambiguate the two definitions. You can either: + |To fix this error, you must disambiguate the two definitions by doing one of the following: | - |1. Rename one of the definitions, or + |1. Rename one of the definitions.$givenAddendum |2. Keep the same names in source but give one definition a distinct | bytecode-level name via `@targetName`; for example: | | @targetName("${decl.name.show}_2") | ${atPhase(typerPhase)(decl.showDcl)} - |$givenAddendum + | |Choose the `@targetName` argument carefully: it is the name that will be used |when calling the method externally, so it should be unique and descriptive. |""" diff --git a/tests/neg/i23350.check b/tests/neg/i23350.check index 801b13aeec77..ac64b3d22c1e 100644 --- a/tests/neg/i23350.check +++ b/tests/neg/i23350.check @@ -31,9 +31,9 @@ | | so the compiler cannot keep both: the generated bytecode symbols would collide. | - | To fix this error, you need to disambiguate the two definitions. You can either: + | To fix this error, you must disambiguate the two definitions by doing one of the following: | - | 1. Rename one of the definitions, or + | 1. Rename one of the definitions. | 2. Keep the same names in source but give one definition a distinct | bytecode-level name via `@targetName`; for example: | diff --git a/tests/neg/i23402.check b/tests/neg/i23402.check index a23221e660ed..b258ab79e75c 100644 --- a/tests/neg/i23402.check +++ b/tests/neg/i23402.check @@ -31,9 +31,9 @@ | | so the compiler cannot keep both: the generated bytecode symbols would collide. | - | To fix this error, you need to disambiguate the two definitions. You can either: + | To fix this error, you must disambiguate the two definitions by doing one of the following: | - | 1. Rename one of the definitions, or + | 1. Rename one of the definitions. | 2. Keep the same names in source but give one definition a distinct | bytecode-level name via `@targetName`; for example: | diff --git a/tests/neg/i23832a.check b/tests/neg/i23832a.check index aa432a836fe3..6886327484c3 100644 --- a/tests/neg/i23832a.check +++ b/tests/neg/i23832a.check @@ -27,20 +27,19 @@ | | so the compiler cannot keep both: the generated bytecode symbols would collide. | - | To fix this error, you need to disambiguate the two definitions. You can either: + | To fix this error, you must disambiguate the two definitions by doing one of the following: + | + | 1. Rename one of the definitions. Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: Special[Option[Int]] | - | 1. Rename one of the definitions, or | 2. Keep the same names in source but give one definition a distinct | bytecode-level name via `@targetName`; for example: | | @targetName("given_Special_Option_2") | final lazy given val given_Special_Option: Special[Option[Int]] | - | 3. Provide an explicit, unique name to given definitions, since the names - | assigned to anonymous givens may clash. For example: - | - | given myGiven: Special[Option[Int]] - | | Choose the `@targetName` argument carefully: it is the name that will be used | when calling the method externally, so it should be unique and descriptive. --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23832b.check b/tests/neg/i23832b.check index 32772cbb9b2f..82cb54044449 100644 --- a/tests/neg/i23832b.check +++ b/tests/neg/i23832b.check @@ -27,20 +27,19 @@ | | so the compiler cannot keep both: the generated bytecode symbols would collide. | - | To fix this error, you need to disambiguate the two definitions. You can either: + | To fix this error, you must disambiguate the two definitions by doing one of the following: + | + | 1. Rename one of the definitions. Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: [A] => Special[Option[A]] | - | 1. Rename one of the definitions, or | 2. Keep the same names in source but give one definition a distinct | bytecode-level name via `@targetName`; for example: | | @targetName("given_Special_Option_2") | final given def given_Special_Option[A]: Special[Option[A]] | - | 3. Provide an explicit, unique name to given definitions, since the names - | assigned to anonymous givens may clash. For example: - | - | given myGiven: [A]: Special[Option[A]] - | | Choose the `@targetName` argument carefully: it is the name that will be used | when calling the method externally, so it should be unique and descriptive. --------------------------------------------------------------------------------------------------------------------- From b43d82c14a8a9929c47150d67128b930987fe40c Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Sun, 21 Sep 2025 00:08:31 +0200 Subject: [PATCH 043/111] Add warnings for flexible types in public methods and fields [Cherry-picked 72c545e9280582cefcbdf2743513c4397637b8a0][modified] --- .../src/dotty/tools/MainGenericCompiler.scala | 2 +- .../src/dotty/tools/MainGenericRunner.scala | 2 +- .../src/dotty/tools/backend/jvm/CodeGen.scala | 2 +- .../dotc/interactive/InteractiveDriver.scala | 2 +- .../src/dotty/tools/dotc/sbt/APIUtils.scala | 8 ++-- .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 14 +++--- .../src/dotty/tools/dotc/typer/Typer.scala | 25 ++++++++-- compiler/src/dotty/tools/io/Path.scala | 4 +- .../src/dotty/tools/repl/JLineTerminal.scala | 2 +- .../src/dotty/tools/repl/ScriptEngine.scala | 13 ++--- .../besteffort/BestEffortTastyFormat.scala | 2 +- .../dotty/tools/pc/InferExpectedType.scala | 4 +- .../dotty/tools/pc/PcInlayHintsProvider.scala | 30 ++++++------ .../tools/pc/ScalaPresentationCompiler.scala | 2 +- .../dotty/tools/pc/WithCompilationUnit.scala | 10 ++-- .../tools/pc/completions/Completions.scala | 23 ++++++++- .../warn/expose-flexible-types.check | 48 +++++++++++++++++++ .../warn/expose-flexible-types.scala | 36 ++++++++++++++ .../explicit-nulls/warn/unnecessary-nn.scala | 2 +- 19 files changed, 177 insertions(+), 54 deletions(-) create mode 100644 tests/explicit-nulls/warn/expose-flexible-types.check create mode 100644 tests/explicit-nulls/warn/expose-flexible-types.scala diff --git a/compiler/src/dotty/tools/MainGenericCompiler.scala b/compiler/src/dotty/tools/MainGenericCompiler.scala index 2c3f6f97e79e..98bd9078c397 100644 --- a/compiler/src/dotty/tools/MainGenericCompiler.scala +++ b/compiler/src/dotty/tools/MainGenericCompiler.scala @@ -85,7 +85,7 @@ case class CompileSettings( object MainGenericCompiler { - val classpathSeparator = File.pathSeparator + val classpathSeparator: String = File.pathSeparator @sharable val javaOption = raw"""-J(.*)""".r @sharable val javaPropOption = raw"""-D(.+?)=(.?)""".r diff --git a/compiler/src/dotty/tools/MainGenericRunner.scala b/compiler/src/dotty/tools/MainGenericRunner.scala index b32630a5d63b..9ff96f812bca 100644 --- a/compiler/src/dotty/tools/MainGenericRunner.scala +++ b/compiler/src/dotty/tools/MainGenericRunner.scala @@ -96,7 +96,7 @@ case class Settings( object MainGenericRunner { - val classpathSeparator = File.pathSeparator + val classpathSeparator: String = File.pathSeparator def processClasspath(cp: String, tail: List[String]): (List[String], List[String]) = val cpEntries = cp.split(classpathSeparator).toList diff --git a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala index 1e2abbcff866..093d4f997aa7 100644 --- a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala +++ b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala @@ -152,7 +152,7 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( new interfaces.AbstractFile { override def name = absfile.name override def path = absfile.path - override def jfile = Optional.ofNullable(absfile.file) + override def jfile: Optional[java.io.File] = Optional.ofNullable(absfile.file) } private def genClass(cd: TypeDef, unit: CompilationUnit): ClassNode = { diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala index 673874ae2769..40c6667fdd24 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala @@ -234,7 +234,7 @@ class InteractiveDriver(val settings: List[String]) extends Driver { private def classesFromDir(dir: Path, buffer: mutable.ListBuffer[TypeName]): Unit = try Files.walkFileTree(dir, new SimpleFileVisitor[Path] { - override def visitFile(path: Path, attrs: BasicFileAttributes) = { + override def visitFile(path: Path, attrs: BasicFileAttributes): FileVisitResult = { if (!attrs.isDirectory) { val name = path.getFileName.toString if name.endsWith(tastySuffix) then diff --git a/compiler/src/dotty/tools/dotc/sbt/APIUtils.scala b/compiler/src/dotty/tools/dotc/sbt/APIUtils.scala index 4ee1fd0f6b68..42843a89c0ab 100644 --- a/compiler/src/dotty/tools/dotc/sbt/APIUtils.scala +++ b/compiler/src/dotty/tools/dotc/sbt/APIUtils.scala @@ -17,10 +17,10 @@ import xsbti.api.SafeLazy.strict */ object APIUtils { private object Constants { - val PublicAccess = api.Public.create() - val EmptyModifiers = new api.Modifiers(false, false, false, false, false, false, false, false) - val EmptyStructure = api.Structure.of(strict(Array.empty), strict(Array.empty), strict(Array.empty)) - val EmptyType = api.EmptyType.of() + val PublicAccess: api.Public = api.Public.create() + val EmptyModifiers: api.Modifiers = new api.Modifiers(false, false, false, false, false, false, false, false) + val EmptyStructure: api.Structure = api.Structure.of(strict(Array.empty), strict(Array.empty), strict(Array.empty)) + val EmptyType: api.EmptyType = api.EmptyType.of() } import Constants.* diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 4d915b57df1b..5f3eeff91ff3 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -232,13 +232,13 @@ private class ExtractAPICollector(nonLocalClassSymbols: mutable.HashSet[Symbol]) private object Constants { val emptyStringArray = Array[String]() - val local = api.ThisQualifier.create() - val public = api.Public.create() - val privateLocal = api.Private.create(local) - val protectedLocal = api.Protected.create(local) - val unqualified = api.Unqualified.create() - val thisPath = api.This.create() - val emptyType = api.EmptyType.create() + val local: api.ThisQualifier = api.ThisQualifier.create() + val public: api.Public = api.Public.create() + val privateLocal: api.Private = api.Private.create(local) + val protectedLocal: api.Protected = api.Protected.create(local) + val unqualified: api.Unqualified = api.Unqualified.create() + val thisPath: api.This = api.This.create() + val emptyType: api.EmptyType = api.EmptyType.create() val emptyModifiers = new api.Modifiers(false, false, false, false, false,false, false, false) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e9e3e22342bf..288fd95be777 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3083,13 +3083,32 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer //todo: make sure dependent method types do not depend on implicits or by-name params } - /** (1) Check that the signature of the class member does not return a repeated parameter type - * (2) Make sure the definition's symbol is `sym`. - * (3) Set the `defTree` of `sym` to be `mdef`. + /** (1) Check that the signature of the class member does not return a repeated parameter type. + * (2) Check that the signature of the public class member does not expose a flexible type. + * (3) Make sure the definition's symbol is `sym`. + * (4) Set the `defTree` of `sym` to be `mdef`. */ private def postProcessInfo(mdef: MemberDef, sym: Symbol)(using Context): MemberDef = if (!sym.isOneOf(Synthetic | InlineProxy | Param) && sym.info.finalResultType.isRepeatedParam) report.error(em"Cannot return repeated parameter type ${sym.info.finalResultType}", sym.srcPos) + + // Warn if a public method/field exposes FlexibleType in its result type under explicit nulls + // and encourage explicit annotation. + if ctx.phase.isTyper && ctx.explicitNulls && !ctx.isJava + && sym.exists && sym.isPublic && sym.owner.isClass + && !sym.isOneOf(Synthetic | InlineProxy | Param) then + val resTp = sym.info.finalResultType + if resTp.existsPart(_.isInstanceOf[FlexibleType], StopAt.Static) then + val suggestion = resTp match + case ft: FlexibleType => + val hi = ft.hi + i"Consider annotating the type as ${hi} or ${hi} | Null explicitly" + case _ => "Consider annotating the type explicitly" + report.warning( + em"Public ${if sym.is(Method) then "method" else "field"} ${sym.show} exposes a flexible type ${resTp} in its inferred signature. $suggestion", + sym.srcPos + ) + mdef.ensureHasSym(sym) mdef.setDefTree diff --git a/compiler/src/dotty/tools/io/Path.scala b/compiler/src/dotty/tools/io/Path.scala index 39665395c289..64137d691898 100644 --- a/compiler/src/dotty/tools/io/Path.scala +++ b/compiler/src/dotty/tools/io/Path.scala @@ -245,12 +245,12 @@ class Path private[io] (val jpath: JPath) { if (!exists) false else { Files.walkFileTree(jpath, new SimpleFileVisitor[JPath]() { - override def visitFile(file: JPath, attrs: BasicFileAttributes) = { + override def visitFile(file: JPath, attrs: BasicFileAttributes): FileVisitResult = { Files.delete(file) FileVisitResult.CONTINUE } - override def postVisitDirectory(dir: JPath, exc: IOException) = { + override def postVisitDirectory(dir: JPath, exc: IOException): FileVisitResult = { Files.delete(dir) FileVisitResult.CONTINUE } diff --git a/compiler/src/dotty/tools/repl/JLineTerminal.scala b/compiler/src/dotty/tools/repl/JLineTerminal.scala index e4ac1626525e..2680704d326e 100644 --- a/compiler/src/dotty/tools/repl/JLineTerminal.scala +++ b/compiler/src/dotty/tools/repl/JLineTerminal.scala @@ -106,7 +106,7 @@ class JLineTerminal extends java.io.Closeable { ) extends reader.ParsedLine { // Using dummy values, not sure what they are used for def wordIndex = -1 - def words = java.util.Collections.emptyList[String] + def words: java.util.List[String] = java.util.Collections.emptyList[String] } def parse(input: String, cursor: Int, context: ParseContext): reader.ParsedLine = { diff --git a/compiler/src/dotty/tools/repl/ScriptEngine.scala b/compiler/src/dotty/tools/repl/ScriptEngine.scala index 7d385daa43e4..cce16000577f 100644 --- a/compiler/src/dotty/tools/repl/ScriptEngine.scala +++ b/compiler/src/dotty/tools/repl/ScriptEngine.scala @@ -64,20 +64,21 @@ class ScriptEngine extends AbstractScriptEngine { object ScriptEngine { import java.util.Arrays + import java.util.List import scala.util.Properties class Factory extends ScriptEngineFactory { def getEngineName = "Scala REPL" def getEngineVersion = "3.0" - def getExtensions = Arrays.asList("scala") + def getExtensions: List[String] = Arrays.asList("scala") def getLanguageName = "Scala" def getLanguageVersion = Properties.versionString - def getMimeTypes = Arrays.asList("application/x-scala") - def getNames = Arrays.asList("scala") + def getMimeTypes: List[String] = Arrays.asList("application/x-scala") + def getNames: List[String] = Arrays.asList("scala") - def getMethodCallSyntax(obj: String, m: String, args: String*) = s"$obj.$m(${args.mkString(", ")})" + def getMethodCallSyntax(obj: String, m: String, args: String*): String = s"$obj.$m(${args.mkString(", ")})" - def getOutputStatement(toDisplay: String) = s"""print("$toDisplay")""" + def getOutputStatement(toDisplay: String): String = s"""print("$toDisplay")""" def getParameter(key: String): Object = key match { case JScriptEngine.ENGINE => getEngineName @@ -88,7 +89,7 @@ object ScriptEngine { case _ => null } - def getProgram(statements: String*) = statements.mkString("; ") + def getProgram(statements: String*): String = statements.mkString("; ") def getScriptEngine: JScriptEngine = new ScriptEngine } diff --git a/compiler/src/dotty/tools/tasty/besteffort/BestEffortTastyFormat.scala b/compiler/src/dotty/tools/tasty/besteffort/BestEffortTastyFormat.scala index 99a24ce5f346..41876016b4ec 100644 --- a/compiler/src/dotty/tools/tasty/besteffort/BestEffortTastyFormat.scala +++ b/compiler/src/dotty/tools/tasty/besteffort/BestEffortTastyFormat.scala @@ -38,7 +38,7 @@ object BestEffortTastyFormat { // added AST tag - Best Effort TASTy only final val ERRORtype = 50 - def astTagToString(tag: Int) = tag match { + def astTagToString(tag: Int): String = tag match { case ERRORtype => "ERRORtype" case _ => TastyFormat.astTagToString(tag) } diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala index 8640f518c0f1..2aecbb7b36b6 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala @@ -26,8 +26,8 @@ class InferExpectedType( driver: InteractiveDriver, params: OffsetParams )(implicit rc: ReportContext): - val uri = params.uri().nn - val code = params.text().nn + val uri: java.net.URI = params.uri() + val code: String = params.text() val sourceFile = SourceFile.virtual(uri, code) driver.run(uri, sourceFile) diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala index 29396a5c0d32..395548822d96 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala @@ -39,11 +39,11 @@ class PcInlayHintsProvider( symbolSearch: SymbolSearch, )(using ReportContext): - val uri = params.uri().nn - val filePath = Paths.get(uri).nn - val sourceText = params.text().nn - val text = sourceText.toCharArray().nn - val source = + val uri: java.net.URI = params.uri() + val filePath: java.nio.file.Path = Paths.get(uri) + val sourceText: String = params.text() + val text: Array[Char] = sourceText.toCharArray() + val source: SourceFile = SourceFile.virtual(filePath.toString, sourceText) driver.run(uri, source) given InlayHintsParams = params @@ -119,7 +119,7 @@ class PcInlayHintsProvider( InlayHintKind.Type, ) .addDefinition(adjustedPos.start) - case Parameters(isInfixFun, args) => + case Parameters(isInfixFun, args) => def isNamedParam(pos: SourcePosition): Boolean = val start = text.indexWhere(!_.isWhitespace, pos.start) val end = text.lastIndexWhere(!_.isWhitespace, pos.end - 1) @@ -142,9 +142,9 @@ class PcInlayHintsProvider( case (ih, (name, pos0, isByName)) => val pos = adjustPos(pos0) val isBlock = isBlockParam(pos) - val namedLabel = + val namedLabel = if params.namedParameters() && !isInfixFun && !isBlock && !isNamedParam(pos) then s"${name} = " else "" - val byNameLabel = + val byNameLabel = if params.byNameParameters() && isByName && (!isInfixFun || isBlock) then "=> " else "" val labelStr = s"${namedLabel}${byNameLabel}" @@ -432,19 +432,19 @@ object InferredType: end InferredType object Parameters: - def unapply(tree: Tree)(using params: InlayHintsParams, ctx: Context): Option[(Boolean, List[(Name, SourcePosition, Boolean)])] = - def shouldSkipFun(fun: Tree)(using Context): Boolean = + def unapply(tree: Tree)(using params: InlayHintsParams, ctx: Context): Option[(Boolean, List[(Name, SourcePosition, Boolean)])] = + def shouldSkipFun(fun: Tree)(using Context): Boolean = fun match case sel: Select => isForComprehensionMethod(sel) || sel.symbol.name == nme.unapply || sel.symbol.is(Flags.JavaDefined) case _ => false - def isInfixFun(fun: Tree, args: List[Tree])(using Context): Boolean = + def isInfixFun(fun: Tree, args: List[Tree])(using Context): Boolean = val isInfixSelect = fun match case Select(sel, _) => sel.isInfix case _ => false val source = fun.source if args.isEmpty then isInfixSelect - else + else (!(fun.span.end until args.head.span.start) .map(source.apply) .contains('.') && fun.symbol.is(Flags.ExtensionMethod)) || isInfixSelect @@ -467,7 +467,7 @@ object Parameters: if (params.namedParameters() || params.byNameParameters()) then tree match - case Apply(fun, args) if isRealApply(fun) => + case Apply(fun, args) if isRealApply(fun) => val underlyingFun = getUnderlyingFun(fun) if shouldSkipFun(underlyingFun) then None @@ -475,7 +475,7 @@ object Parameters: val funTp = fun.typeOpt.widenTermRefExpr val paramNames = funTp.paramNamess.flatten val paramInfos = funTp.paramInfoss.flatten - + Some( isInfixFun(fun, args) || underlyingFun.isInfix, ( @@ -483,7 +483,7 @@ object Parameters: .zip(paramNames) .zip(paramInfos) .collect { - case ((arg, paramName), paramInfo) if !arg.span.isZeroExtent && !isDefaultArg(arg) => + case ((arg, paramName), paramInfo) if !arg.span.isZeroExtent && !isDefaultArg(arg) => (paramName.fieldName, arg.sourcePos, paramInfo.isByName) } ) diff --git a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala index 18311d1b7853..81517524e3a6 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala @@ -119,7 +119,7 @@ case class ScalaPresentationCompiler( ): PresentationCompiler = copy(completionItemPriority = priority) - override def withBuildTargetName(buildTargetName: String) = + override def withBuildTargetName(buildTargetName: String): PresentationCompiler = copy(buildTargetName = Some(buildTargetName)) override def withReportsLoggerLevel(level: String): PresentationCompiler = diff --git a/presentation-compiler/src/main/dotty/tools/pc/WithCompilationUnit.scala b/presentation-compiler/src/main/dotty/tools/pc/WithCompilationUnit.scala index 56be6614bbd4..5ba89324fca8 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/WithCompilationUnit.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/WithCompilationUnit.scala @@ -22,11 +22,11 @@ class WithCompilationUnit( val driver: InteractiveDriver, params: VirtualFileParams, ): - val uri = params.uri() - val filePath = Paths.get(uri) - val sourceText = params.text - val text = sourceText.toCharArray() - val source = + val uri: java.net.URI = params.uri() + val filePath: java.nio.file.Path = Paths.get(uri) + val sourceText: String = params.text + val text: Array[Char] = sourceText.toCharArray() + val source: SourceFile = SourceFile.virtual(filePath.toString, sourceText) driver.run(uri, source) given ctx: Context = driver.currentCtx diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala index b396dd780cc0..bbf8fc522a5d 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -74,7 +74,15 @@ class Completions( case tpe :: (appl: AppliedTypeTree) :: _ if appl.tpt == tpe => false case sel :: (funSel @ Select(fun, name)) :: (appl: GenericApply) :: _ if appl.fun == funSel && sel == fun => false - case _ => true) + case _ => true) && + (adjustedPath match + /* In case of `class X derives TC@@` we shouldn't add `[]` + */ + case Ident(_) :: (templ: untpd.DerivingTemplate) :: _ => + val pos = completionPos.toSourcePosition + !templ.derived.exists(_.sourcePos.contains(pos)) + case _ => true + ) private lazy val isNew: Boolean = Completion.isInNewContext(adjustedPath) @@ -200,6 +208,17 @@ class Completions( suffix.withNewSuffixSnippet(Affix(SuffixKind.Bracket)) else suffix } + .chain{ suffix => + adjustedPath match + case (ident: Ident) :: (app@Apply(_, List(arg))) :: _ => + app.symbol.info match + case mt@MethodType(termNames) if app.symbol.paramSymss.last.exists(_.is(Given)) && + !text.substring(app.fun.span.start, arg.span.end).contains("using") => + suffix.withNewPrefix(Affix(PrefixKind.Using)) + case _ => suffix + case _ => suffix + + } .chain { suffix => // for () suffix if shouldAddSuffix && symbol.is(Flags.Method) then val paramss = getParams(symbol) @@ -888,7 +907,7 @@ class Completions( application: CompletionApplication ): Ordering[CompletionValue] = new Ordering[CompletionValue]: - val queryLower = completionPos.query.toLowerCase() + val queryLower: String = completionPos.query.toLowerCase() val fuzzyCache = mutable.Map.empty[CompletionValue, Int] def compareLocalSymbols(s1: Symbol, s2: Symbol): Int = diff --git a/tests/explicit-nulls/warn/expose-flexible-types.check b/tests/explicit-nulls/warn/expose-flexible-types.check new file mode 100644 index 000000000000..9cf8cd78157e --- /dev/null +++ b/tests/explicit-nulls/warn/expose-flexible-types.check @@ -0,0 +1,48 @@ +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:2:6 -------------------------------------------------- +2 | def f = s.trim // warn + | ^ + |Public method method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:6:6 -------------------------------------------------- +6 | def h = (s.trim, s.length) // warn + | ^ + |Public method method h exposes a flexible type ((String)?, Int) in its inferred signature. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:7:6 -------------------------------------------------- +7 | val ss = s.replace("a", "A") // warn + | ^ + |Public field value ss exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:8:6 -------------------------------------------------- +8 | val ss2 = Seq(s.trim) // warn + | ^ + |Public field value ss2 exposes a flexible type Seq[(String)?] in its inferred signature. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:16:6 ------------------------------------------------- +16 | def f = s2.trim // warn + | ^ + |Public method method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:20:6 ------------------------------------------------- +20 | def h = (s2.trim, s2.length) // warn + | ^ + |Public method method h exposes a flexible type ((String)?, Int) in its inferred signature. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:21:6 ------------------------------------------------- +21 | val ss = s2.replace("a", "A") // warn + | ^ + |Public field value ss exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:22:6 ------------------------------------------------- +22 | val ss2 = Seq(s2.trim) // warn + | ^ + |Public field value ss2 exposes a flexible type Seq[(String)?] in its inferred signature. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:27:4 ------------------------------------------------- +27 |def f = s2.trim // warn + | ^ + |Public method method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:31:4 ------------------------------------------------- +31 |def h = (s2.trim, s2.length) // warn + | ^ + |Public method method h exposes a flexible type ((String)?, Int) in its inferred signature. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:32:4 ------------------------------------------------- +32 |val ss = s2.replace("a", "A") // warn + | ^ + |Public field value ss exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:33:4 ------------------------------------------------- +33 |val ss2 = Seq(s2.trim) // warn + | ^ + |Public field value ss2 exposes a flexible type Seq[(String)?] in its inferred signature. Consider annotating the type explicitly diff --git a/tests/explicit-nulls/warn/expose-flexible-types.scala b/tests/explicit-nulls/warn/expose-flexible-types.scala new file mode 100644 index 000000000000..d0407ff94477 --- /dev/null +++ b/tests/explicit-nulls/warn/expose-flexible-types.scala @@ -0,0 +1,36 @@ +class C(s: String): + def f = s.trim // warn + def g: String = + val s2 = s.trim + s2 + def h = (s.trim, s.length) // warn + val ss = s.replace("a", "A") // warn + val ss2 = Seq(s.trim) // warn + val ss3: Seq[String] = + val s3 = s.trim + Seq(s3) + +val s2: String = "" + +object O: + def f = s2.trim // warn + def g: String = + val s3 = s2.trim + s3 + def h = (s2.trim, s2.length) // warn + val ss = s2.replace("a", "A") // warn + val ss2 = Seq(s2.trim) // warn + val ss3: Seq[String] = + val s3 = s2.trim + Seq(s3) + +def f = s2.trim // warn +def g: String = + val s3 = s2.trim + s3 +def h = (s2.trim, s2.length) // warn +val ss = s2.replace("a", "A") // warn +val ss2 = Seq(s2.trim) // warn +val ss3: Seq[String] = + val s3 = s2.trim + Seq(s3) \ No newline at end of file diff --git a/tests/explicit-nulls/warn/unnecessary-nn.scala b/tests/explicit-nulls/warn/unnecessary-nn.scala index 0e93b61fb408..82d87e75c0a5 100644 --- a/tests/explicit-nulls/warn/unnecessary-nn.scala +++ b/tests/explicit-nulls/warn/unnecessary-nn.scala @@ -9,7 +9,7 @@ def f6[T >: String|Null](s: String|Null): T = s.nn // warn def f5a[T <: String](s: T): String = s.nn // warn // flexible types -def f7(s: String|Null) = "".concat(s.nn) // warn +def f7(s: String|Null): String = "".concat(s.nn) // warn def f8(s: String): String = s.trim().nn // OK because the .nn could be useful as a dynamic null check From a4d6f084cf18619f8f3ff429604e35d473aa468f Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 5 Sep 2025 16:50:59 +0200 Subject: [PATCH 044/111] Refine warning message [Cherry-picked 23b4a2deddca8732e972aabf714a6b8e75225bd4] --- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- .../warn/expose-flexible-types.check | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 288fd95be777..bb25998bb424 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3105,7 +3105,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer i"Consider annotating the type as ${hi} or ${hi} | Null explicitly" case _ => "Consider annotating the type explicitly" report.warning( - em"Public ${if sym.is(Method) then "method" else "field"} ${sym.show} exposes a flexible type ${resTp} in its inferred signature. $suggestion", + em"Public ${sym.show} exposes a flexible type ${resTp} in its inferred signature. $suggestion", sym.srcPos ) diff --git a/tests/explicit-nulls/warn/expose-flexible-types.check b/tests/explicit-nulls/warn/expose-flexible-types.check index 9cf8cd78157e..3cc4223d9f8d 100644 --- a/tests/explicit-nulls/warn/expose-flexible-types.check +++ b/tests/explicit-nulls/warn/expose-flexible-types.check @@ -1,48 +1,48 @@ -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:2:6 -------------------------------------------------- 2 | def f = s.trim // warn | ^ - |Public method method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly + |Public method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:6:6 -------------------------------------------------- 6 | def h = (s.trim, s.length) // warn | ^ - |Public method method h exposes a flexible type ((String)?, Int) in its inferred signature. Consider annotating the type explicitly + |Public method h exposes a flexible type ((String)?, Int) in its inferred signature. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:7:6 -------------------------------------------------- 7 | val ss = s.replace("a", "A") // warn | ^ - |Public field value ss exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly + |Public value ss exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:8:6 -------------------------------------------------- 8 | val ss2 = Seq(s.trim) // warn | ^ - |Public field value ss2 exposes a flexible type Seq[(String)?] in its inferred signature. Consider annotating the type explicitly + |Public value ss2 exposes a flexible type Seq[(String)?] in its inferred signature. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:16:6 ------------------------------------------------- 16 | def f = s2.trim // warn | ^ - |Public method method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly + |Public method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:20:6 ------------------------------------------------- 20 | def h = (s2.trim, s2.length) // warn | ^ - |Public method method h exposes a flexible type ((String)?, Int) in its inferred signature. Consider annotating the type explicitly + |Public method h exposes a flexible type ((String)?, Int) in its inferred signature. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:21:6 ------------------------------------------------- 21 | val ss = s2.replace("a", "A") // warn | ^ - |Public field value ss exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly + |Public value ss exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:22:6 ------------------------------------------------- 22 | val ss2 = Seq(s2.trim) // warn | ^ - |Public field value ss2 exposes a flexible type Seq[(String)?] in its inferred signature. Consider annotating the type explicitly + |Public value ss2 exposes a flexible type Seq[(String)?] in its inferred signature. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:27:4 ------------------------------------------------- 27 |def f = s2.trim // warn | ^ - |Public method method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly + |Public method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:31:4 ------------------------------------------------- 31 |def h = (s2.trim, s2.length) // warn | ^ - |Public method method h exposes a flexible type ((String)?, Int) in its inferred signature. Consider annotating the type explicitly + |Public method h exposes a flexible type ((String)?, Int) in its inferred signature. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:32:4 ------------------------------------------------- 32 |val ss = s2.replace("a", "A") // warn | ^ - |Public field value ss exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly + |Public value ss exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:33:4 ------------------------------------------------- 33 |val ss2 = Seq(s2.trim) // warn | ^ - |Public field value ss2 exposes a flexible type Seq[(String)?] in its inferred signature. Consider annotating the type explicitly + |Public value ss2 exposes a flexible type Seq[(String)?] in its inferred signature. Consider annotating the type explicitly From 2f48f0d378b51dc5fa7be005790babe4abde0736 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 5 Sep 2025 17:05:13 +0200 Subject: [PATCH 045/111] Update compiler tests [Cherry-picked c46c9bf87b4f306065631c6bd9a1aaaef4881b85] --- .../dotty/tools/dotc/TupleShowTests.scala | 22 +++++++++++-------- .../dotc/semanticdb/SemanticdbTests.scala | 16 +++++++------- .../repl/AbstractFileClassLoaderTest.scala | 2 +- .../dotty/tools/scripting/ScriptTestEnv.scala | 4 ++-- compiler/test/dotty/tools/utils.scala | 2 +- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/TupleShowTests.scala b/compiler/test/dotty/tools/dotc/TupleShowTests.scala index 88e0587d7d71..3aa9d0d84b42 100644 --- a/compiler/test/dotty/tools/dotc/TupleShowTests.scala +++ b/compiler/test/dotty/tools/dotc/TupleShowTests.scala @@ -54,24 +54,28 @@ class TupleShowTests extends DottyTest: @Test def tup3_show10 = chkEq("(Int,\n Long,\n Short)".normEOL, tup3.toText(ctx.printer).mkString(10, false)) - val res21 = """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, - | Int, Long, Long, Long, Long, Long, Int)""".stripMargin.normEOL + val res21: String = + """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, + | Int, Long, Long, Long, Long, Long, Int)""".stripMargin.normEOL - val res22 = """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, - | Int, Long, Long, Long, Long, Long, Int, Long)""".stripMargin.normEOL + val res22: String = + """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, + | Int, Long, Long, Long, Long, Long, Int, Long)""".stripMargin.normEOL - val res23 = """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, - | Int, Long, Long, Long, Long, Long, Int, Long, Short)""".stripMargin.normEOL + val res23: String = + """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, + | Int, Long, Long, Long, Long, Long, Int, Long, Short)""".stripMargin.normEOL - val res24 = """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, - | Int, Long, Long, Long, Long, Long, Int, Long, Short, Short)""".stripMargin.normEOL + val res24: String = + """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, + | Int, Long, Long, Long, Long, Long, Int, Long, Short, Short)""".stripMargin.normEOL def chkEq[A](expected: A, obtained: A) = assert(expected == obtained, diff(s"$expected", s"$obtained")) /** On Windows the string literal in this test source file will be read with `\n` (b/c of "-encoding UTF8") * but the compiler will correctly emit \r\n as the line separator. * So we align the expected result to faithfully compare test results. */ - extension (str: String) def normEOL = if EOL == "\n" then str else str.replace("\n", EOL) + extension (str: String) def normEOL: String = if EOL == "\n" then str else str.replace("\n", EOL) def diff(exp: String, obt: String) = val min = math.min(exp.length, obt.length) diff --git a/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala b/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala index 827a997af14b..6df830905603 100644 --- a/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala +++ b/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala @@ -33,7 +33,7 @@ import dotty.tools.dotc.util.SourceFile * only 1 semanticdb file should be present * @param source the single source file producing the semanticdb */ -@main def metac(root: String, source: String) = +@main def metac(root: String, source: String): Unit = val rootSrc = Paths.get(root) val sourceSrc = Paths.get(source) val semanticFile = FileSystems.getDefault.getPathMatcher("glob:**.semanticdb") @@ -53,13 +53,13 @@ import dotty.tools.dotc.util.SourceFile @Category(Array(classOf[BootstrappedOnlyTests])) class SemanticdbTests: - val javaFile = FileSystems.getDefault.getPathMatcher("glob:**.java") - val scalaFile = FileSystems.getDefault.getPathMatcher("glob:**.scala") - val expectFile = FileSystems.getDefault.getPathMatcher("glob:**.expect.scala") - val rootSrc = Paths.get(System.getProperty("dotty.tools.dotc.semanticdb.test")) - val expectSrc = rootSrc.resolve("expect") - val javaRoot = rootSrc.resolve("javacp") - val metacExpectFile = rootSrc.resolve("metac.expect") + val javaFile: PathMatcher = FileSystems.getDefault.getPathMatcher("glob:**.java") + val scalaFile: PathMatcher = FileSystems.getDefault.getPathMatcher("glob:**.scala") + val expectFile: PathMatcher = FileSystems.getDefault.getPathMatcher("glob:**.expect.scala") + val rootSrc: Path = Paths.get(System.getProperty("dotty.tools.dotc.semanticdb.test")) + val expectSrc: Path = rootSrc.resolve("expect") + val javaRoot: Path = rootSrc.resolve("javacp") + val metacExpectFile: Path = rootSrc.resolve("metac.expect") @Category(Array(classOf[dotty.SlowTests])) @Test def expectTests: Unit = if (!scala.util.Properties.isWin) runExpectTest(updateExpectFiles = false) diff --git a/compiler/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala b/compiler/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala index 27796feb819b..44540629016e 100644 --- a/compiler/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala +++ b/compiler/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala @@ -29,7 +29,7 @@ class AbstractFileClassLoaderTest: // cf ScalaClassLoader#classBytes extension (loader: ClassLoader) // An InputStream representing the given class name, or null if not found. - def classAsStream(className: String) = loader.getResourceAsStream { + def classAsStream(className: String): InputStream = loader.getResourceAsStream { if className.endsWith(".class") then className else s"${className.replace('.', '/')}.class" // classNameToPath } diff --git a/compiler/test/dotty/tools/scripting/ScriptTestEnv.scala b/compiler/test/dotty/tools/scripting/ScriptTestEnv.scala index 771c3ba14af0..387e309f6aec 100644 --- a/compiler/test/dotty/tools/scripting/ScriptTestEnv.scala +++ b/compiler/test/dotty/tools/scripting/ScriptTestEnv.scala @@ -282,7 +282,7 @@ object ScriptTestEnv { } extension(f: File) { - def name = f.getName + def name: String = f.getName def norm: String = f.toPath.normalize.norm def absPath: String = f.getAbsolutePath.norm def relpath: Path = f.toPath.relpath @@ -305,7 +305,7 @@ object ScriptTestEnv { // dist[*]/target/universal/stage, if present // else, SCALA_HOME if defined // else, not defined - lazy val envScalaHome = + lazy val envScalaHome: String = printf("scalacPath: %s\n", scalacPath.norm) if scalacPath.isFile then scalacPath.replaceAll("/bin/scalac", "") else envOrElse("SCALA_HOME", "not-found").norm diff --git a/compiler/test/dotty/tools/utils.scala b/compiler/test/dotty/tools/utils.scala index 14981e001d38..5dd949643f7f 100644 --- a/compiler/test/dotty/tools/utils.scala +++ b/compiler/test/dotty/tools/utils.scala @@ -33,7 +33,7 @@ def scriptsDir(path: String): File = { dir } -extension (f: File) def absPath = +extension (f: File) def absPath: String = f.getAbsolutePath.replace('\\', '/') extension (str: String) def dropExtension = From 2c20198ae3499261fb9b6a2f7944dfd06f03e4be Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 5 Sep 2025 17:36:42 +0200 Subject: [PATCH 046/111] Move warning to RefCheck [Cherry-picked 1d3d0c4fd35b4f173cc509e039c15d114f9388d6] --- .../dotty/tools/dotc/typer/RefChecks.scala | 14 ++++ .../src/dotty/tools/dotc/typer/Typer.scala | 25 +----- .../warn/expose-flexible-types.check | 80 ++++++++++--------- .../warn/expose-flexible-types.scala | 5 ++ 4 files changed, 66 insertions(+), 58 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index a79408b756ee..1b04d7b4ca21 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -1185,6 +1185,18 @@ object RefChecks { report.warning(ExtensionNullifiedByMember(sym, target), sym.srcPos) end checkExtensionMethods + /** Check that public (and protected) methods/fields do not expose flexible types. */ + def checkPublicFlexibleTypes(sym: Symbol)(using Context): Unit = + if ctx.explicitNulls && !ctx.isJava + && sym.exists && !sym.is(Private) && sym.owner.isClass + && !sym.isOneOf(Synthetic | InlineProxy | Param) then + val resTp = sym.info.finalResultType + if resTp.existsPart(_.isInstanceOf[FlexibleType], StopAt.Static) then + report.warning( + em"${sym.show} exposes a flexible type in its inferred result type ${resTp}. Consider annotating the type explicitly", + sym.srcPos + ) + /** Verify that references in the user-defined `@implicitNotFound` message are valid. * (i.e. they refer to a type variable that really occurs in the signature of the annotated symbol.) */ @@ -1321,6 +1333,7 @@ class RefChecks extends MiniPhase { thisPhase => val sym = tree.symbol checkNoPrivateOverrides(sym) checkVolatile(sym) + checkPublicFlexibleTypes(sym) if (sym.exists && sym.owner.isTerm) { tree.rhs match { case Ident(nme.WILDCARD) => report.error(UnboundPlaceholderParameter(), sym.srcPos) @@ -1336,6 +1349,7 @@ class RefChecks extends MiniPhase { thisPhase => checkImplicitNotFoundAnnotation.defDef(sym.denot) checkUnaryMethods(sym) checkExtensionMethods(sym) + checkPublicFlexibleTypes(sym) tree } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bb25998bb424..e9e3e22342bf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3083,32 +3083,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer //todo: make sure dependent method types do not depend on implicits or by-name params } - /** (1) Check that the signature of the class member does not return a repeated parameter type. - * (2) Check that the signature of the public class member does not expose a flexible type. - * (3) Make sure the definition's symbol is `sym`. - * (4) Set the `defTree` of `sym` to be `mdef`. + /** (1) Check that the signature of the class member does not return a repeated parameter type + * (2) Make sure the definition's symbol is `sym`. + * (3) Set the `defTree` of `sym` to be `mdef`. */ private def postProcessInfo(mdef: MemberDef, sym: Symbol)(using Context): MemberDef = if (!sym.isOneOf(Synthetic | InlineProxy | Param) && sym.info.finalResultType.isRepeatedParam) report.error(em"Cannot return repeated parameter type ${sym.info.finalResultType}", sym.srcPos) - - // Warn if a public method/field exposes FlexibleType in its result type under explicit nulls - // and encourage explicit annotation. - if ctx.phase.isTyper && ctx.explicitNulls && !ctx.isJava - && sym.exists && sym.isPublic && sym.owner.isClass - && !sym.isOneOf(Synthetic | InlineProxy | Param) then - val resTp = sym.info.finalResultType - if resTp.existsPart(_.isInstanceOf[FlexibleType], StopAt.Static) then - val suggestion = resTp match - case ft: FlexibleType => - val hi = ft.hi - i"Consider annotating the type as ${hi} or ${hi} | Null explicitly" - case _ => "Consider annotating the type explicitly" - report.warning( - em"Public ${sym.show} exposes a flexible type ${resTp} in its inferred signature. $suggestion", - sym.srcPos - ) - mdef.ensureHasSym(sym) mdef.setDefTree diff --git a/tests/explicit-nulls/warn/expose-flexible-types.check b/tests/explicit-nulls/warn/expose-flexible-types.check index 3cc4223d9f8d..5e0f8519b7e7 100644 --- a/tests/explicit-nulls/warn/expose-flexible-types.check +++ b/tests/explicit-nulls/warn/expose-flexible-types.check @@ -1,48 +1,56 @@ --- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:2:6 -------------------------------------------------- -2 | def f = s.trim // warn +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:4:6 -------------------------------------------------- +4 | def f = s.trim // warn | ^ - |Public method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly --- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:6:6 -------------------------------------------------- -6 | def h = (s.trim, s.length) // warn - | ^ - |Public method h exposes a flexible type ((String)?, Int) in its inferred signature. Consider annotating the type explicitly --- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:7:6 -------------------------------------------------- -7 | val ss = s.replace("a", "A") // warn - | ^ - |Public value ss exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly + | method f exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:8:6 -------------------------------------------------- -8 | val ss2 = Seq(s.trim) // warn +8 | def h = (s.trim, s.length) // warn | ^ - |Public value ss2 exposes a flexible type Seq[(String)?] in its inferred signature. Consider annotating the type explicitly --- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:16:6 ------------------------------------------------- -16 | def f = s2.trim // warn + |method h exposes a flexible type in its inferred result type ((String)?, Int). Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:9:16 ------------------------------------------------- +9 | protected def i = s.trim // warn + | ^ + | method i exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:11:19 ------------------------------------------------ +11 | private[foo] def k = s.trim // warn + | ^ + | method k exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:12:6 ------------------------------------------------- +12 | val ss = s.replace("a", "A") // warn | ^ - |Public method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly --- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:20:6 ------------------------------------------------- -20 | def h = (s2.trim, s2.length) // warn + | value ss exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:13:6 ------------------------------------------------- +13 | val ss2 = Seq(s.trim) // warn | ^ - |Public method h exposes a flexible type ((String)?, Int) in its inferred signature. Consider annotating the type explicitly + |value ss2 exposes a flexible type in its inferred result type Seq[(String)?]. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:21:6 ------------------------------------------------- -21 | val ss = s2.replace("a", "A") // warn +21 | def f = s2.trim // warn + | ^ + | method f exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:25:6 ------------------------------------------------- +25 | def h = (s2.trim, s2.length) // warn | ^ - |Public value ss exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly --- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:22:6 ------------------------------------------------- -22 | val ss2 = Seq(s2.trim) // warn + |method h exposes a flexible type in its inferred result type ((String)?, Int). Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:26:6 ------------------------------------------------- +26 | val ss = s2.replace("a", "A") // warn | ^ - |Public value ss2 exposes a flexible type Seq[(String)?] in its inferred signature. Consider annotating the type explicitly --- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:27:4 ------------------------------------------------- -27 |def f = s2.trim // warn + | value ss exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:27:6 ------------------------------------------------- +27 | val ss2 = Seq(s2.trim) // warn + | ^ + |value ss2 exposes a flexible type in its inferred result type Seq[(String)?]. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:32:4 ------------------------------------------------- +32 |def f = s2.trim // warn | ^ - |Public method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly --- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:31:4 ------------------------------------------------- -31 |def h = (s2.trim, s2.length) // warn + | method f exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:36:4 ------------------------------------------------- +36 |def h = (s2.trim, s2.length) // warn | ^ - |Public method h exposes a flexible type ((String)?, Int) in its inferred signature. Consider annotating the type explicitly --- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:32:4 ------------------------------------------------- -32 |val ss = s2.replace("a", "A") // warn + |method h exposes a flexible type in its inferred result type ((String)?, Int). Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:37:4 ------------------------------------------------- +37 |val ss = s2.replace("a", "A") // warn | ^ - |Public value ss exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly --- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:33:4 ------------------------------------------------- -33 |val ss2 = Seq(s2.trim) // warn + | value ss exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:38:4 ------------------------------------------------- +38 |val ss2 = Seq(s2.trim) // warn | ^ - |Public value ss2 exposes a flexible type Seq[(String)?] in its inferred signature. Consider annotating the type explicitly + |value ss2 exposes a flexible type in its inferred result type Seq[(String)?]. Consider annotating the type explicitly diff --git a/tests/explicit-nulls/warn/expose-flexible-types.scala b/tests/explicit-nulls/warn/expose-flexible-types.scala index d0407ff94477..9908e6228cd4 100644 --- a/tests/explicit-nulls/warn/expose-flexible-types.scala +++ b/tests/explicit-nulls/warn/expose-flexible-types.scala @@ -1,9 +1,14 @@ +package foo + class C(s: String): def f = s.trim // warn def g: String = val s2 = s.trim s2 def h = (s.trim, s.length) // warn + protected def i = s.trim // warn + private def j = s.trim + private[foo] def k = s.trim // warn val ss = s.replace("a", "A") // warn val ss2 = Seq(s.trim) // warn val ss3: Seq[String] = From 3f31e0872277347498c6a019878f9f2d46f72100 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 5 Sep 2025 17:55:13 +0200 Subject: [PATCH 047/111] Exclude exported symbols [Cherry-picked 77dae85ad5479eabcaeedacd0f2e5e2995a0ffea] --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 2 +- compiler/src/dotty/tools/repl/AbstractFileClassLoader.scala | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 9fe08c27a159..6700959eee0e 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2961,7 +2961,7 @@ object SymDenotations { dependent = null } - protected def addDependent(dep: InheritedCache) = { + protected def addDependent(dep: InheritedCache): Unit = { if (dependent == null) dependent = new WeakHashMap dependent.nn.put(dep, ()) } diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 1b04d7b4ca21..f1dce00c4fc2 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -1189,7 +1189,7 @@ object RefChecks { def checkPublicFlexibleTypes(sym: Symbol)(using Context): Unit = if ctx.explicitNulls && !ctx.isJava && sym.exists && !sym.is(Private) && sym.owner.isClass - && !sym.isOneOf(Synthetic | InlineProxy | Param) then + && !sym.isOneOf(Synthetic | InlineProxy | Param | Exported) then val resTp = sym.info.finalResultType if resTp.existsPart(_.isInstanceOf[FlexibleType], StopAt.Static) then report.warning( diff --git a/compiler/src/dotty/tools/repl/AbstractFileClassLoader.scala b/compiler/src/dotty/tools/repl/AbstractFileClassLoader.scala index 7a457a1d7546..1796a7dc68b5 100644 --- a/compiler/src/dotty/tools/repl/AbstractFileClassLoader.scala +++ b/compiler/src/dotty/tools/repl/AbstractFileClassLoader.scala @@ -26,7 +26,7 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) exten // on JDK 20 the URL constructor we're using is deprecated, // but the recommended replacement, URL.of, doesn't exist on JDK 8 @annotation.nowarn("cat=deprecation") - override protected def findResource(name: String) = + override protected def findResource(name: String): URL | Null = findAbstractFile(name) match case null => null case file => new URL(null, s"memory:${file.path}", new URLStreamHandler { @@ -35,13 +35,13 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) exten override def getInputStream = file.input } }) - override protected def findResources(name: String) = + override protected def findResources(name: String): java.util.Enumeration[URL] = findResource(name) match case null => Collections.enumeration(Collections.emptyList[URL]) //Collections.emptyEnumeration[URL] case url => Collections.enumeration(Collections.singleton(url)) override def findClass(name: String): Class[?] = { - var file: AbstractFile = root + var file: AbstractFile | Null = root val pathParts = name.split("[./]").toList for (dirPart <- pathParts.init) { file = file.lookupName(dirPart, true) From 404ac7721cdb66fe2143228fb887b9dd796c583a Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sun, 7 Sep 2025 18:41:56 +0200 Subject: [PATCH 048/111] Fix more warnings [Cherry-picked f879e647502f7e45215c418fd2218606a0a2adc0] --- compiler/test/dotty/tools/vulpix/ParallelTesting.scala | 4 ++-- .../test/dotty/tools/pc/base/BasePCSuite.scala | 4 ++-- .../test/dotty/tools/pc/utils/MockSymbolSearch.scala | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 35fbb6e5fb14..41db48272937 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -353,8 +353,8 @@ trait ParallelTesting extends RunnerOrchestration { self => import summaryReport._ - protected final val realStdout = System.out - protected final val realStderr = System.err + protected final val realStdout: PrintStream = System.out + protected final val realStderr: PrintStream = System.err /** A runnable that logs its contents in a buffer */ trait LoggedRunnable extends Runnable { diff --git a/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala b/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala index a26c31ef084d..5128303d105f 100644 --- a/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala @@ -25,7 +25,7 @@ import org.junit.runner.RunWith import scala.meta.pc.CompletionItemPriority object TestResources: - val classpath = BuildInfo.ideTestsDependencyClasspath.map(_.toPath).toSeq + val classpath: Seq[Path] = BuildInfo.ideTestsDependencyClasspath.map(_.toPath).toSeq val classpathSearch = ClasspathSearch.fromClasspath(classpath, ExcludedPackagesHandler.default) @@ -34,7 +34,7 @@ abstract class BasePCSuite extends PcAssertions: val completionItemPriority: CompletionItemPriority = (_: String) => 0 private val isDebug = ManagementFactory.getRuntimeMXBean.getInputArguments.toString.contains("-agentlib:jdwp") - val tmp = Files.createTempDirectory("stable-pc-tests") + val tmp: Path = Files.createTempDirectory("stable-pc-tests") val executorService: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor() val testingWorkspaceSearch = TestingWorkspaceSearch( diff --git a/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala b/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala index 459c41e3c8e5..735ee846be2d 100644 --- a/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala +++ b/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala @@ -66,7 +66,7 @@ class MockSymbolSearch( override def documentation( symbol: String, parents: ParentSymbols - ) = documentation(symbol, parents, ContentType.MARKDOWN) + ): Optional[SymbolDocumentation] = documentation(symbol, parents, ContentType.MARKDOWN) override def documentation( symbol: String, From 898bbd0c2d345af2b2d0bf644b50cc50437df91a Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 7 May 2025 09:16:52 -0700 Subject: [PATCH 049/111] Invent given pattern name in for comprehension [Cherry-picked b20b3382bc4ea5d4de8857201fe4fd608e31630a] --- .../src/dotty/tools/dotc/ast/Desugar.scala | 17 ++++++----- tests/pos/i23119.scala | 29 +++++++++++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 tests/pos/i23119.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index cd86e064cfb4..398b3af607ef 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1347,7 +1347,7 @@ object desugar { )).withSpan(tree.span) end makePolyFunctionType - /** Invent a name for an anonympus given of type or template `impl`. */ + /** Invent a name for an anonymous given of type or template `impl`. */ def inventGivenName(impl: Tree)(using Context): SimpleName = val str = impl match case impl: Template => @@ -2136,18 +2136,19 @@ object desugar { * that refers to the bound variable for the pattern. Wildcard Binds are * also replaced by Binds with fresh names. */ - def makeIdPat(pat: Tree): (Tree, Ident) = pat match { - case bind @ Bind(name, pat1) => - if name == nme.WILDCARD then - val name = UniqueName.fresh() - (cpy.Bind(pat)(name, pat1).withMods(bind.mods), Ident(name)) - else (pat, Ident(name)) + def makeIdPat(pat: Tree): (Tree, Ident) = pat match + case pat @ Bind(nme.WILDCARD, body) => + val name = + body match + case Typed(Ident(nme.WILDCARD), tpt) if pat.mods.is(Given) => inventGivenName(tpt) + case _ => UniqueName.fresh() + (cpy.Bind(pat)(name, body).withMods(pat.mods), Ident(name)) + case Bind(name, _) => (pat, Ident(name)) case id: Ident if isVarPattern(id) && id.name != nme.WILDCARD => (id, id) case Typed(id: Ident, _) if isVarPattern(id) && id.name != nme.WILDCARD => (pat, id) case _ => val name = UniqueName.fresh() (Bind(name, pat), Ident(name)) - } /** Make a pattern filter: * rhs.withFilter { case pat => true case _ => false } diff --git a/tests/pos/i23119.scala b/tests/pos/i23119.scala new file mode 100644 index 000000000000..cd8026005447 --- /dev/null +++ b/tests/pos/i23119.scala @@ -0,0 +1,29 @@ +//> using options -Wunused:patvars -Werror + +def make: IndexedSeq[FalsePositive] = + for { + i <- 1 to 2 + given Int = i + fp = FalsePositive() + } yield fp + +def broken = + for + i <- List(42) + (x, y) = "hello" -> "world" + yield + s"$x, $y" * i + +def alt: IndexedSeq[FalsePositive] = + given String = "hi" + for + given Int <- 1 to 2 + j: Int = summon[Int] // simple assign because irrefutable + _ = j + 1 + k :: Nil = j :: Nil : @unchecked // pattern in one var + fp = FalsePositive(using k) + yield fp + +class FalsePositive(using Int): + def usage(): Unit = + println(summon[Int]) From d99b67205c2088847c31de4276595b2f54ae63e9 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 29 Aug 2025 07:58:42 -0700 Subject: [PATCH 050/111] Status quo clashing of given names in for desugar [Cherry-picked 245a519ec97200d30ea7c492bf7b0935e1adeea7] --- tests/neg/i23119.check | 6 ++++++ tests/neg/i23119.scala | 13 +++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/neg/i23119.check create mode 100644 tests/neg/i23119.scala diff --git a/tests/neg/i23119.check b/tests/neg/i23119.check new file mode 100644 index 000000000000..717199a610d0 --- /dev/null +++ b/tests/neg/i23119.check @@ -0,0 +1,6 @@ +-- [E161] Naming Error: tests/neg/i23119.scala:7:4 --------------------------------------------------------------------- +7 | given Option[List[Int]] = Some(List(x)) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | given_Option_List is already defined as given instance given_Option_List + | + | Note that overloaded methods must all be defined in the same group of toplevel definitions diff --git a/tests/neg/i23119.scala b/tests/neg/i23119.scala new file mode 100644 index 000000000000..39e521313594 --- /dev/null +++ b/tests/neg/i23119.scala @@ -0,0 +1,13 @@ + +@main def test = println: + for x <- 1 to 2 + // works with explicit name + //ols @ given Option[List[String]] = Some(List(x.toString)) + given Option[List[String]] = Some(List(x.toString)) + given Option[List[Int]] = Some(List(x)) // error + yield summon[Option[List[String]]].map(ss => ss.corresponds(given_Option_List.get)((a, b) => a == b.toString)) + +// The naming clash is noticed when defining local values for "packaging": +// given_Option_List is already defined as given instance given_Option_List +// Previously the naming clash was noticed when extracting values in the map or do function: +// duplicate pattern variable: given_Option_List From 2b5e38eed8bb8df82723187180649668df0c2a9a Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sat, 6 Sep 2025 09:48:19 -0700 Subject: [PATCH 051/111] Help renaming conflicting givens [Cherry-picked 87e434a82b02cd063a0ba190fac24bbd2fd21fd5] --- .../dotty/tools/dotc/reporting/messages.scala | 21 ++++++++++++++++++- tests/neg/i23119.check | 20 +++++++++++++++--- tests/neg/i23119.scala | 6 ++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 5a9d5e8b5cd4..41189608ce1b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2125,8 +2125,27 @@ extends NamingMsg(AlreadyDefinedID): i" in ${conflicting.associatedFile}" else if conflicting.owner == owner then "" else i" in ${conflicting.owner}" + def print(tpe: Type): String = + def addParams(tpe: Type): List[String] = tpe match + case tpe: MethodType => + val s = if tpe.isContextualMethod then i"(${tpe.paramInfos}%, %) =>" else "" + s :: addParams(tpe.resType) + case tpe: PolyType => + i"[${tpe.paramNames}%, %] =>" :: addParams(tpe.resType) + case tpe => + i"$tpe" :: Nil + addParams(tpe).mkString(" ") def note = - if owner.is(Method) || conflicting.is(Method) then + if conflicting.is(Given) && name.startsWith("given_") then + i"""| + | + |Provide an explicit, unique name to given definitions, + |since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: ${print(atPhase(typerPhase)(conflicting.info))} // define an instance + | given myGiven @ ${print(atPhase(typerPhase)(conflicting.info))} // as a pattern variable + |""" + else if owner.is(Method) || conflicting.is(Method) then "\n\nNote that overloaded methods must all be defined in the same group of toplevel definitions" else "" if conflicting.isTerm != name.isTermName then diff --git a/tests/neg/i23119.check b/tests/neg/i23119.check index 717199a610d0..34f9e3c564ae 100644 --- a/tests/neg/i23119.check +++ b/tests/neg/i23119.check @@ -1,6 +1,20 @@ --- [E161] Naming Error: tests/neg/i23119.scala:7:4 --------------------------------------------------------------------- -7 | given Option[List[Int]] = Some(List(x)) // error +-- [E161] Naming Error: tests/neg/i23119.scala:8:4 --------------------------------------------------------------------- +8 | given Option[List[Int]] = Some(List(x)) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | given_Option_List is already defined as given instance given_Option_List | - | Note that overloaded methods must all be defined in the same group of toplevel definitions + | Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: Option[List[String]] // define an instance + | given myGiven @ Option[List[String]] // as a pattern variable +-- [E161] Naming Error: tests/neg/i23119.scala:18:8 -------------------------------------------------------------------- +18 | given [A] => List[A] = ??? // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | given_List_A is already defined as given instance given_List_A + | + | Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: [A] => List[A] // define an instance + | given myGiven @ [A] => List[A] // as a pattern variable diff --git a/tests/neg/i23119.scala b/tests/neg/i23119.scala index 39e521313594..0f882b66dc8d 100644 --- a/tests/neg/i23119.scala +++ b/tests/neg/i23119.scala @@ -1,3 +1,4 @@ +//> using options -explain @main def test = println: for x <- 1 to 2 @@ -11,3 +12,8 @@ // given_Option_List is already defined as given instance given_Option_List // Previously the naming clash was noticed when extracting values in the map or do function: // duplicate pattern variable: given_Option_List + +def also = + given [A] => List[A] = ??? + given [A] => List[A] = ??? // error + () From cd43a6e0d6a79264445ce14cdef8175c9768ba75 Mon Sep 17 00:00:00 2001 From: Yoonjae Jeon Date: Sun, 17 Aug 2025 22:19:05 +0900 Subject: [PATCH 052/111] Add subtype-based fallback in inferPrefixMap add new line second approach revert previous approach address reviews [Cherry-picked 4fd7a90f18ceec027cf6378951a3d71a8649de3c] --- .../src/dotty/tools/dotc/core/TypeOps.scala | 9 +++++-- tests/warn/i23369.scala | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 tests/warn/i23369.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index cf03273b4805..f1936ff8cd01 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -805,9 +805,13 @@ object TypeOps: prefixTVar.uncheckedNN case ThisType(tref) if !tref.symbol.isStaticOwner => val symbol = tref.symbol + val compatibleSingleton = singletons.valuesIterator.find(_.underlying.derivesFrom(symbol)) if singletons.contains(symbol) then prefixTVar = singletons(symbol) // e.g. tests/pos/i16785.scala, keep Outer.this prefixTVar.uncheckedNN + else if compatibleSingleton.isDefined then + prefixTVar = compatibleSingleton.get + prefixTVar.uncheckedNN else if symbol.is(Module) then TermRef(this(tref.prefix), symbol.sourceModule) else if (prefixTVar != null) @@ -905,10 +909,11 @@ object TypeOps: } val inferThisMap = new InferPrefixMap - val tvars = tp1.etaExpand match + val prefixInferredTp = inferThisMap(tp1) + val tvars = prefixInferredTp.etaExpand match case eta: TypeLambda => constrained(eta) case _ => Nil - val protoTp1 = inferThisMap.apply(tp1).appliedTo(tvars) + val protoTp1 = prefixInferredTp.appliedTo(tvars) if gadtSyms.nonEmpty then ctx.gadtState.addToConstraint(gadtSyms) diff --git a/tests/warn/i23369.scala b/tests/warn/i23369.scala new file mode 100644 index 000000000000..5f4130ab96d3 --- /dev/null +++ b/tests/warn/i23369.scala @@ -0,0 +1,26 @@ +class Module { + type BarTy + sealed trait Adt[A] + case class Foo() extends Adt[String] + case class Bar[A <: BarTy](x: BarTy) extends Adt[A] +} + +object Basic extends Module { + type BarTy = String +} + +def test(a: Basic.Adt[String]) = { + a match { // warn: match may not be exhaustive + case Basic.Foo() => + } +} + +object Basic2 extends Module { + type BarTy = Int +} + +def test2(a: Basic2.Adt[String]) = { + a match { + case Basic2.Foo() => + } +} From 22d2247af544a728a143669802e21109c78c7499 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Wed, 6 Aug 2025 17:34:54 +0200 Subject: [PATCH 053/111] Fix implicit scope liftToAnchors for parameter lower bounds Related to https://github.com/scala/scala3/pull/23672#event-19012454596 But we were dropping a lower bound in this case. [Cherry-picked 673263028aad932ad8eeb5fd3820177c7574563e] --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 2 +- tests/pos/i21951b.scala | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i21951b.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index c47d12ba7e88..e1bd045ff3a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -832,7 +832,7 @@ trait ImplicitRunInfo: WildcardType else seen += t - t.superType match + t.underlying match case TypeBounds(lo, hi) => if lo.isBottomTypeAfterErasure then apply(hi) else AndType.make(apply(lo), apply(hi)) diff --git a/tests/pos/i21951b.scala b/tests/pos/i21951b.scala new file mode 100644 index 000000000000..30068b89d994 --- /dev/null +++ b/tests/pos/i21951b.scala @@ -0,0 +1,12 @@ + +class A +object A: + given A = ??? + +class B[X] +object B: + given g[T]: B[T] = ??? + +object Test: + def foo[X >: A] = summon[X] // was error + def bar[F[T] >: B[T]] = summon[F[Int]] // was error From 48838a67a0b6650ee6182bf88bcd300114495c95 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Thu, 4 Sep 2025 11:29:19 +0000 Subject: [PATCH 054/111] Add addendum to `private val` parameter variance error message [Cherry-picked e6c474d3b3558843625391cef3387ba36117e8c7] --- .../tools/dotc/typer/VarianceChecker.scala | 15 ++++++++++++- tests/neg/i22620.check | 21 +++++++++++++++++++ tests/neg/i22620.scala | 4 ++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i22620.check diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala index 0c2929283ee3..354f09382d82 100644 --- a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -178,7 +178,20 @@ class VarianceChecker(using Context) { i"\n${hl("enum case")} ${towner.name} requires explicit declaration of $tvar to resolve this issue.\n$example" else "" - em"${varianceLabel(tvar.flags)} $tvar occurs in ${varianceLabel(required)} position in type ${sym.info} of $sym$enumAddendum" + val privateParamAddendum = + if sym.flags.is(ParamAccessor) && sym.flags.is(Private) then + val varOrVal = if sym.is(Mutable) then "var" else "val" + val varFieldInstead = if sym.is(Mutable) then " and add\na field inside the class instead" else "" + s""" + | + |Implementation limitation: ${hl(f"private $varOrVal")} parameters cannot be inferred to be local + |and therefore are always variance-checked. + | + |Potential fix: remove the ${hl(f"private $varOrVal")} modifiers on the parameter ${sym.name}$varFieldInstead. + """.stripMargin + else + "" + em"${varianceLabel(tvar.flags)} $tvar occurs in ${varianceLabel(required)} position in type ${sym.info} of $sym$enumAddendum$privateParamAddendum" if (migrateTo3 && (sym.owner.isConstructor || sym.ownersIterator.exists(_.isAllOf(ProtectedLocal)))) report.migrationWarning( diff --git a/tests/neg/i22620.check b/tests/neg/i22620.check new file mode 100644 index 000000000000..51a79d7cabce --- /dev/null +++ b/tests/neg/i22620.check @@ -0,0 +1,21 @@ +-- Error: tests/neg/i22620.scala:4:34 ---------------------------------------------------------------------------------- +4 |class PrivateTest[-M](private val v: ArrayBuffer[M]) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | contravariant type M occurs in invariant position in type scala.collection.mutable.ArrayBuffer[M] of value v + | + | Implementation limitation: private val parameters cannot be inferred to be local + | and therefore are always variance-checked. + | + | Potential fix: remove the private val modifiers on the parameter v. + | +-- Error: tests/neg/i22620.scala:6:37 ---------------------------------------------------------------------------------- +6 |class PrivateTestMut[-M](private var v: ArrayBuffer[M]) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | contravariant type M occurs in invariant position in type scala.collection.mutable.ArrayBuffer[M] of variable v + | + | Implementation limitation: private var parameters cannot be inferred to be local + | and therefore are always variance-checked. + | + | Potential fix: remove the private var modifiers on the parameter v and add + | a field inside the class instead. + | diff --git a/tests/neg/i22620.scala b/tests/neg/i22620.scala index 97d1d55e3302..0f06d97f73e0 100644 --- a/tests/neg/i22620.scala +++ b/tests/neg/i22620.scala @@ -2,3 +2,7 @@ import scala.collection.mutable.ArrayBuffer class PrivateTest[-M](private val v: ArrayBuffer[M]) // error + +class PrivateTestMut[-M](private var v: ArrayBuffer[M]) // error + +class PrivateTestParamOnly[-M](v: ArrayBuffer[M]) // no error From 60ff42312c343a46d10479a1232ee4b9e6de7e1a Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 29 Aug 2025 21:35:39 -0700 Subject: [PATCH 055/111] Explain no expansion of ContextFunction0 [Cherry-picked 3fdc94f1d807f1571eb63b4ba1ae5cbf0616936f] --- .../src/dotty/tools/dotc/typer/Typer.scala | 10 ++++++++-- tests/neg/context-function-syntax.scala | 2 +- tests/neg/i21321.check | 19 +++++++++++++++++++ tests/neg/i21321.scala | 4 ++++ 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 tests/neg/i21321.check create mode 100644 tests/neg/i21321.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e9e3e22342bf..5349cade601e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3745,12 +3745,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val ifpt = defn.asContextFunctionType(pt) val result = if ifpt.exists - && defn.functionArity(ifpt) > 0 // ContextFunction0 is only used after ElimByName + && !ctx.isAfterTyper + && { + // ContextFunction0 is only used after ElimByName + val arity = defn.functionArity(ifpt) + if arity == 0 then + report.error(em"context function types require at least one parameter", xtree.srcPos) + arity > 0 + } && xtree.isTerm && !untpd.isContextualClosure(xtree) && !ctx.mode.is(Mode.Pattern) && !xtree.isInstanceOf[SplicePattern] - && !ctx.isAfterTyper && !ctx.isInlineContext then makeContextualFunction(xtree, ifpt) diff --git a/tests/neg/context-function-syntax.scala b/tests/neg/context-function-syntax.scala index e411e840d8b5..5df5fd41aeb6 100644 --- a/tests/neg/context-function-syntax.scala +++ b/tests/neg/context-function-syntax.scala @@ -2,7 +2,7 @@ val test = (using x: Int) => x // error // error // error val f = () ?=> 23 // error -val g: ContextFunction0[Int] = ??? // ok +val g: ContextFunction0[Int] = ??? // error at typer for RHS not expanded val h: () ?=> Int = ??? // error object Example3 extends App { diff --git a/tests/neg/i21321.check b/tests/neg/i21321.check new file mode 100644 index 000000000000..88cb76154352 --- /dev/null +++ b/tests/neg/i21321.check @@ -0,0 +1,19 @@ +-- Error: tests/neg/i21321.scala:3:42 ---------------------------------------------------------------------------------- +3 |val v1b: scala.ContextFunction0[String] = () ?=> "x" // error + | ^^ + | context function literals require at least one formal parameter +-- Error: tests/neg/i21321.scala:4:8 ----------------------------------------------------------------------------------- +4 |val v2: () ?=> String = "y" // error // error in parser + | ^^ + | context function types require at least one parameter +-- Error: tests/neg/i21321.scala:2:41 ---------------------------------------------------------------------------------- +2 |val v1: scala.ContextFunction0[String] = "x" // error + | ^^^ + | context function types require at least one parameter +-- [E007] Type Mismatch Error: tests/neg/i21321.scala:4:24 ------------------------------------------------------------- +4 |val v2: () ?=> String = "y" // error // error in parser + | ^^^ + | Found: ("y" : String) + | Required: () => String + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i21321.scala b/tests/neg/i21321.scala new file mode 100644 index 000000000000..a7c60994d351 --- /dev/null +++ b/tests/neg/i21321.scala @@ -0,0 +1,4 @@ + +val v1: scala.ContextFunction0[String] = "x" // error +val v1b: scala.ContextFunction0[String] = () ?=> "x" // error +val v2: () ?=> String = "y" // error // error in parser From 29093202bfe3a1e4df80fea546b96680bc51c3d5 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Thu, 11 Sep 2025 15:37:35 +0200 Subject: [PATCH 056/111] fix: correctly require a `ClassTag` when building a multidimensional `Array` [Cherry-picked 9da1fcbeef4e9dc868b0f6121708f7736cc81c94] --- compiler/src/dotty/tools/dotc/core/TypeErasure.scala | 6 ++++++ .../src/dotty/tools/dotc/typer/Applications.scala | 11 ++++------- tests/run/i23901.scala | 8 ++++++++ 3 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 tests/run/i23901.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 2e6fa7d94d43..6eafae70c4ee 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -386,6 +386,12 @@ object TypeErasure { case _ => false } + /** Is `tp` of the form `Array^N[T]` where T is generic? */ + def isGenericArrayArg(tp: Type)(using Context): Boolean = tp.dealias match + case defn.ArrayOf(elem) => isGenericArrayArg(elem) + case _ => isGeneric(tp) + end isGenericArrayArg + /** The erased least upper bound of two erased types is computed as follows * - if both argument are arrays of objects, an array of the erased lub of the element types * - if both arguments are arrays of same primitives, an array of this primitive diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 290e061772e4..15e2bcb7427d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1427,14 +1427,11 @@ trait Applications extends Compatibility { def convertNewGenericArray(tree: Tree)(using Context): Tree = tree match { case Apply(TypeApply(tycon, targs@(targ :: Nil)), args) if tycon.symbol == defn.ArrayConstructor => fullyDefinedType(tree.tpe, "array", tree.srcPos) - - def newGenericArrayCall = + if TypeErasure.isGenericArrayArg(targ.tpe) then ref(defn.DottyArraysModule) - .select(defn.newGenericArrayMethod).withSpan(tree.span) - .appliedToTypeTrees(targs).appliedToTermArgs(args) - - if (TypeErasure.isGeneric(targ.tpe)) - newGenericArrayCall + .select(defn.newGenericArrayMethod).withSpan(tree.span) + .appliedToTypeTrees(targs) + .appliedToTermArgs(args) else tree case _ => tree diff --git a/tests/run/i23901.scala b/tests/run/i23901.scala new file mode 100644 index 000000000000..7bfcfff23551 --- /dev/null +++ b/tests/run/i23901.scala @@ -0,0 +1,8 @@ +import scala.reflect.ClassTag + +object MyArray: + def empty[T: ClassTag]: Array[Array[T]] = new Array[Array[T]](0) + +@main def Test = + val arr: Array[Array[String]] = MyArray.empty[String] + assert(arr.length == 0) From e78c54c667ff9cb291c43b69fa99a5d5bc21dc6a Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 15 Sep 2025 19:04:44 +0200 Subject: [PATCH 057/111] Fix separation checking for function results [Cherry-picked d96bf104318ac486fd5e198479933865cb49e177] --- .../src/dotty/tools/dotc/cc/SepCheck.scala | 99 +++++++++++-------- tests/neg-custom-args/captures/i23726.check | 51 ++++++++++ tests/neg-custom-args/captures/i23726.scala | 23 +++++ 3 files changed, 132 insertions(+), 41 deletions(-) create mode 100644 tests/neg-custom-args/captures/i23726.check create mode 100644 tests/neg-custom-args/captures/i23726.scala diff --git a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala index be71fe82dc72..6989ef21f081 100644 --- a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala +++ b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala @@ -457,14 +457,16 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: * Also check separation via checkType within individual arguments widened to their * formal paramater types. * - * @param fn the applied function - * @param args the flattened argument lists - * @param app the entire application tree - * @param deps cross argument dependencies: maps argument trees to - * those other arguments that where mentioned by coorresponding - * formal parameters. + * @param fn the applied function + * @param args the flattened argument lists + * @param app the entire application tree + * @param deps cross argument dependencies: maps argument trees to + * those other arguments that where mentioned by coorresponding + * formal parameters. + * @param resultPeaks peaks in the result type that could interfere with the + * hidden sets of formal parameters */ - private def checkApply(fn: Tree, args: List[Tree], app: Tree, deps: collection.Map[Tree, List[Tree]])(using Context): Unit = + private def checkApply(fn: Tree, args: List[Tree], app: Tree, deps: collection.Map[Tree, List[Tree]], resultPeaks: Refs)(using Context): Unit = val (qual, fnCaptures) = methPart(fn) match case Select(qual, _) => (qual, qual.nuType.captureSet) case _ => (fn, CaptureSet.empty) @@ -475,6 +477,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: i"""check separate $fn($args), fnCaptures = $fnCaptures, | formalCaptures = ${args.map(arg => CaptureSet(formalCaptures(arg)))}, | actualCaptures = ${args.map(arg => CaptureSet(captures(arg)))}, + | resultPeaks = ${resultPeaks}, | deps = ${deps.toList}""") val parts = qual :: args var reported: SimpleIdentitySet[Tree] = SimpleIdentitySet.empty @@ -519,26 +522,10 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: currentPeaks.hidden ++ argPeaks.hidden) end for - def collectRefs(args: List[Type], res: Type) = - args.foldLeft(argCaptures(res)): (refs, arg) => - refs ++ arg.deepCaptureSet.elems - - /** The deep capture sets of all parameters of this type (if it is a function type) */ - def argCaptures(tpe: Type): Refs = tpe match - case defn.FunctionOf(args, resultType, isContextual) => - collectRefs(args, resultType) - case defn.RefinedFunctionOf(mt) => - collectRefs(mt.paramInfos, mt.resType) - case CapturingType(parent, _) => - argCaptures(parent) - case _ => - emptyRefs - - if !deps(app).isEmpty then - lazy val appPeaks = argCaptures(app.nuType).peaks + if !resultPeaks.isEmpty then lazy val partPeaks = partsWithPeaks.toMap - for arg <- deps(app) do - if arg.needsSepCheck && !partPeaks(arg).hidden.sharedWith(appPeaks).isEmpty then + for arg <- args do + if arg.needsSepCheck && !partPeaks(arg).hidden.sharedWith(resultPeaks).isEmpty then sepApplyError(fn, parts, arg, app) end checkApply @@ -816,10 +803,15 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: * then the dependencies of an application `f(a, b, c)` of type C^{y} is the map * * [ b -> [a] - * , c -> [a, b] - * , f(a, b, c) -> [b]] + * , c -> [a, b] ] + * + * It also returns the interfering peaks of the result of the application. They are the + * peaks of argument captures and deep captures of the result function type, minus the + * those dependent on parameters. For instance, + * if `f` has the type (x: A, y: B, c: C) -> (op: () ->{b} Unit) -> List[() ->{x, y, a} Unit], its interfering + * peaks will be the peaks of `a` and `b`. */ - private def dependencies(fn: Tree, argss: List[List[Tree]], app: Tree)(using Context): collection.Map[Tree, List[Tree]] = + private def dependencies(fn: Tree, argss: List[List[Tree]], app: Tree)(using Context): (collection.Map[Tree, List[Tree]], Refs) = def isFunApply(sym: Symbol) = sym.name == nme.apply && defn.isFunctionClass(sym.owner) val mtpe = @@ -831,23 +823,47 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: val argMap = mtpsWithArgs.toMap val deps = mutable.HashMap[Tree, List[Tree]]().withDefaultValue(Nil) + def argOfDep(dep: Capability): Option[Tree] = + dep.stripReach match + case dep: TermParamRef => + Some(argMap(dep.binder)(dep.paramNum)) + case dep: ThisType if dep.cls == fn.symbol.owner => + val Select(qual, _) = fn: @unchecked // TODO can we use fn instead? + Some(qual) + case _ => + None + def recordDeps(formal: Type, actual: Tree) = - for dep <- formal.captureSet.elems.toList do - val referred = dep.stripReach match - case dep: TermParamRef => - argMap(dep.binder)(dep.paramNum) :: Nil - case dep: ThisType if dep.cls == fn.symbol.owner => - val Select(qual, _) = fn: @unchecked // TODO can we use fn instead? - qual :: Nil - case _ => - Nil + def captures = formal.captureSet + for dep <- captures.elems.toList do + val referred = argOfDep(dep) deps(actual) ++= referred + inline def isLocalRef(x: Capability): Boolean = x.isInstanceOf[TermParamRef] + + def resultArgCaptures(tpe: Type): Refs = + def collectRefs(args: List[Type], res: Type) = + args.foldLeft(resultArgCaptures(res)): (refs, arg) => + refs ++ arg.captureSet.elems + tpe match + case defn.FunctionOf(args, resultType, isContextual) => + collectRefs(args, resultType) + case defn.RefinedFunctionOf(mt) => + collectRefs(mt.paramInfos, mt.resType) + case CapturingType(parent, refs) => + resultArgCaptures(parent) ++ tpe.boxedCaptureSet.elems + case _ => + emptyRefs + for (mt, args) <- mtpsWithArgs; (formal, arg) <- mt.paramInfos.zip(args) do recordDeps(formal, arg) - recordDeps(mtpe.finalResultType, app) + + val resultType = mtpe.finalResultType + val resultCaptures = + (resultArgCaptures(resultType) ++ resultType.deepCaptureSet.elems).filter(!isLocalRef(_)) + val resultPeaks = resultCaptures.peaks capt.println(i"deps for $app = ${deps.toList}") - deps + (deps, resultPeaks) /** Decompose an application into a function prefix and a list of argument lists. @@ -860,7 +876,8 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: case TypeApply(fn, args) => recur(fn, argss) // skip type arguments case _ => if argss.nestedExists(_.needsSepCheck) then - checkApply(tree, argss.flatten, app, dependencies(tree, argss, app)) + val (deps, resultPeaks) = dependencies(tree, argss, app) + checkApply(tree, argss.flatten, app, deps, resultPeaks) recur(app, Nil) /** Is `tree` an application of `caps.unsafe.unsafeAssumeSeparate`? */ diff --git a/tests/neg-custom-args/captures/i23726.check b/tests/neg-custom-args/captures/i23726.check new file mode 100644 index 000000000000..8c8ac94a61e0 --- /dev/null +++ b/tests/neg-custom-args/captures/i23726.check @@ -0,0 +1,51 @@ +-- Error: tests/neg-custom-args/captures/i23726.scala:10:5 ------------------------------------------------------------- +10 | f1(a) // error, as expected + | ^ + |Separation failure: argument of type (a : Ref^) + |to a function of type (x: Ref^) -> List[() ->{a, x} Unit] + |corresponds to capture-polymorphic formal parameter x of type Ref^² + |and hides capabilities {a}. + |Some of these overlap with the captures of the function result with type List[() ->{a} Unit]. + | + | Hidden set of current argument : {a} + | Hidden footprint of current argument : {a} + | Capture set of function result : {a} + | Footprint set of function result : {a} + | The two sets overlap at : {a} + | + |where: ^ refers to a fresh root capability classified as Mutable created in value a when constructing mutable Ref + | ^² refers to a fresh root capability classified as Mutable created in method test1 when checking argument to parameter x of method apply +-- Error: tests/neg-custom-args/captures/i23726.scala:15:5 ------------------------------------------------------------- +15 | f3(b) // error + | ^ + |Separation failure: argument of type (b : Ref^) + |to a function of type (x: Ref^) -> (op: () ->{b} Unit) -> List[() ->{op} Unit] + |corresponds to capture-polymorphic formal parameter x of type Ref^² + |and hides capabilities {b}. + |Some of these overlap with the captures of the function result with type (op: () ->{b} Unit) -> List[() ->{op} Unit]. + | + | Hidden set of current argument : {b} + | Hidden footprint of current argument : {b} + | Capture set of function result : {op} + | Footprint set of function result : {op, b} + | The two sets overlap at : {b} + | + |where: ^ refers to a fresh root capability classified as Mutable created in value b when constructing mutable Ref + | ^² refers to a fresh root capability classified as Mutable created in method test1 when checking argument to parameter x of method apply +-- Error: tests/neg-custom-args/captures/i23726.scala:23:5 ------------------------------------------------------------- +23 | f7(a) // error + | ^ + |Separation failure: argument of type (a : Ref^) + |to a function of type (x: Ref^) ->{a, b} (y: List[Ref^{a, b}]) ->{a, b} Unit + |corresponds to capture-polymorphic formal parameter x of type Ref^² + |and hides capabilities {a}. + |Some of these overlap with the captures of the function prefix. + | + | Hidden set of current argument : {a} + | Hidden footprint of current argument : {a} + | Capture set of function prefix : {f7*} + | Footprint set of function prefix : {f7*, a, b} + | The two sets overlap at : {a} + | + |where: ^ refers to a fresh root capability classified as Mutable created in value a when constructing mutable Ref + | ^² refers to a fresh root capability classified as Mutable created in method test1 when checking argument to parameter x of method apply diff --git a/tests/neg-custom-args/captures/i23726.scala b/tests/neg-custom-args/captures/i23726.scala new file mode 100644 index 000000000000..fc833ef29583 --- /dev/null +++ b/tests/neg-custom-args/captures/i23726.scala @@ -0,0 +1,23 @@ +import language.experimental.captureChecking +import language.experimental.separationChecking +import caps.* +class Ref extends Mutable +def swap(a: Ref^, b: Ref^): Unit = () +def test1(): Unit = + val a = Ref() + val b = Ref() + val f1: (x: Ref^) -> List[() ->{a,x} Unit] = ??? + f1(a) // error, as expected + val f2: (x: Ref^) -> List[() ->{x} Unit] = ??? + f2(a) // ok, as expected + val f3: (x: Ref^) -> (op: () ->{b} Unit) -> List[() ->{op} Unit] = ??? + f3(a) // ok + f3(b) // error + val f4: (x: Ref^) -> (y: Ref^{x}) ->{x} Unit = ??? + f4(a) // ok + val f5: (x: Ref^) -> (y: List[Ref^{a}]) ->{} Unit = ??? + f5(a) // ok + val f6: (x: Ref^) -> (y: List[Ref^{a, b}]) ->{} Unit = ??? + f6(b) // ok + val f7: (x: Ref^) ->{a, b} (y: List[Ref^{a, b}]) ->{a, b} Unit = ??? + f7(a) // error From 536dfea4eae087a03f798e7f5bf9a9ba9e5b4626 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 18 Sep 2025 18:38:36 +0200 Subject: [PATCH 058/111] fix: go to definition and hover for named args in pattern match Co-Authored-By: Prince <98524116+ajafri2001@users.noreply.github.com> [Cherry-picked 47b6a29e2f8d87a96f51d13f8cb71b6116eb2c20] --- .../dotty/tools/pc/MetalsInteractive.scala | 12 +++++-- .../tests/definition/PcDefinitionSuite.scala | 31 +++++++++++++++++++ .../tools/pc/tests/hover/HoverTermSuite.scala | 12 +++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala b/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala index 4e89c687a7b8..9c7d0c3fbcf0 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala @@ -120,8 +120,9 @@ object MetalsInteractive: // For a named arg, find the target `DefDef` and jump to the param case NamedArg(name, _) :: Apply(fn, _) :: _ => val funSym = fn.symbol - if funSym.is(Synthetic) && funSym.owner.is(CaseClass) then - val sym = funSym.owner.info.member(name).symbol + lazy val owner = funSym.owner.companionClass + if funSym.is(Synthetic) && owner.is(CaseClass) then + val sym = owner.info.member(name).symbol List((sym, sym.info, None)) else val paramSymbol = @@ -130,6 +131,13 @@ object MetalsInteractive: val sym = paramSymbol.getOrElse(fn.symbol) List((sym, sym.info, None)) + case NamedArg(name, _) :: UnApply(s, _, _) :: _ => + lazy val owner = s.symbol.owner.companionClass + if s.symbol.is(Synthetic) && owner.is(CaseClass) then + val sym = owner.info.member(name).symbol + List((sym, sym.info, None)) + else Nil + case (_: untpd.ImportSelector) :: (imp: Import) :: _ => importedSymbols(imp, _.span.contains(pos.span)).map(sym => (sym, sym.info, None) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala index 0eebade8afc9..108e74738f71 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala @@ -647,3 +647,34 @@ class PcDefinitionSuite extends BasePcDefinitionSuite: | export scala.collection.immutable.V/*scala/collection/immutable/Vector. Vector.scala*/@@ector |""".stripMargin ) + + @Test def i7763 = + check( + """|case class MyItem(<>: String) + | + |def handle(item: MyItem) = + | item match { + | case MyItem(na@@me = n2) => println(n2) + | } + |""".stripMargin + ) + + @Test def `i7763-neg` = + check( + """|object MyItem: + | def unapply(name: String): Option[Int] = ??? + | + |def handle(item: String) = + | item match { + | case MyItem(na@@me = n2) => println(n2) + | } + |""".stripMargin + ) + + @Test def `i7763-apply` = + check( + """|case class MyItem(<>: String) + | + |def handle(item: String) = MyItem(na@@me = item) + |""".stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala index 60827f1e3590..f288215aa077 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala @@ -926,3 +926,15 @@ class HoverTermSuite extends BaseHoverSuite: |""".stripMargin, "val aa: Int".hover ) + + @Test def i7763 = + check( + """|case class MyItem(name: String) + | + |def handle(item: MyItem) = + | item match { + | case MyItem(na@@me = n2) => println(n2) + | } + |""".stripMargin, + "val name: String".hover + ) From bc67e271bdf100084b6d01a2ff65013f18607b4b Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Sun, 21 Sep 2025 11:56:11 +0200 Subject: [PATCH 059/111] Add missing PrefixKind.Using enum --- .../src/main/dotty/tools/pc/completions/CompletionAffix.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionAffix.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionAffix.scala index 4ed58c773a7c..78f9f5f68bfb 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionAffix.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionAffix.scala @@ -56,6 +56,7 @@ case class CompletionAffix( private def loopPrefix(prefixes: List[PrefixKind]): String = prefixes match case PrefixKind.New :: tail => "new " + loopPrefix(tail) + case PrefixKind.Using :: tail => "using " + loopPrefix(tail) case _ => "" /** @@ -87,7 +88,7 @@ enum SuffixKind: case Brace, Bracket, Template, NoSuffix enum PrefixKind: - case New + case New, Using type Suffix = Affix[SuffixKind] type Prefix = Affix[PrefixKind] From 9b55b2d4bf6199577fc142b59652c98c7983aa10 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 1 Sep 2025 17:55:14 +0100 Subject: [PATCH 060/111] Add context parameters to SemanticDB synthetics (#23381) ## Fix #22936 Depends on scalameta/scalameta#4272 (for the generated SemanticsDB to be consumable). - Add `ApplyContextTree` from generated code, generated at https://github.com/natsukagami/semanticdb-for-scala3/tree/apply-context-tree - Use ApplyContextTree instead of ApplyTree for context parameter applications [Cherry-picked 4493b49f919b5de1832c9b483c86fa640db6cbe2] --- .../dotc/semanticdb/SyntheticsExtractor.scala | 3 +- .../dotc/semanticdb/generated/Tree.scala | 36 ++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/semanticdb/SyntheticsExtractor.scala b/compiler/src/dotty/tools/dotc/semanticdb/SyntheticsExtractor.scala index af38315a857e..bf6ec40635f1 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/SyntheticsExtractor.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/SyntheticsExtractor.scala @@ -72,7 +72,8 @@ class SyntheticsExtractor: range(tree.span, tree.source), s.ApplyTree( tree.fun.toSemanticOriginal, - tree.args.map(_.toSemanticTree) + tree.args.map(_.toSemanticTree), + SymbolInformation.Property.GIVEN.value ) ).toOpt diff --git a/compiler/src/dotty/tools/dotc/semanticdb/generated/Tree.scala b/compiler/src/dotty/tools/dotc/semanticdb/generated/Tree.scala index 310e9c010826..550b839b67c4 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/generated/Tree.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/generated/Tree.scala @@ -318,10 +318,14 @@ object TreeMessage extends SemanticdbGeneratedMessageCompanion[dotty.tools.dotc // @@protoc_insertion_point(GeneratedMessageCompanion[dotty.tools.dotc.semanticdb.Tree]) } +/** @param properties + * bitmask of SymbolInformation.Property + */ @SerialVersionUID(0L) final case class ApplyTree( function: dotty.tools.dotc.semanticdb.Tree = dotty.tools.dotc.semanticdb.ApplyTree._typemapper_function.toCustom(dotty.tools.dotc.semanticdb.TreeMessage.defaultInstance), - arguments: _root_.scala.Seq[dotty.tools.dotc.semanticdb.Tree] = _root_.scala.Seq.empty + arguments: _root_.scala.Seq[dotty.tools.dotc.semanticdb.Tree] = _root_.scala.Seq.empty, + properties: _root_.scala.Int = 0 ) extends dotty.tools.dotc.semanticdb.Tree.NonEmpty with SemanticdbGeneratedMessage derives CanEqual { @transient @sharable private var __serializedSizeMemoized: _root_.scala.Int = 0 @@ -338,6 +342,13 @@ final case class ApplyTree( val __value = dotty.tools.dotc.semanticdb.ApplyTree._typemapper_arguments.toBase(__item) __size += 1 + SemanticdbOutputStream.computeUInt32SizeNoTag(__value.serializedSize) + __value.serializedSize } + + { + val __value = properties + if (__value != 0) { + __size += SemanticdbOutputStream.computeInt32Size(3, __value) + } + }; __size } override def serializedSize: _root_.scala.Int = { @@ -364,12 +375,19 @@ final case class ApplyTree( _output__.writeUInt32NoTag(__m.serializedSize) __m.writeTo(_output__) }; + { + val __v = properties + if (__v != 0) { + _output__.writeInt32(3, __v) + } + }; } def withFunction(__v: dotty.tools.dotc.semanticdb.Tree): ApplyTree = copy(function = __v) def clearArguments = copy(arguments = _root_.scala.Seq.empty) def addArguments(__vs: dotty.tools.dotc.semanticdb.Tree *): ApplyTree = addAllArguments(__vs) def addAllArguments(__vs: Iterable[dotty.tools.dotc.semanticdb.Tree]): ApplyTree = copy(arguments = arguments ++ __vs) def withArguments(__v: _root_.scala.Seq[dotty.tools.dotc.semanticdb.Tree]): ApplyTree = copy(arguments = __v) + def withProperties(__v: _root_.scala.Int): ApplyTree = copy(properties = __v) @@ -382,6 +400,7 @@ object ApplyTree extends SemanticdbGeneratedMessageCompanion[dotty.tools.dotc.s def parseFrom(`_input__`: SemanticdbInputStream): dotty.tools.dotc.semanticdb.ApplyTree = { var __function: _root_.scala.Option[dotty.tools.dotc.semanticdb.TreeMessage] = _root_.scala.None val __arguments: _root_.scala.collection.immutable.VectorBuilder[dotty.tools.dotc.semanticdb.Tree] = new _root_.scala.collection.immutable.VectorBuilder[dotty.tools.dotc.semanticdb.Tree] + var __properties: _root_.scala.Int = 0 var _done__ = false while (!_done__) { val _tag__ = _input__.readTag() @@ -391,12 +410,15 @@ object ApplyTree extends SemanticdbGeneratedMessageCompanion[dotty.tools.dotc.s __function = _root_.scala.Some(__function.fold(LiteParser.readMessage[dotty.tools.dotc.semanticdb.TreeMessage](_input__))(LiteParser.readMessage(_input__, _))) case 18 => __arguments += dotty.tools.dotc.semanticdb.ApplyTree._typemapper_arguments.toCustom(LiteParser.readMessage[dotty.tools.dotc.semanticdb.TreeMessage](_input__)) + case 24 => + __properties = _input__.readInt32() case tag => _input__.skipField(tag) } } dotty.tools.dotc.semanticdb.ApplyTree( function = dotty.tools.dotc.semanticdb.ApplyTree._typemapper_function.toCustom(__function.getOrElse(dotty.tools.dotc.semanticdb.TreeMessage.defaultInstance)), - arguments = __arguments.result() + arguments = __arguments.result(), + properties = __properties ) } @@ -407,20 +429,24 @@ object ApplyTree extends SemanticdbGeneratedMessageCompanion[dotty.tools.dotc.s lazy val defaultInstance = dotty.tools.dotc.semanticdb.ApplyTree( function = dotty.tools.dotc.semanticdb.ApplyTree._typemapper_function.toCustom(dotty.tools.dotc.semanticdb.TreeMessage.defaultInstance), - arguments = _root_.scala.Seq.empty + arguments = _root_.scala.Seq.empty, + properties = 0 ) final val FUNCTION_FIELD_NUMBER = 1 final val ARGUMENTS_FIELD_NUMBER = 2 + final val PROPERTIES_FIELD_NUMBER = 3 @transient @sharable private[semanticdb] val _typemapper_function: SemanticdbTypeMapper[dotty.tools.dotc.semanticdb.TreeMessage, dotty.tools.dotc.semanticdb.Tree] = implicitly[SemanticdbTypeMapper[dotty.tools.dotc.semanticdb.TreeMessage, dotty.tools.dotc.semanticdb.Tree]] @transient @sharable private[semanticdb] val _typemapper_arguments: SemanticdbTypeMapper[dotty.tools.dotc.semanticdb.TreeMessage, dotty.tools.dotc.semanticdb.Tree] = implicitly[SemanticdbTypeMapper[dotty.tools.dotc.semanticdb.TreeMessage, dotty.tools.dotc.semanticdb.Tree]] def of( function: dotty.tools.dotc.semanticdb.Tree, - arguments: _root_.scala.Seq[dotty.tools.dotc.semanticdb.Tree] + arguments: _root_.scala.Seq[dotty.tools.dotc.semanticdb.Tree], + properties: _root_.scala.Int ): _root_.dotty.tools.dotc.semanticdb.ApplyTree = _root_.dotty.tools.dotc.semanticdb.ApplyTree( function, - arguments + arguments, + properties ) // @@protoc_insertion_point(GeneratedMessageCompanion[dotty.tools.dotc.semanticdb.ApplyTree]) } From fb58b01a4e3a0702311b38f8ff3d0f84e9fdbcd3 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Wed, 30 Jul 2025 11:53:21 +0200 Subject: [PATCH 061/111] bugfix: Include synthetic apply in semanticdb (#23629) Fixes https://github.com/scalameta/metals/issues/7666 Some weird entries around string interpolation, but they actually make sense. [Cherry-picked 74b9e85a0a65004dedcb321d6e496ff473da6793] --- .../dotc/semanticdb/SyntheticsExtractor.scala | 4 +- tests/semanticdb/metac.expect | 184 ++++++++++++++++-- 2 files changed, 169 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/semanticdb/SyntheticsExtractor.scala b/compiler/src/dotty/tools/dotc/semanticdb/SyntheticsExtractor.scala index bf6ec40635f1..c6cf834c6959 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/SyntheticsExtractor.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/SyntheticsExtractor.scala @@ -77,7 +77,9 @@ class SyntheticsExtractor: ) ).toOpt - case tree: Apply if tree.fun.symbol.is(Implicit) => + case tree: Apply + if tree.fun.symbol.is(Implicit) || + (tree.fun.symbol.name == nme.apply && tree.fun.span.isSynthetic) => val pos = range(tree.span, tree.source) s.Synthetic( pos, diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 1b303fa563db..13f68886dd87 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -519,6 +519,7 @@ Text => empty Language => Scala Symbols => 22 entries Occurrences => 17 entries +Synthetics => 2 entries Symbols: caseclass/CaseClass# => case class CaseClass extends Object with Product with Serializable { self: CaseClass => +8 decls } @@ -563,6 +564,10 @@ Occurrences: [6:15..6:24): CaseClass -> caseclass/CaseClass# [6:27..6:36): CaseClass -> caseclass/CaseClass. +Synthetics: +[5:35..5:52):CaseClass(int, 0) => apply(*) +[6:27..6:42):CaseClass(0, 0) => apply(*) + expect/Classes.scala -------------------- @@ -574,7 +579,7 @@ Language => Scala Symbols => 108 entries Occurrences => 127 entries Diagnostics => 11 entries -Synthetics => 2 entries +Synthetics => 3 entries Symbols: classes/C1# => final class C1 extends AnyVal { self: C1 => +2 decls } @@ -837,6 +842,7 @@ This construct can be rewritten automatically under -rewrite -source 3.4-migrati Synthetics: [51:16..51:27):List(1).map => *[Int] [51:16..51:20):List => *.apply[Int] +[51:16..51:23):List(1) => List.apply[Int](*) expect/Deprecated.scala ----------------------- @@ -926,6 +932,7 @@ Language => Scala Symbols => 30 entries Occurrences => 49 entries Diagnostics => 3 entries +Synthetics => 2 entries Symbols: endmarkers/Container# => class Container extends Object { self: Container => +5 decls } @@ -1015,6 +1022,10 @@ Diagnostics: [42:8..42:16): [warning] unused local definition [46:8..46:16): [warning] unused local definition +Synthetics: +[23:6..23:13):(1,2,3) => Tuple3.apply[Int, Int, Int](*) +[27:6..27:13):(4,5,6) => Tuple3.apply[Int, Int, Int](*) + expect/EndMarkers2.scala ------------------------ @@ -1097,7 +1108,7 @@ Language => Scala Symbols => 181 entries Occurrences => 159 entries Diagnostics => 1 entries -Synthetics => 6 entries +Synthetics => 9 entries Symbols: _empty_/Enums. => final object Enums extends Object { self: Enums.type => +30 decls } @@ -1447,11 +1458,14 @@ Diagnostics: [30:12..30:17): [warning] unused explicit parameter Synthetics: +[49:27..49:33):Refl() => Refl.apply[T](*) [52:9..52:13):Refl => *.unapply[Option[B]] [52:31..52:50):identity[Option[B]] => *[Function1[A, Option[B]]] [54:14..54:18):Some => *.apply[Some[Int]] +[54:14..54:27):Some(Some(1)) => Some.apply[Some[Int]](*) [54:14..54:34):Some(Some(1)).unwrap => *(given_<:<_T_T[Option[Int]]) [54:19..54:23):Some => *.apply[Int] +[54:19..54:26):Some(1) => Some.apply[Int](*) [54:28..54:34):unwrap => *[Some[Int], Int] expect/EtaExpansion.scala @@ -1464,7 +1478,7 @@ Text => empty Language => Scala Symbols => 3 entries Occurrences => 9 entries -Synthetics => 5 entries +Synthetics => 7 entries Symbols: example/EtaExpansion# => class EtaExpansion extends Object { self: EtaExpansion => +1 decls } @@ -1485,9 +1499,11 @@ Occurrences: Synthetics: [3:2..3:13):Some(1).map => *[Int] [3:2..3:6):Some => *.apply[Int] +[3:2..3:9):Some(1) => Some.apply[Int](*) [3:14..3:22):identity => *[Int] [4:2..4:18):List(1).foldLeft => *[String] [4:2..4:6):List => *.apply[Int] +[4:2..4:9):List(1) => List.apply[Int](*) expect/Example.scala -------------------- @@ -1500,6 +1516,7 @@ Language => Scala Symbols => 5 entries Occurrences => 23 entries Diagnostics => 1 entries +Synthetics => 1 entries Symbols: example/Example. => final object Example extends Object { self: Example.type => +3 decls } @@ -1536,6 +1553,9 @@ Occurrences: Diagnostics: [2:24..2:30): [warning] unused import +Synthetics: +[9:37..9:37): => ClassTag.apply[Int](*) + expect/Extension.scala ---------------------- @@ -1546,7 +1566,7 @@ Text => empty Language => Scala Symbols => 32 entries Occurrences => 66 entries -Synthetics => 1 entries +Synthetics => 2 entries Symbols: ext/DeckUsage. => final object DeckUsage extends Object { self: DeckUsage.type => +2 decls } @@ -1651,6 +1671,7 @@ Occurrences: [26:7..26:14): fooSize -> ext/Extension$package.Deck.fooSize(). Synthetics: +[4:36..4:42):(s, i) => Tuple2.apply[String, Int](*) [14:46..14:61):summon[Read[T]] => *(x$2) expect/ForComprehension.scala @@ -1663,7 +1684,7 @@ Text => empty Language => Scala Symbols => 13 entries Occurrences => 53 entries -Synthetics => 6 entries +Synthetics => 23 entries Symbols: example/ForComprehension# => class ForComprehension extends Object { self: ForComprehension => +1 decls } @@ -1737,11 +1758,50 @@ Occurrences: Synthetics: [4:9..4:13):List => *.apply[Int] +[4:9..4:16):List(1) => List.apply[Int](*) +[5:4..7:5):b <- List(1) + if b > 1 + c => Tuple2.apply[Int, Int](*) [5:9..5:13):List => *.apply[Int] +[5:9..5:16):List(1) => List.apply[Int](*) +[8:10..8:19):(a, b, c) => Tuple3.apply[Int, Int, Int](*) [10:9..10:13):List => *.apply[Int] +[10:9..10:16):List(1) => List.apply[Int](*) [11:9..11:13):List => *.apply[Int] +[11:9..11:16):List(a) => List.apply[Int](*) +[12:7..15:5):( + a, + b + ) => Tuple2.apply[Int, Int](*) +[15:9..15:15):(1, 2) => Tuple2.apply[Int, Int](*) [19:9..19:13):List => *.apply[Tuple2[Int, Int]] +[19:9..19:21):List((a, b)) => List.apply[Tuple2[Int, Int]](*) +[19:14..19:20):(a, b) => Tuple2.apply[Int, Int](*) +[20:7..25:5):( + a, + b, + c, + d + ) => Tuple4.apply[Int, Int, Int, Int](*) +[25:9..25:21):(1, 2, 3, 4) => Tuple4.apply[Int, Int, Int, Int](*) +[26:4..26:5):e => Tuple2.apply[Tuple2[Int, Int], Tuple4[Int, Int, Int, Int]](*) +[26:8..31:5):( + a, + b, + c, + d + ) => Tuple4.apply[Int, Int, Int, Int](*) +[32:12..32:24):(1, 2, 3, 4) => Tuple4.apply[Int, Int, Int, Int](*) [33:9..33:13):List => *.apply[Tuple4[Int, Int, Int, Int]] +[33:9..33:16):List(e) => List.apply[Tuple4[Int, Int, Int, Int]](*) +[35:4..42:5):( + a, + b, + c, + d, + e, + f + ) => Tuple6.apply[Int, Int, Int, Int, Tuple4[Int, Int, Int, Int], Tuple4[Int, Int, Int, Int]](*) expect/Givens.scala ------------------- @@ -1753,7 +1813,7 @@ Text => empty Language => Scala Symbols => 33 entries Occurrences => 72 entries -Synthetics => 3 entries +Synthetics => 6 entries Symbols: a/b/Givens. => final object Givens extends Object { self: Givens.type => +13 decls } @@ -1865,6 +1925,9 @@ Occurrences: [27:59..27:64): empty -> a/b/Givens.Monoid#empty(). Synthetics: +[6:21..6:37):Hello, I am $any => apply(*) +[9:23..9:41):Goodbye, from $any => apply(*) +[10:22..10:40):So Long, from $any => apply(*) [12:17..12:25):sayHello => *[Int] [13:19..13:29):sayGoodbye => *[Int] [14:18..14:27):saySoLong => *[Int] @@ -1879,7 +1942,7 @@ Text => empty Language => Scala Symbols => 23 entries Occurrences => 52 entries -Synthetics => 6 entries +Synthetics => 9 entries Symbols: example/ImplicitConversion# => class ImplicitConversion extends Object { self: ImplicitConversion => +9 decls } @@ -1961,12 +2024,17 @@ Occurrences: [34:58..34:63): other -> example/ImplicitConversion.newAny2stringadd#`+`().(other) Synthetics: +[11:14..11:20):(1, 2) => Tuple2.apply[Int, Int](*) [15:2..15:9):message => augmentString(*) [17:2..17:7):tuple => newAny2stringadd[Tuple2[Int, Int]](*) [20:15..20:22):message => string2Number(*) +[23:4..23:26):Hello $message $number => apply(*) [24:2..26:16):s"""Hello |$message |$number""" => augmentString(*) +[24:6..26:13):Hello + |$message + |$number => apply(*) [28:15..28:19):char => char2int(*) [29:16..29:20):char => char2long(*) @@ -2017,7 +2085,7 @@ Text => empty Language => Scala Symbols => 7 entries Occurrences => 23 entries -Synthetics => 6 entries +Synthetics => 7 entries Symbols: _empty_/InfoMacro. => final object InfoMacro extends Object { self: InfoMacro.type => +3 decls } @@ -2057,6 +2125,7 @@ Synthetics: [3:48..3:69):reportInfoMacro('msg) => *(contextual$1) [3:64..3:68):'msg => orig()(contextual$1) [6:11..6:17):quotes => *(x$2) +[9:18..9:54):Info from macro: ${msg.valueOrAbort} => apply(*) [9:37..9:53):msg.valueOrAbort => *(StringFromExpr[String]) [9:41..9:53):valueOrAbort => *[String] [11:4..11:11):'{ () } => orig(())(x$2) @@ -2100,7 +2169,7 @@ Text => empty Language => Scala Symbols => 8 entries Occurrences => 53 entries -Synthetics => 2 entries +Synthetics => 4 entries Symbols: example/InstrumentTyper# => class InstrumentTyper extends Object { self: AnyRef & InstrumentTyper => +5 decls } @@ -2169,7 +2238,22 @@ Occurrences: Synthetics: [8:12..8:16):List => *.apply[Char | String | LinkOption | Int | Long | Class[Option[Int]] | Float | Double | Boolean | Unit | List[Nothing]] +[8:12..21:3):List( + Literal.int, + Literal.long, + Literal.float, + Literal.double, + Literal.nil, + Literal.char, + Literal.string, + Literal.bool, + Literal.unit, + Literal.javaEnum, + Literal.clazzOf, + List() + ) => List.apply[Char | String | LinkOption | Int | Long | Class[Option[Int]] | Float | Double | Boolean | Unit | List[Nothing]](*) [20:4..20:8):List => *.apply[Nothing] +[20:4..20:10):List() => List.apply[Nothing](*) expect/InventedNames.scala -------------------------- @@ -2317,7 +2401,7 @@ Text => empty Language => Scala Symbols => 7 entries Occurrences => 24 entries -Synthetics => 3 entries +Synthetics => 5 entries Symbols: example/Issue1749# => class Issue1749 extends Object { self: Issue1749 => +3 decls } @@ -2356,8 +2440,10 @@ Occurrences: Synthetics: [8:2..8:10):(x1, x1) => orderingToOrdered[Tuple2[Int, Int]](*) +[8:2..8:10):(x1, x1) => Tuple2.apply[Int, Int](*) [8:2..8:10):(x1, x1) => *(Tuple2(Int, Int)) [8:10..8:10): => *(Int, Int) +[9:13..9:21):(x2, x2) => Tuple2.apply[Int, Int](*) expect/JavaStaticVar.scala -------------------------- @@ -2432,7 +2518,7 @@ Text => empty Language => Scala Symbols => 3 entries Occurrences => 6 entries -Synthetics => 1 entries +Synthetics => 2 entries Symbols: local0 => val local x: Int @@ -2449,6 +2535,7 @@ Occurrences: Synthetics: [5:4..5:8):List => *.apply[Int] +[5:4..5:11):List(x) => List.apply[Int](*) expect/MatchType.scala ---------------------- @@ -2960,6 +3047,7 @@ Text => empty Language => Scala Symbols => 41 entries Occurrences => 41 entries +Synthetics => 1 entries Symbols: example/NamedApplyBlockCaseClassConstruction. => final object NamedApplyBlockCaseClassConstruction extends Object { self: NamedApplyBlockCaseClassConstruction.type => +6 decls } @@ -3047,6 +3135,9 @@ Occurrences: [12:16..12:24): bodyText -> example/NamedApplyBlockCaseClassConstruction.bodyText. [12:26..12:30): tail -> example/NamedApplyBlockCaseClassConstruction.Msg.apply().(tail) +Synthetics: +[12:12..12:40):Msg(bodyText, tail = "tail") => apply(*) + expect/NamedArguments.scala --------------------------- @@ -3058,6 +3149,7 @@ Language => Scala Symbols => 16 entries Occurrences => 12 entries Diagnostics => 2 entries +Synthetics => 1 entries Symbols: example/NamedArguments# => class NamedArguments extends Object { self: NamedArguments => +4 decls } @@ -3095,6 +3187,9 @@ Diagnostics: [4:2..4:21): [warning] A pure expression does nothing in statement position [5:2..5:27): [warning] A pure expression does nothing in statement position +Synthetics: +[4:2..4:21):User(name = "John") => apply(*) + expect/NewModifiers.scala ------------------------- @@ -3287,7 +3382,7 @@ Language => Scala Symbols => 68 entries Occurrences => 115 entries Diagnostics => 1 entries -Synthetics => 3 entries +Synthetics => 4 entries Symbols: example/C# => class C extends Object { self: C => +3 decls } @@ -3483,6 +3578,7 @@ This construct can be rewritten automatically under -rewrite -source 3.4-migrati Synthetics: [15:23..15:34):elems.toMap => *[String, Any] [15:23..15:34):elems.toMap => *(refl[Tuple2[String, Any]]) +[16:41..16:53):fields(name) => apply(*) [32:47..32:56):s.pickOne => *[String] expect/RightAssociativeExtension.scala @@ -3495,6 +3591,7 @@ Text => empty Language => Scala Symbols => 5 entries Occurrences => 12 entries +Synthetics => 1 entries Symbols: ext/RightAssociativeExtension$package. => final package object ext extends Object { self: ext.type => +3 decls } @@ -3517,6 +3614,9 @@ Occurrences: [5:4..5:5): b <- ext/RightAssociativeExtension$package.b. [5:14..5:17): :*: -> ext/RightAssociativeExtension$package.`:*:`(). +Synthetics: +[3:36..3:42):(s, i) => Tuple2.apply[String, Int](*) + expect/Selfs.scala ------------------ @@ -3583,7 +3683,7 @@ Language => Scala Symbols => 20 entries Occurrences => 31 entries Diagnostics => 14 entries -Synthetics => 1 entries +Synthetics => 2 entries Symbols: example/Shadow# => class Shadow extends Object { self: Shadow => +5 decls } @@ -3658,6 +3758,7 @@ Diagnostics: Synthetics: [16:16..16:20):List => *.apply[Int] +[16:16..16:27):List(1,2,3) => List.apply[Int](*) expect/StructuralTypes.scala ---------------------------- @@ -3743,7 +3844,7 @@ Language => Scala Symbols => 62 entries Occurrences => 165 entries Diagnostics => 4 entries -Synthetics => 39 entries +Synthetics => 48 entries Symbols: example/Synthetic# => class Synthetic extends Object { self: Synthetic => +23 decls } @@ -3985,11 +4086,14 @@ Diagnostics: Synthetics: [5:2..5:13):List(1).map => *[Int] [5:2..5:6):List => *.apply[Int] +[5:2..5:9):List(1) => List.apply[Int](*) [6:2..6:18):Array.empty[Int] => intArrayOps(*) +[6:18..6:18): => ClassTag.apply[Int](*) [7:2..7:8):"fooo" => augmentString(*) [10:13..10:24):"name:(.*)" => augmentString(*) [11:8..11:11):#:: => *.unapply[Int] [11:17..11:25):LazyList => *.apply[Int] +[11:17..11:31):LazyList(1, 2) => LazyList.apply[Int](*) [13:4..13:28):#:: 2 #:: LazyList.empty => *[Int] [13:8..13:28):2 #:: LazyList.empty => toDeferrer[Int](*) [13:10..13:28):#:: LazyList.empty => *[Int] @@ -3998,6 +4102,7 @@ Synthetics: [15:9..15:12):#:: => *.unapply[Int] [15:16..15:19):#:: => *.unapply[Int] [15:25..15:33):LazyList => *.apply[Int] +[15:25..15:39):LazyList(1, 2) => LazyList.apply[Int](*) [17:14..17:38):#:: 2 #:: LazyList.empty => *[Int] [17:18..17:38):2 #:: LazyList.empty => toDeferrer[Int](*) [17:20..17:38):#:: LazyList.empty => *[Int] @@ -4009,8 +4114,13 @@ Synthetics: [19:46..19:47):x => ArrowAssoc[Int](*) [20:12..20:13):1 => intWrapper(*) [20:26..20:27):0 => intWrapper(*) +[20:44..20:50):(i, j) => Tuple2.apply[Int, Int](*) [21:12..21:13):1 => intWrapper(*) [21:26..21:27):0 => intWrapper(*) +[21:58..21:64):(i, j) => Tuple2.apply[Int, Int](*) +[25:4..25:7):s() => apply(*) +[28:4..28:9):Bar() => apply(*) +[29:4..29:36):null.asInstanceOf[Int => Int](2) => apply(*) [32:35..32:49):Array.empty[T] => *(evidence$1) [36:22..36:27):new F => orderingToOrdered[F](*) [36:22..36:27):new F => *(ordering) @@ -4033,7 +4143,7 @@ Text => empty Language => Scala Symbols => 2 entries Occurrences => 5 entries -Synthetics => 2 entries +Synthetics => 3 entries Symbols: example/Tabs$package. => final package object example extends Object { self: example.type => +2 decls } @@ -4050,6 +4160,7 @@ Synthetics: [3:1..4:6):List(1,2,3) .map => *[Int] [3:1..3:5):List => *.apply[Int] +[3:1..3:12):List(1,2,3) => List.apply[Int](*) expect/TargetName.scala ----------------------- @@ -4144,7 +4255,7 @@ Language => Scala Symbols => 22 entries Occurrences => 45 entries Diagnostics => 3 entries -Synthetics => 11 entries +Synthetics => 21 entries Symbols: example/ValPattern# => class ValPattern extends Object { self: ValPattern => +14 decls } @@ -4223,17 +4334,41 @@ Diagnostics: [31:15..31:25): [warning] unset local variable, consider using an immutable val instead Synthetics: +[4:22..4:28):(1, 2) => Tuple2.apply[Int, Int](*) [5:6..5:10):Some => *.unapply[Int] [6:4..6:8):Some => *.apply[Int] +[6:4..6:11):Some(1) => Some.apply[Int](*) [8:6..8:10):List => *.unapplySeq[Nothing] [8:11..8:15):Some => *.unapply[Nothing] +[10:28..10:34):(1, 2) => Tuple2.apply[Int, Int](*) [11:6..11:10):Some => *.unapply[Int] [12:4..12:8):Some => *.apply[Int] +[12:4..12:11):Some(1) => Some.apply[Int](*) +[16:6..23:7):( + number1, + left, + right, + number1Var, + leftVar, + rightVar + ) => Tuple6.apply[Int, Int, Int, Int, Int, Int](*) [25:4..25:11):locally => *[Unit] +[26:26..26:32):(1, 2) => Tuple2.apply[Int, Int](*) [27:10..27:14):Some => *.unapply[Int] [28:8..28:12):Some => *.apply[Int] +[28:8..28:15):Some(1) => Some.apply[Int](*) +[30:32..30:38):(1, 2) => Tuple2.apply[Int, Int](*) [31:10..31:14):Some => *.unapply[Int] [32:8..32:12):Some => *.apply[Int] +[32:8..32:15):Some(1) => Some.apply[Int](*) +[34:8..41:9):( + number1, + left, + right, + number1Var, + leftVar, + rightVar + ) => Tuple6.apply[Int, Int, Int, Int, Int, Int](*) expect/Vals.scala ----------------- @@ -4833,6 +4968,7 @@ Language => Scala Symbols => 24 entries Occurrences => 63 entries Diagnostics => 2 entries +Synthetics => 1 entries Symbols: _empty_/Copy# => trait Copy [typeparam In <: Txn[In], typeparam Out <: Txn[Out]] extends Object { self: Copy[In, Out] => +5 decls } @@ -4929,6 +5065,9 @@ Diagnostics: [13:12..13:17): [warning] unused pattern variable [13:28..13:34): [warning] unused pattern variable +Synthetics: +[12:4..12:13):(in, out) => Tuple2.apply[Repr[In], Repr[Out]](*) + expect/inlineconsume.scala -------------------------- @@ -5023,7 +5162,7 @@ Text => empty Language => Scala Symbols => 17 entries Occurrences => 31 entries -Synthetics => 1 entries +Synthetics => 2 entries Symbols: _empty_/Concrete# => class Concrete extends NullaryTest[Int, List] { self: Concrete => +3 decls } @@ -5079,6 +5218,7 @@ Occurrences: Synthetics: [13:17..13:21):List => *.apply[Int] +[13:17..13:28):List(1,2,3) => List.apply[Int](*) expect/recursion.scala ---------------------- @@ -5090,6 +5230,7 @@ Text => empty Language => Scala Symbols => 36 entries Occurrences => 48 entries +Synthetics => 2 entries Symbols: local0 => type N$1 <: Nat @@ -5179,6 +5320,10 @@ Occurrences: [23:35..23:39): Zero -> recursion/Nats.Zero. [23:40..23:42): ++ -> recursion/Nats.Nat#`++`(). +Synthetics: +[5:50..5:60):Succ(this) => Succ.apply[Nat.this.type](*) +[5:50..5:60):Succ(this) => Succ.apply[Nat.this.type](*) + expect/semanticdb-Definitions.scala ----------------------------------- @@ -5798,7 +5943,7 @@ Text => empty Language => Scala Symbols => 18 entries Occurrences => 21 entries -Synthetics => 3 entries +Synthetics => 6 entries Symbols: _empty_/AnObject. => final object AnObject extends Object { self: AnObject.type => +6 decls } @@ -5845,8 +5990,11 @@ Occurrences: Synthetics: [11:2..11:6):List => *.apply[Int] +[11:2..11:12):List(1, 2) => List.apply[Int](*) [12:2..12:12):List.apply => *[Nothing] +[12:2..12:14):List.apply() => List.apply[Nothing](*) [13:2..13:14):List.`apply` => *[Nothing] +[13:2..13:16):List.`apply`() => List.apply[Nothing](*) expect/toplevel.scala --------------------- From 38c0e65f3266df8fc44c9ad961193e0d55080eff Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Wed, 30 Jul 2025 12:53:02 +0200 Subject: [PATCH 062/111] Add missing reference page: `Quoted Patterns with Polymorphic Functions` [Cherry-picked 009e064fcb7edc381f19d4c9f9565efa34537e48] --- docs/sidebar.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sidebar.yml b/docs/sidebar.yml index f0ca5433d649..e49bddfd2e7d 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -172,6 +172,7 @@ subsection: - page: reference/experimental/runtimeChecked.md - page: reference/experimental/unrolled-defs.md - page: reference/experimental/package-object-values.md + - page: reference/experimental/quoted-patterns-with-polymorphic-functions.md - page: reference/syntax.md - title: Language Versions index: reference/language-versions/language-versions.md From 00c04a378cd8fdd1744534595cbef52060d3ff83 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 31 Jul 2025 22:55:11 +0200 Subject: [PATCH 063/111] [build] Increment default thread stack size from to 2MB (was 1MB) (#23638) I suspect the StackOverflow errors started to get triggered recently as our build got larger/more complicated - increae in the thread stack size should mitigate the issue, otherwise, we'll need to investigate it again. [Cherry-picked fe6b7eb1d5a72993b1ebe4f4d02ce3b61f47514f] --- .jvmopts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jvmopts b/.jvmopts index 4df4f826d1db..d8606eba733e 100644 --- a/.jvmopts +++ b/.jvmopts @@ -1,4 +1,4 @@ --Xss1m +-Xss2m -Xms1024m -Xmx8192m -XX:MaxInlineLevel=35 From 32dc1c654a3765e45b5a652024e1c7f597d0f743 Mon Sep 17 00:00:00 2001 From: vder Date: Thu, 14 Aug 2025 14:47:47 +0200 Subject: [PATCH 064/111] Draft: additional completions for using clause (#23647) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix for #22939 --------- Co-authored-by: Piotr Fałdrowicz --- .../tools/pc/completions/Completions.scala | 7 +-- .../tests/completion/CompletionArgSuite.scala | 59 +++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala index bbf8fc522a5d..80e551dacd31 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -201,7 +201,7 @@ class Completions( ) end isAbstractType - private def findSuffix(symbol: Symbol): CompletionAffix = + private def findSuffix(symbol: Symbol, adjustedPath: List[untpd.Tree]): CompletionAffix = CompletionAffix.empty .chain { suffix => // for [] suffix if shouldAddSuffix && symbol.info.typeParams.nonEmpty then @@ -217,7 +217,6 @@ class Completions( suffix.withNewPrefix(Affix(PrefixKind.Using)) case _ => suffix case _ => suffix - } .chain { suffix => // for () suffix if shouldAddSuffix && symbol.is(Flags.Method) then @@ -290,7 +289,7 @@ class Completions( val existsApply = extraMethodDenots.exists(_.symbol.name == nme.apply) extraMethodDenots.map { methodDenot => - val suffix = findSuffix(methodDenot.symbol) + val suffix = findSuffix(methodDenot.symbol, adjustedPath) val affix = if methodDenot.symbol.isConstructor && existsApply then adjustedPath match case (select @ Select(qual, _)) :: _ => @@ -312,7 +311,7 @@ class Completions( if skipOriginalDenot then extraCompletionValues else - val suffix = findSuffix(denot.symbol) + val suffix = findSuffix(denot.symbol, adjustedPath) val name = undoBacktick(label) val denotCompletionValue = toCompletionValue(name, denot, suffix) denotCompletionValue :: extraCompletionValues diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala index 044b5456d31d..910044485896 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala @@ -238,6 +238,65 @@ class CompletionArgSuite extends BaseCompletionSuite: "" ) + @Test def `using` = + checkEdit( + s"""|def hello(using String): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | hello(st@@) + |""".stripMargin, + s"""|def hello(using String): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | hello(using str) + |""".stripMargin, + assertSingleItem = false) + + @Test def `using2` = + checkEdit( + s"""|def hello(using String): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | hello(using st@@) + |""".stripMargin, + s"""|def hello(using String): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | hello(using str) + |""".stripMargin, + assertSingleItem = false) + + @Test def `using3` = + checkEdit( + s"""|def hello(using String, Int): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | val int = 4 + | hello(str, in@@) + |""".stripMargin, + s"""|def hello(using String, Int): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | val int = 4 + | hello(str, int) + |""".stripMargin, + assertSingleItem = false) + + @Test def `using4` = + checkEdit( + s"""|def hello(name: String)(using String): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | hello("name")(str@@) + |""".stripMargin, + s"""|def hello(name: String)(using String): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | hello("name")(using str) + |""".stripMargin, + assertSingleItem = false + ) + @Test def `default-args` = check( s"""|object Main { From bb7c4713dfbf34c5594360d4be23e2e7655ced32 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 22 Sep 2025 22:43:06 +0200 Subject: [PATCH 065/111] Draft: additional completions for using clause (#23647) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix for #22939 --------- Co-authored-by: Piotr Fałdrowicz [Cherry-picked 2b004ee695be634f9aea4ed55ededb71a407ce1c][modified] From 2f053b1ba758d2e1a1d9baabfdd0faf753c17bba Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 5 Aug 2025 21:52:01 +0200 Subject: [PATCH 066/111] Handle assertion error in TyperState (#23665) #23609 triggers an assertion error in TyperState. The relevant explanation seems to be in ProtoTypes.scala: ```scala // To respect the pre-condition of `mergeConstraintWith` and keep // `protoTyperState` committable we must ensure that it does not // contain any type variable which don't already exist in the passed // TyperState. This is achieved by instantiating any such type // variable. NOTE: this does not suffice to discard type variables // in ancestors of `protoTyperState`, if this situation ever // comes up, an assertion in TyperState will trigger and this code // will need to be generalized. ``` We should go to the bottom of it and fix the assertion. But before that's done this PR offers a temporary hack to catch the exception when it is triggered from a new code path created by PR #23532. This should fix the regression reported in #23609. We should leave the issue open as a reminder that we still need a better fix. Also: handle crash due to missing span in a migration helper. [Cherry-picked 408298d7bf73921303c31c330deae8c618b589e7] --- compiler/src/dotty/tools/dotc/core/TyperState.scala | 10 +++++++--- .../src/dotty/tools/dotc/typer/Migrations.scala | 1 + compiler/src/dotty/tools/dotc/typer/Typer.scala | 13 +++++++++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index d4345916ba77..c0db3301b8b5 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -28,6 +28,8 @@ object TyperState { opaque type Snapshot = (Constraint, TypeVars, LevelMap) + class BadTyperStateAssertion(msg: String) extends AssertionError(msg) + extension (ts: TyperState) def snapshot()(using Context): Snapshot = (ts.constraint, ts.ownedVars, ts.upLevels) @@ -43,7 +45,7 @@ object TyperState { } class TyperState() { - import TyperState.LevelMap + import TyperState.{LevelMap, BadTyperStateAssertion} private var myId: Int = uninitialized def id: Int = myId @@ -269,8 +271,10 @@ class TyperState() { */ private def includeVar(tvar: TypeVar)(using Context): Unit = val oldState = tvar.owningState.nn.get - assert(oldState == null || !oldState.isCommittable, - i"$this attempted to take ownership of $tvar which is already owned by committable $oldState") + + if oldState != null && oldState.isCommittable then + throw BadTyperStateAssertion( + i"$this attempted to take ownership of $tvar which is already owned by committable $oldState") tvar.owningState = new WeakReference(this) ownedVars += tvar diff --git a/compiler/src/dotty/tools/dotc/typer/Migrations.scala b/compiler/src/dotty/tools/dotc/typer/Migrations.scala index d811987383c6..31bb29e0e6c5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Migrations.scala +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -162,6 +162,7 @@ trait Migrations: private def checkParentheses(tree: Tree, pt: FunProto)(using Context): Boolean = val ptSpan = pt.args.head.span ptSpan.exists + && tree.span.exists && ctx.source.content .slice(tree.span.end, ptSpan.start) .exists(_ == '(') diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5349cade601e..57221d907011 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4380,11 +4380,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // But in this case we should search with additional arguments typed only if there // is no default argument. + // Try to constrain the result using `pt1`, but back out if a BadTyperStateAssertion + // is thrown. TODO Find out why the bad typer state arises and prevent it. The try-catch + // is a temporary hack to keep projects compiling that would fail otherwise due to + // searching more arguments to instantiate implicits (PR #23532). A failing project + // is described in issue #23609. + def tryConstrainResult(pt: Type): Boolean = + try constrainResult(tree.symbol, wtp, pt) + catch case ex: TyperState.BadTyperStateAssertion => false + arg.tpe match case failed: SearchFailureType if canProfitFromMoreConstraints => val pt1 = pt.deepenProtoTrans - if (pt1 `ne` pt) && (pt1 ne sharpenedPt) && constrainResult(tree.symbol, wtp, pt1) - then return implicitArgs(formals, argIndex, pt1) + if (pt1 `ne` pt) && (pt1 ne sharpenedPt) && tryConstrainResult(pt1) then + return implicitArgs(formals, argIndex, pt1) case _ => arg.tpe match From cd442cae66eaf762ac72af83ec83a81014badc2b Mon Sep 17 00:00:00 2001 From: katrinafyi <39479354+katrinafyi@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:07:08 +1000 Subject: [PATCH 067/111] scaladoc: indicate optional parameters with `= ...` (#23676) currently, there is no indication in the scaladoc when parameters may be optional. this leads to [long and overwhelming signatures][1] which is not at all user-friendly. users are forced to first intuit that this method *might* have some optional parameters, then manually find [the source code][2] to learn which parameters are optional. [1]: https://javadoc.io/static/com.lihaoyi/os-lib_3/0.11.5/os/proc.html#call-fffff910 [2]: https://github.com/com-lihaoyi/os-lib/blob/0.11.5/os/src/ProcessOps.scala#L192-L205 this PR suffixes `= ...` after the type signature of optional parameters. this makes it possible to tell that these parameters are optional and may be omitted. this applies to both method parameters and class parameters. the format is intentionally similar to the definition of such optional parameters in code: ```scala // new scaladoc display: def f(x: Int, s: String = ...): Nothing // code: def f(x: Int, s: String = "a"): Nothing ``` of course, the `...` term is different. i think this is a reasonable choice because (1) ellipsis commonly represents something present but omitted, and (2) it is not valid Scala, so there is no risk someone will think this is denoting a literal default value of `...`. a proper ellipsis character (rather than 3 periods) could also be considered, but i found that looked out of place amongst the monospace signature. about displaying the default value itself, this PR does not display the default value. this is because of anticipated difficulties around displaying an expression. this could be re-visited in future, but i think it should not hold up this PR. i believe that this PR alone is already a substantial improvement for the documentation of optional parameters. finally, here is a screenshot of the scaladoc from the new optionalParams.scala test case: image this might be related to https://github.com/scala/scala3/issues/13424 [Cherry-picked d2404688091a6a40b80e83df72072efa4fea8f22] --- .../src/tests/extendsCall.scala | 2 +- .../src/tests/optionalParams.scala | 23 +++++++++++++++++++ .../scaladoc/tasty/ClassLikeSupport.scala | 7 +++--- .../TranslatableSignaturesTestCases.scala | 2 ++ 4 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 scaladoc-testcases/src/tests/optionalParams.scala diff --git a/scaladoc-testcases/src/tests/extendsCall.scala b/scaladoc-testcases/src/tests/extendsCall.scala index b90af8162e15..3ccd70de4216 100644 --- a/scaladoc-testcases/src/tests/extendsCall.scala +++ b/scaladoc-testcases/src/tests/extendsCall.scala @@ -3,4 +3,4 @@ package extendsCall class Impl() extends Base(Seq.empty, c = "-") //expected: class Impl() extends Base -class Base(val a: Seq[String], val b: String = "", val c: String = "") //expected: class Base(val a: Seq[String], val b: String, val c: String) +class Base(val a: Seq[String], val b: String = "", val c: String = "") //expected: class Base(val a: Seq[String], val b: String = ..., val c: String = ...) diff --git a/scaladoc-testcases/src/tests/optionalParams.scala b/scaladoc-testcases/src/tests/optionalParams.scala new file mode 100644 index 000000000000..551e14f7f811 --- /dev/null +++ b/scaladoc-testcases/src/tests/optionalParams.scala @@ -0,0 +1,23 @@ +package tests +package optionalParams + +class C(val a: Seq[String], val b: String = "", var c: String = "") //expected: class C(val a: Seq[String], val b: String = ..., var c: String = ...) +{ + def m(x: Int, s: String = "a"): Nothing //expected: def m(x: Int, s: String = ...): Nothing + = ??? +} + +def f(x: Int, s: String = "a"): Nothing //expected: def f(x: Int, s: String = ...): Nothing + = ??? + +extension (y: Int) + def ext(x: Int = 0): Int //expected: def ext(x: Int = ...): Int + = 0 + +def byname(s: => String = "a"): Int //expected: def byname(s: => String = ...): Int + = 0 + +enum E(val x: Int = 0) //expected: enum E(val x: Int = ...) +{ + case E1(y: Int = 10) extends E(y) //expected: final case class E1(y: Int = ...) extends E +} diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 690a19c6e78b..1a64625d491c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -454,14 +454,15 @@ trait ClassLikeSupport: val inlinePrefix = if symbol.flags.is(Flags.Inline) then "inline " else "" val name = symbol.normalizedName val nameIfNotSynthetic = Option.when(!symbol.flags.is(Flags.Synthetic))(name) + val defaultValue = Option.when(symbol.flags.is(Flags.HasDefault))(Plain(" = ...")) api.TermParameter( symbol.getAnnotations(), inlinePrefix + prefix(symbol), nameIfNotSynthetic, symbol.dri, - argument.tpt.asSignature(classDef, symbol.owner), - isExtendedSymbol, - isGrouped + argument.tpt.asSignature(classDef, symbol.owner) :++ defaultValue, + isExtendedSymbol = isExtendedSymbol, + isGrouped = isGrouped ) def mkTypeArgument( diff --git a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala index 34e9bc128402..98656d3b5c4a 100644 --- a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala @@ -130,3 +130,5 @@ class RightAssocExtension extends SignatureTest("rightAssocExtension", Signature class NamedTuples extends SignatureTest("namedTuples", SignatureTest.all) class InnerClasses extends SignatureTest("innerClasses", SignatureTest.all) + +class OptionalParams extends SignatureTest("optionalParams", SignatureTest.all) From 0544b4bdbe09bc941eba969f60c24114377fcccb Mon Sep 17 00:00:00 2001 From: som-snytt Date: Tue, 12 Aug 2025 02:19:34 -0700 Subject: [PATCH 068/111] Unused var message mentions unread or unset (#23719) Fixes #23704 Further distinguish whether a variable was "mutated but not read". [Cherry-picked 212ab3fed6f5bd599eb938fa5a8eee8e5b685856] --- .../dotty/tools/dotc/reporting/messages.scala | 2 + .../tools/dotc/transform/CheckUnused.scala | 20 +++++++--- tests/warn/i23704.check | 20 ++++++++++ tests/warn/i23704.scala | 39 +++++++++++++++++++ 4 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 tests/warn/i23704.check create mode 100644 tests/warn/i23704.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 41189608ce1b..86d1ce65c032 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3444,11 +3444,13 @@ extends Message(UnusedSymbolID): object UnusedSymbol: def imports(actions: List[CodeAction])(using Context): UnusedSymbol = UnusedSymbol(i"unused import", actions) def localDefs(using Context): UnusedSymbol = UnusedSymbol(i"unused local definition") + def localVars(using Context): UnusedSymbol = UnusedSymbol(i"local variable was mutated but not read") def explicitParams(sym: Symbol)(using Context): UnusedSymbol = UnusedSymbol(i"unused explicit parameter${paramAddendum(sym)}") def implicitParams(sym: Symbol)(using Context): UnusedSymbol = UnusedSymbol(i"unused implicit parameter${paramAddendum(sym)}") def privateMembers(using Context): UnusedSymbol = UnusedSymbol(i"unused private member") + def privateVars(using Context): UnusedSymbol = UnusedSymbol(i"private variable was mutated but not read") def patVars(using Context): UnusedSymbol = UnusedSymbol(i"unused pattern variable") def unsetLocals(using Context): UnusedSymbol = UnusedSymbol(i"unset local variable, consider using an immutable val instead") diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index a37dbce5bc2e..35310dd91ccc 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -498,8 +498,11 @@ object CheckUnused: val warnings = ArrayBuilder.make[MessageInfo] def warnAt(pos: SrcPos)(msg: UnusedSymbol, origin: String = ""): Unit = warnings.addOne((msg, pos, origin)) val infos = refInfos - //println(infos.defs.mkString("DEFS\n", "\n", "\n---")) - //println(infos.refs.mkString("REFS\n", "\n", "\n---")) + + // non-local sym was target of assignment or has a sibling setter that was referenced + def isMutated(sym: Symbol): Boolean = + infos.asss(sym) + || infos.refs(sym.owner.info.member(sym.name.asTermName.setterName).symbol) def checkUnassigned(sym: Symbol, pos: SrcPos) = if sym.isLocalToBlock then @@ -509,8 +512,7 @@ object CheckUnused: && sym.is(Mutable) && (sym.is(Private) || sym.isEffectivelyPrivate) && !sym.isSetter // tracks sym.underlyingSymbol sibling getter, check setter below - && !infos.asss(sym) - && !infos.refs(sym.owner.info.member(sym.name.asTermName.setterName).symbol) + && !isMutated(sym) then warnAt(pos)(UnusedSymbol.unsetPrivates) @@ -526,7 +528,10 @@ object CheckUnused: ) && !infos.nowarn(sym) then - warnAt(pos)(UnusedSymbol.privateMembers) + if sym.is(Mutable) && isMutated(sym) then + warnAt(pos)(UnusedSymbol.privateVars) + else + warnAt(pos)(UnusedSymbol.privateMembers) def checkParam(sym: Symbol, pos: SrcPos) = val m = sym.owner @@ -629,7 +634,10 @@ object CheckUnused: && !sym.is(InlineProxy) && !sym.isCanEqual then - warnAt(pos)(UnusedSymbol.localDefs) + if sym.is(Mutable) && infos.asss(sym) then + warnAt(pos)(UnusedSymbol.localVars) + else + warnAt(pos)(UnusedSymbol.localDefs) def checkPatvars() = // convert the one non-synthetic span so all are comparable; filter NoSpan below diff --git a/tests/warn/i23704.check b/tests/warn/i23704.check new file mode 100644 index 000000000000..71a69aefef11 --- /dev/null +++ b/tests/warn/i23704.check @@ -0,0 +1,20 @@ +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:9:8 ----------------------------------------------------------- +9 | var position: Int = 0 // warn position is assigned but not read + | ^^^^^^^^ + | private variable was mutated but not read +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:16:14 --------------------------------------------------------- +16 | private var myvar: Int = 0 // warn for the same case with simpler syntax + | ^^^^^ + | private variable was mutated but not read +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:26:8 ---------------------------------------------------------- +26 | var localvar = 0 // warn local variable was mutated but not read + | ^^^^^^^^ + | local variable was mutated but not read +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:33:8 ---------------------------------------------------------- +33 | var settable: Int = 0 // warn private variable was mutated but not read + | ^^^^^^^^ + | private variable was mutated but not read +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:39:14 --------------------------------------------------------- +39 | private var myvar: Int = 0 // warn plain unused + | ^^^^^ + | unused private member diff --git a/tests/warn/i23704.scala b/tests/warn/i23704.scala new file mode 100644 index 000000000000..9cfaae3278c1 --- /dev/null +++ b/tests/warn/i23704.scala @@ -0,0 +1,39 @@ +//> using options -Wunused:all + +trait Test { + def incr(): Unit +} + +object Test { + val test = new Test { + var position: Int = 0 // warn position is assigned but not read + + def incr(): Unit = { position += 1 } + } +} + +class C: + private var myvar: Int = 0 // warn for the same case with simpler syntax + def mine: Int = + myvar = 42 + 27 + +class D: + private var myvar: Int = 0 // nowarn (although read is RHS of assignment) + def incr(): Unit = myvar = myvar + 1 + + def local(): Unit = + var localvar = 0 // warn local variable was mutated but not read + localvar += 1 + +class E: + trait Setting: + def setting(): Unit + val test = new Test: + var settable: Int = 0 // warn private variable was mutated but not read + def setting(): Unit = + settable_=(42) + def incr() = setting() + +class F: + private var myvar: Int = 0 // warn plain unused From f162878f1a5d5af3955b05eb871147c0b19c456f Mon Sep 17 00:00:00 2001 From: Jan Chyb <48855024+jchyb@users.noreply.github.com> Date: Fri, 15 Aug 2025 10:58:49 +0200 Subject: [PATCH 069/111] Make coverage more similar to the one in Scala 2 (#23722) Closes #21877 * removes coverage of inlined nodes (as mentioned in the accompanying comment, those are impossible to represent in most cases) * adds coverage for Literals (ones directly in Apply are omitted) * removes coverage of `throw` contents * if apply node is tagged, we do not tag it's prefix, outside of other prefixing Apply's arguments (eg. when we tag `a+b+c` we do not redundantly tag `a+b`) * allows instrumenting synthetic method calls (like apply of a case After all of these changes the statements tagged are much more similar to Scala 2, let's look at the #21877 minimisation: * Scala 2: Zrzut ekranu 2025-08-12 o 17 07 31 Zrzut ekranu 2025-08-12 o 17 07 46 * Scala 3: Zrzut ekranu 2025-08-12 o 17 08 48 Zrzut ekranu 2025-08-12 o 17 08 55 There are some differences still remaining, most notably the tagging the DefDefs and its default parameters, but I left them for now, as those seem more useful than harmful. BEcouse of those changed most of the .covergae files had to be regenerated, however I want through each and every diff to make sure that all of those changes there are expected. Additionally, this PR also fixes #21695 (issue with certain generated Block nodes not having assigned the correct type, causing later undefined errors). [Cherry-picked c535dbc1ff81043d098bdf4c9b8f5a7db96f7f03] --- .../dotc/transform/InstrumentCoverage.scala | 74 ++- .../coverage/pos/Constructor.scoverage.check | 80 ++- .../pos/ContextFunctions.scoverage.check | 43 +- tests/coverage/pos/Enum.scoverage.check | 510 +++++++++++++++++- .../coverage/pos/ExcludeClass.scoverage.check | 38 +- tests/coverage/pos/For.scoverage.check | 118 +--- tests/coverage/pos/Givens.scoverage.check | 105 +++- tests/coverage/pos/Inlined.scala | 1 + tests/coverage/pos/Inlined.scoverage.check | 216 +------- tests/coverage/pos/InlinedFromLib.scala | 1 + .../pos/InlinedFromLib.scoverage.check | 216 +------- tests/coverage/pos/Lift.scoverage.check | 63 ++- tests/coverage/pos/Literals.scoverage.check | 59 +- .../coverage/pos/MatchNumbers.scoverage.check | 61 ++- .../pos/PolymorphicExtensions.scoverage.check | 60 +-- .../pos/PolymorphicMethods.scoverage.check | 20 +- tests/coverage/pos/Select.scoverage.check | 19 +- .../pos/SimpleMethods.scoverage.check | 249 ++++++++- .../pos/StructuralTypes.scoverage.check | 44 +- .../coverage/pos/TypeLambdas.scoverage.check | 19 +- tests/coverage/pos/i21695/A.scala | 9 + tests/coverage/pos/i21695/Builder.java | 6 + tests/coverage/pos/i21695/Service.java | 3 + .../coverage/pos/i21695/test.scoverage.check | 71 +++ tests/coverage/pos/i21877.scala | 21 + tests/coverage/pos/i21877.scoverage.check | 224 ++++++++ .../macro-late-suspend/test.scoverage.check | 17 - .../scoverage-samples-case.scoverage.check | 50 +- ...age-samples-implicit-class.scoverage.check | 35 +- .../run/currying/test.scoverage.check | 143 ++--- .../extend-case-class/test.scoverage.check | 89 ++- .../coverage/run/i16940/test.scoverage.check | 119 ++-- .../run/i18233-min/test.scoverage.check | 76 +-- .../run/inheritance/test.scoverage.check | 44 +- .../run/inline-def/test.scoverage.check | 40 +- .../run/interpolation/test.scoverage.check | 106 +++- .../run/java-methods/test.scoverage.check | 27 +- .../run/lifting-bool/test.scoverage.check | 298 +++++++++- .../coverage/run/lifting/test.scoverage.check | 209 ++++--- .../run/macro-suspend/test.scoverage.check | 70 +-- .../run/parameterless/test.scoverage.check | 117 ++-- tests/coverage/run/trait/test.scoverage.check | 52 +- .../run/type-apply/test.measurement.check | 4 +- .../run/type-apply/test.scoverage.check | 46 +- .../coverage/run/varargs/test.scoverage.check | 149 ++++- 45 files changed, 2819 insertions(+), 1202 deletions(-) create mode 100644 tests/coverage/pos/i21695/A.scala create mode 100644 tests/coverage/pos/i21695/Builder.java create mode 100644 tests/coverage/pos/i21695/Service.java create mode 100644 tests/coverage/pos/i21695/test.scoverage.check create mode 100644 tests/coverage/pos/i21877.scala create mode 100644 tests/coverage/pos/i21877.scoverage.check diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index 491f3d3d2572..449402f17fce 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -145,6 +145,31 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: val span = pos.span.toSynthetic invokeCall(statementId, span) + private def transformApplyArgs(trees: List[Tree])(using Context): List[Tree] = + if allConstArgs(trees) then trees else transform(trees) + + private def transformInnerApply(tree: Tree)(using Context): Tree = tree match + case a: Apply if a.fun.symbol == defn.StringContextModule_apply => + a + case a: Apply => + cpy.Apply(a)( + transformInnerApply(a.fun), + transformApplyArgs(a.args) + ) + case a: TypeApply => + cpy.TypeApply(a)( + transformInnerApply(a.fun), + transformApplyArgs(a.args) + ) + case s: Select => + cpy.Select(s)(transformInnerApply(s.qualifier), s.name) + case i: (Ident | This) => i + case t: Typed => + cpy.Typed(t)(transformInnerApply(t.expr), t.tpt) + case other => transform(other) + + private def allConstArgs(args: List[Tree]) = + args.forall(arg => arg.isInstanceOf[Literal] || arg.isInstanceOf[Ident]) /** * Tries to instrument an `Apply`. * These "tryInstrument" methods are useful to tweak the generation of coverage instrumentation, @@ -158,10 +183,12 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: // Create a call to Invoker.invoked(coverageDirectory, newStatementId) val coverageCall = createInvokeCall(tree, tree.sourcePos) - if needsLift(tree) then - // Transform args and fun, i.e. instrument them if needed (and if possible) - val app = cpy.Apply(tree)(transform(tree.fun), tree.args.map(transform)) + // Transform args and fun, i.e. instrument them if needed (and if possible) + val app = + if tree.fun.symbol eq defn.throwMethod then tree + else cpy.Apply(tree)(transformInnerApply(tree.fun), transformApplyArgs(tree.args)) + if needsLift(tree) then // Lifts the arguments. Note that if only one argument needs to be lifted, we lift them all. // Also, tree.fun can be lifted too. // See LiftCoverage for the internal working of this lifting. @@ -171,11 +198,10 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: InstrumentedParts(liftedDefs.toList, coverageCall, liftedApp) else // Instrument without lifting - val transformed = cpy.Apply(tree)(transform(tree.fun), transform(tree.args)) - InstrumentedParts.singleExpr(coverageCall, transformed) + InstrumentedParts.singleExpr(coverageCall, app) else // Transform recursively but don't instrument the tree itself - val transformed = cpy.Apply(tree)(transform(tree.fun), transform(tree.args)) + val transformed = cpy.Apply(tree)(transformInnerApply(tree.fun), transform(tree.args)) InstrumentedParts.notCovered(transformed) private def tryInstrument(tree: Ident)(using Context): InstrumentedParts = @@ -187,9 +213,14 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: else InstrumentedParts.notCovered(tree) + private def tryInstrument(tree: Literal)(using Context): InstrumentedParts = + val coverageCall = createInvokeCall(tree, tree.sourcePos) + InstrumentedParts.singleExpr(coverageCall, tree) + private def tryInstrument(tree: Select)(using Context): InstrumentedParts = val sym = tree.symbol - val transformed = cpy.Select(tree)(transform(tree.qualifier), tree.name) + val qual = transform(tree.qualifier).ensureConforms(tree.qualifier.tpe) + val transformed = cpy.Select(tree)(qual, tree.name) if canInstrumentParameterless(sym) then // call to a parameterless method val coverageCall = createInvokeCall(tree, tree.sourcePos) @@ -202,6 +233,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: tree match case t: Apply => tryInstrument(t) case t: Ident => tryInstrument(t) + case t: Literal => tryInstrument(t) case t: Select => tryInstrument(t) case _ => InstrumentedParts.notCovered(transform(tree)) @@ -223,10 +255,14 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: inContext(transformCtx(tree)) { // necessary to position inlined code properly tree match // simple cases - case tree: (Import | Export | Literal | This | Super | New) => tree + case tree: (Import | Export | This | Super | New) => tree case tree if tree.isEmpty || tree.isType => tree // empty Thicket, Ident (referring to a type), TypeTree, ... case tree if !tree.span.exists || tree.span.isZeroExtent => tree // no meaningful position + case tree: Literal => + val rest = tryInstrument(tree).toTree + rest + // identifier case tree: Ident => tryInstrument(tree).toTree @@ -280,6 +316,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: case tree: CaseDef => transformCaseDef(tree) + case tree: ValDef if tree.symbol.is(Inline) => + tree // transforming inline vals will result in `inline value must be pure` errors + case tree: ValDef => // only transform the rhs val rhs = transform(tree.rhs) @@ -323,13 +362,13 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: ) case tree: Inlined => - // Ideally, tree.call would provide precise information about the inlined call, - // and we would use this information for the coverage report. - // But PostTyper simplifies tree.call, so we can't report the actual method that was inlined. - // In any case, the subtrees need to be repositioned right now, otherwise the - // coverage statement will point to a potentially unreachable source file. - val dropped = Inlines.dropInlined(tree) // drop and reposition - transform(dropped) // transform the content of the Inlined + // Inlined code contents might come from another file (or project), + // which means that we cannot clearly designate which part of the inlined code + // was run using the API we are given. + // At best, we can show that the Inlined tree itself was reached. + // Additionally, Scala 2's coverage ignores macro calls entirely, + // so let's do that here too, also for regular inlined calls. + tree // For everything else just recurse and transform case _ => @@ -559,15 +598,14 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: private def isCompilerIntrinsicMethod(sym: Symbol)(using Context): Boolean = val owner = sym.maybeOwner owner.exists && ( - owner.eq(defn.AnyClass) || - owner.isPrimitiveValueClass || + (owner.eq(defn.AnyClass) && (sym == defn.Any_asInstanceOf || sym == defn.Any_isInstanceOf)) || owner.maybeOwner == defn.CompiletimePackageClass ) object InstrumentCoverage: val name: String = "instrumentCoverage" val description: String = "instrument code for coverage checking" - val ExcludeMethodFlags: FlagSet = Synthetic | Artifact | Erased + val ExcludeMethodFlags: FlagSet = Artifact | Erased /** * An instrumented Tree, in 3 parts. diff --git a/tests/coverage/pos/Constructor.scoverage.check b/tests/coverage/pos/Constructor.scoverage.check index a95bb21488b8..ed9ac68752bb 100644 --- a/tests/coverage/pos/Constructor.scoverage.check +++ b/tests/coverage/pos/Constructor.scoverage.check @@ -110,6 +110,23 @@ C Class covtest.C x +161 +162 +13 + +Literal +false +0 +false +1 + +6 +Constructor.scala +covtest +C +Class +covtest.C +x 153 158 13 @@ -120,7 +137,7 @@ false false def x -6 +7 Constructor.scala covtest C @@ -137,7 +154,7 @@ false false f(x) -7 +8 Constructor.scala covtest C @@ -154,7 +171,24 @@ false false x -8 +9 +Constructor.scala +covtest +C +Class +covtest.C +g +188 +189 +16 + +Literal +false +0 +false +2 + +10 Constructor.scala covtest C @@ -171,7 +205,7 @@ false false def g -9 +11 Constructor.scala covtest O @@ -188,7 +222,24 @@ false false def g -10 +12 +Constructor.scala +covtest +O +Object +covtest.O +y +231 +232 +20 + +Literal +false +0 +false +1 + +13 Constructor.scala covtest O @@ -205,7 +256,7 @@ false false def y -11 +14 Constructor.scala covtest O @@ -222,20 +273,3 @@ false false g(y) -12 -Constructor.scala -covtest -O -Object -covtest.O - -237 -238 -21 -y -Ident -false -0 -false -y - diff --git a/tests/coverage/pos/ContextFunctions.scoverage.check b/tests/coverage/pos/ContextFunctions.scoverage.check index 0d616d811ffe..ee5c40b9b1ac 100644 --- a/tests/coverage/pos/ContextFunctions.scoverage.check +++ b/tests/coverage/pos/ContextFunctions.scoverage.check @@ -41,6 +41,23 @@ covtest Imperative Class covtest.Imperative +$anonfun +178 +184 +9 + +Literal +false +0 +false +"name" + +2 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative readName2 121 134 @@ -52,24 +69,24 @@ false false def readName2 -2 +3 ContextFunctions.scala covtest Imperative Class covtest.Imperative readPerson -252 -309 -14 -onError -Apply +243 +247 +13 + +Literal false 0 false -OnError((e) => readName2(using e)(using s)).onError(None) +null -3 +4 ContextFunctions.scala covtest Imperative @@ -77,16 +94,16 @@ Class covtest.Imperative readPerson 252 -295 +309 14 - +onError Apply false 0 false -OnError((e) => readName2(using e)(using s)) +OnError((e) => readName2(using e)(using s)).onError(None) -4 +5 ContextFunctions.scala covtest Imperative @@ -103,7 +120,7 @@ false false readName2(using e)(using s) -5 +6 ContextFunctions.scala covtest Imperative diff --git a/tests/coverage/pos/Enum.scoverage.check b/tests/coverage/pos/Enum.scoverage.check index 6806934e0dec..4ad02971f6cf 100644 --- a/tests/coverage/pos/Enum.scoverage.check +++ b/tests/coverage/pos/Enum.scoverage.check @@ -24,6 +24,57 @@ covtest Planet Class covtest.Planet + +322 +333 +14 + +Literal +false +0 +false +6.67300E-11 + +1 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +386 +15 +/ +Apply +false +0 +false +G * mass / (radius * radius + +2 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +371 +386 +15 +* +Apply +false +0 +false +radius * radius + +3 +Enum.scala +covtest +Planet +Class +covtest.Planet surfaceGravity 338 356 @@ -35,7 +86,24 @@ false false def surfaceGravity -1 +4 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceWeight +432 +458 +16 +* +Apply +false +0 +false +otherMass * surfaceGravity + +5 Enum.scala covtest Planet @@ -52,7 +120,7 @@ false false surfaceGravity -2 +6 Enum.scala covtest Planet @@ -69,7 +137,381 @@ false false def surfaceWeight +7 +Enum.scala +covtest +$anon +Class +covtest.$anon + +492 +501 +18 + +Literal +false +0 +false +3.303e+23 + +8 +Enum.scala +covtest +$anon +Class +covtest.$anon + +503 +511 +18 + +Literal +false +0 +false +2.4397e6 + +9 +Enum.scala +covtest +$anon +Class +covtest.$anon + +545 +554 +19 + +Literal +false +0 +false +4.869e+24 + +10 +Enum.scala +covtest +$anon +Class +covtest.$anon + +556 +564 +19 + +Literal +false +0 +false +6.0518e6 + +11 +Enum.scala +covtest +$anon +Class +covtest.$anon + +598 +607 +20 + +Literal +false +0 +false +5.976e+24 + +12 +Enum.scala +covtest +$anon +Class +covtest.$anon + +609 +618 +20 + +Literal +false +0 +false +6.37814e6 + +13 +Enum.scala +covtest +$anon +Class +covtest.$anon + +652 +661 +21 + +Literal +false +0 +false +6.421e+23 + +14 +Enum.scala +covtest +$anon +Class +covtest.$anon + +663 +671 +21 + +Literal +false +0 +false +3.3972e6 + +15 +Enum.scala +covtest +$anon +Class +covtest.$anon + +705 +712 +22 + +Literal +false +0 +false +1.9e+27 + +16 +Enum.scala +covtest +$anon +Class +covtest.$anon + +716 +724 +22 + +Literal +false +0 +false +7.1492e7 + +17 +Enum.scala +covtest +$anon +Class +covtest.$anon + +758 +767 +23 + +Literal +false +0 +false +5.688e+26 + +18 +Enum.scala +covtest +$anon +Class +covtest.$anon + +769 +777 +23 + +Literal +false +0 +false +6.0268e7 + +19 +Enum.scala +covtest +$anon +Class +covtest.$anon + +811 +820 +24 + +Literal +false +0 +false +8.686e+25 + +20 +Enum.scala +covtest +$anon +Class +covtest.$anon + +822 +830 +24 + +Literal +false +0 +false +2.5559e7 + +21 +Enum.scala +covtest +$anon +Class +covtest.$anon + +864 +873 +25 + +Literal +false +0 +false +1.024e+26 + +22 +Enum.scala +covtest +$anon +Class +covtest.$anon + +875 +883 +25 + +Literal +false +0 +false +2.4746e7 + +23 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +970 +1038 +30 +apply +Apply +false +0 +false +ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) + +24 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +984 +985 +30 + +Literal +false +0 +false +1 + +25 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +987 +1037 +30 +apply +Apply +false +0 +false +ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty)) + +26 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +1001 +1002 +30 + +Literal +false +0 +false +2 + +27 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +1004 +1036 +30 +apply +Apply +false +0 +false +ListEnum.Cons(3, ListEnum.Empty) + +28 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +1018 +1019 +30 + +Literal +false +0 +false 3 + +29 Enum.scala covtest EnumTypes @@ -86,7 +528,7 @@ false false println("Example 1: \\n"+emptyList) -4 +30 Enum.scala covtest EnumTypes @@ -103,7 +545,24 @@ false false "Example 1: \\n"+emptyList -5 +31 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +1051 +1066 +31 + +Literal +false +0 +false +"Example 1: \\n" + +32 Enum.scala covtest EnumTypes @@ -120,7 +579,7 @@ false false println(s"${list}\\n") -6 +33 Enum.scala covtest EnumTypes @@ -137,7 +596,24 @@ false false s"${list}\\n" -7 +34 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +calculateEarthWeightOnPlanets +1183 +1222 +35 +/ +Apply +false +0 +false +earthWeight/Planet.Earth.surfaceGravity + +35 Enum.scala covtest EnumTypes @@ -154,7 +630,7 @@ false false Planet.Earth.surfaceGravity -8 +36 Enum.scala covtest EnumTypes @@ -171,7 +647,7 @@ false false for p <- Planet.values do\n println(s"Your weight on $p is ${p.surfaceWeight(mass)}") -9 +37 Enum.scala covtest EnumTypes @@ -181,14 +657,14 @@ calculateEarthWeightOnPlanets 1238 1251 36 -refArrayOps -Apply +values +Select false 0 false Planet.values -10 +38 Enum.scala covtest EnumTypes @@ -205,7 +681,7 @@ false false println(s"Your weight on $p is ${p.surfaceWeight(mass)}") -11 +39 Enum.scala covtest EnumTypes @@ -222,7 +698,7 @@ false false s"Your weight on $p is ${p.surfaceWeight(mass)}" -12 +40 Enum.scala covtest EnumTypes @@ -239,7 +715,7 @@ false false p.surfaceWeight(mass) -13 +41 Enum.scala covtest EnumTypes @@ -256,7 +732,7 @@ false false def calculateEarthWeightOnPlanets -14 +42 Enum.scala covtest EnumTypes @@ -273,7 +749,7 @@ false false println("Example 2:") -15 +43 Enum.scala covtest EnumTypes @@ -290,7 +766,7 @@ false false calculateEarthWeightOnPlanets(80) -16 +44 Enum.scala covtest EnumTypes diff --git a/tests/coverage/pos/ExcludeClass.scoverage.check b/tests/coverage/pos/ExcludeClass.scoverage.check index 5e77f0ce21a1..370410b68091 100644 --- a/tests/coverage/pos/ExcludeClass.scoverage.check +++ b/tests/coverage/pos/ExcludeClass.scoverage.check @@ -25,6 +25,23 @@ Klass2 Class covtest.Klass2 abs +202 +207 +15 +> +Apply +false +0 +false +i > 0 + +1 +ExcludeClass.scala +covtest +Klass2 +Class +covtest.Klass2 +abs 219 220 16 @@ -35,7 +52,24 @@ true false i -1 +2 +ExcludeClass.scala +covtest +Klass2 +Class +covtest.Klass2 +abs +236 +238 +18 +unary_- +Select +false +0 +false +-i + +3 ExcludeClass.scala covtest Klass2 @@ -52,7 +86,7 @@ true false -i -2 +4 ExcludeClass.scala covtest Klass2 diff --git a/tests/coverage/pos/For.scoverage.check b/tests/coverage/pos/For.scoverage.check index 6eeab746b4c5..9942da22d02e 100644 --- a/tests/coverage/pos/For.scoverage.check +++ b/tests/coverage/pos/For.scoverage.check @@ -41,40 +41,6 @@ covtest For$package Object covtest.For$package -testForLoop -52 -59 -4 -to -Apply -false -0 -false -1 to 10 - -2 -For.scala -covtest -For$package -Object -covtest.For$package -testForLoop -52 -53 -4 -intWrapper -Apply -false -0 -false -1 - -3 -For.scala -covtest -For$package -Object -covtest.For$package $anonfun 67 77 @@ -86,7 +52,7 @@ false false println(i) -4 +2 For.scala covtest For$package @@ -103,92 +69,58 @@ false false def testForLoop -5 +3 For.scala covtest For$package Object covtest.For$package f -109 -114 +134 +138 8 -f -DefDef -false -0 -false -def f - -6 -For.scala -covtest -For$package -Object -covtest.For$package -testForAdvanced -141 -183 -9 -foreach -Apply + +Literal false 0 false -for j <- 1 to 10 if f(j) do\n println(j) +true -7 +4 For.scala covtest For$package Object covtest.For$package -testForAdvanced -145 -165 -9 -withFilter -Apply -false -0 -false -j <- 1 to 10 if f(j) - +f +109 +114 8 -For.scala -covtest -For$package -Object -covtest.For$package -testForAdvanced -150 -157 -9 -to -Apply +f +DefDef false 0 false -1 to 10 +def f -9 +5 For.scala covtest For$package Object covtest.For$package testForAdvanced -150 -151 +141 +183 9 -intWrapper +foreach Apply false 0 false -1 +for j <- 1 to 10 if f(j) do\n println(j) -10 +6 For.scala covtest For$package @@ -205,7 +137,7 @@ false false f(j) -11 +7 For.scala covtest For$package @@ -222,7 +154,7 @@ false false println(j) -12 +8 For.scala covtest For$package @@ -239,7 +171,7 @@ false false def testForAdvanced -13 +9 For.scala covtest For$package @@ -256,7 +188,7 @@ false false Nil.foreach(_ => println("user code here")) -14 +10 For.scala covtest For$package @@ -273,7 +205,7 @@ false false println("user code here") -15 +11 For.scala covtest For$package diff --git a/tests/coverage/pos/Givens.scoverage.check b/tests/coverage/pos/Givens.scoverage.check index 4442f329c6b2..b09e369ee076 100644 --- a/tests/coverage/pos/Givens.scoverage.check +++ b/tests/coverage/pos/Givens.scoverage.check @@ -42,6 +42,40 @@ Givens Class covtest.Givens test +182 +190 +11 +== +Apply +false +0 +false +3 == "3" + +2 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +182 +183 +11 + +Literal +false +0 +false +3 + +3 +Givens.scala +covtest +Givens +Class +covtest.Givens +test 196 213 12 @@ -52,7 +86,41 @@ false false println(3 == 5.1) -2 +4 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +204 +212 +12 +== +Apply +false +0 +false +3 == 5.1 + +5 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +204 +205 +12 + +Literal +false +0 +false +3 + +6 Givens.scala covtest Givens @@ -69,7 +137,7 @@ false false def test -3 +7 Givens.scala covtest Givens @@ -86,7 +154,7 @@ false false println(msg) -4 +8 Givens.scala covtest Givens @@ -103,7 +171,7 @@ false false println(ctx.id) -5 +9 Givens.scala covtest Givens @@ -120,7 +188,24 @@ false false def printContext -6 +10 +Givens.scala +covtest +Givens +Class +covtest.Givens +getMessage +348 +358 +18 +toString +Apply +false +0 +false +i.toString + +11 Givens.scala covtest Givens @@ -137,7 +222,7 @@ false false def getMessage -7 +12 Givens.scala covtest Givens @@ -154,7 +239,7 @@ false false Context(0) -8 +13 Givens.scala covtest Givens @@ -171,7 +256,7 @@ false false printContext("test")(using c) -9 +14 Givens.scala covtest Givens @@ -188,7 +273,7 @@ false false printContext(getMessage(123)) -10 +15 Givens.scala covtest Givens @@ -205,7 +290,7 @@ false false getMessage(123) -11 +16 Givens.scala covtest Givens diff --git a/tests/coverage/pos/Inlined.scala b/tests/coverage/pos/Inlined.scala index 5e51f037220f..37519e9f63e3 100644 --- a/tests/coverage/pos/Inlined.scala +++ b/tests/coverage/pos/Inlined.scala @@ -1,6 +1,7 @@ package covtest // Checks that we use the new positions of the inlined code properly +// NOTE (12.08.2025): After recent changes, the inlined nodes will not be tagged in coverage def testInlined(): Unit = val l = 1 assert(l == 1) diff --git a/tests/coverage/pos/Inlined.scoverage.check b/tests/coverage/pos/Inlined.scoverage.check index c74868219b67..cf8d1bcdb0f7 100644 --- a/tests/coverage/pos/Inlined.scoverage.check +++ b/tests/coverage/pos/Inlined.scoverage.check @@ -25,230 +25,26 @@ Inlined$package Object covtest.Inlined$package testInlined -288 -330 -11 -assertFailed -Apply -false -0 -false -scala.runtime.Scala3RunTime.assertFailed() - -1 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -288 -330 -11 -assertFailed -Apply -true -0 -false -scala.runtime.Scala3RunTime.assertFailed() - -2 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -330 -330 -11 - -Literal -true -0 -false - - -3 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -155 -162 -7 -apply -Apply -false -0 -false -List(l) - -4 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -155 -169 -7 -length -Select -false -0 -false -List(l).length - -5 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -288 -330 -11 -assertFailed -Apply -false -0 -false -scala.runtime.Scala3RunTime.assertFailed() - +215 +216 6 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -288 -330 -11 -assertFailed -Apply -true -0 -false -scala.runtime.Scala3RunTime.assertFailed() - -7 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -330 -330 -11 Literal -true -0 -false - - -8 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -180 -187 -8 -apply -Apply false 0 false -List(l) +1 -9 +1 Inlined.scala covtest Inlined$package Object covtest.Inlined$package testInlined -180 +179 194 -8 -length -Select -false -0 -false -List(l).length - -10 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -288 -330 -11 -assertFailed -Apply -false -0 -false -scala.runtime.Scala3RunTime.assertFailed() - -11 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -288 -330 -11 -assertFailed -Apply -true -0 -false -scala.runtime.Scala3RunTime.assertFailed() - -12 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -330 -330 -11 - -Literal -true -0 -false - - -13 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -86 -101 -4 +5 testInlined DefDef false diff --git a/tests/coverage/pos/InlinedFromLib.scala b/tests/coverage/pos/InlinedFromLib.scala index 1b05e11b7558..7607ded865d7 100644 --- a/tests/coverage/pos/InlinedFromLib.scala +++ b/tests/coverage/pos/InlinedFromLib.scala @@ -2,6 +2,7 @@ package covtest // assert is a `transparent inline` in Predef, // but its source path should not appear in the coverage report. +// NOTE (12.08.2025): After recent changes, the inlined nodes will not be tagged in coverage def testInlined(): Unit = val l = 1 assert(l == 1) diff --git a/tests/coverage/pos/InlinedFromLib.scoverage.check b/tests/coverage/pos/InlinedFromLib.scoverage.check index 5aff5473f6d9..bd7bd1a65b6f 100644 --- a/tests/coverage/pos/InlinedFromLib.scoverage.check +++ b/tests/coverage/pos/InlinedFromLib.scoverage.check @@ -25,230 +25,26 @@ InlinedFromLib$package Object covtest.InlinedFromLib$package testInlined -169 -183 +258 +259 7 -assertFailed -Apply -false -0 -false -assert(l == 1) - -1 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -169 -183 -7 -assertFailed -Apply -true -0 -false -assert(l == 1) - -2 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -169 -183 -7 - -Literal -true -0 -false -assert(l == 1) - -3 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -198 -205 -8 -apply -Apply -false -0 -false -List(l) - -4 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -198 -212 -8 -length -Select -false -0 -false -List(l).length - -5 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -186 -213 -8 -assertFailed -Apply -false -0 -false -assert(l == List(l).length) - -6 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -186 -213 -8 -assertFailed -Apply -true -0 -false -assert(l == List(l).length) - -7 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -186 -213 -8 Literal -true -0 -false -assert(l == List(l).length) - -8 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -223 -230 -9 -apply -Apply false 0 false -List(l) +1 -9 +1 InlinedFromLib.scala covtest InlinedFromLib$package Object covtest.InlinedFromLib$package testInlined -223 +222 237 -9 -length -Select -false -0 -false -List(l).length - -10 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -216 -243 -9 -assertFailed -Apply -false -0 -false -assert(List(l).length == 1) - -11 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -216 -243 -9 -assertFailed -Apply -true -0 -false -assert(List(l).length == 1) - -12 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -216 -243 -9 - -Literal -true -0 -false -assert(List(l).length == 1) - -13 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -129 -144 -5 +6 testInlined DefDef false diff --git a/tests/coverage/pos/Lift.scoverage.check b/tests/coverage/pos/Lift.scoverage.check index cce8c18c6254..516692cd2503 100644 --- a/tests/coverage/pos/Lift.scoverage.check +++ b/tests/coverage/pos/Lift.scoverage.check @@ -25,6 +25,23 @@ SomeFunctions Class covtest.SomeFunctions f +56 +58 +4 + +Literal +false +0 +false +() + +1 +Lift.scala +covtest +SomeFunctions +Class +covtest.SomeFunctions +f 40 45 4 @@ -35,7 +52,24 @@ false false def f -1 +2 +Lift.scala +covtest +SomeFunctions +Class +covtest.SomeFunctions +g +71 +72 +5 + +Literal +false +0 +false +0 + +3 Lift.scala covtest SomeFunctions @@ -52,7 +86,7 @@ false false def g -2 +4 Lift.scala covtest SomeFunctions @@ -69,7 +103,7 @@ false false SomeFunctions() -3 +5 Lift.scala covtest SomeFunctions @@ -86,7 +120,7 @@ false false def c -4 +6 Lift.scala covtest SomeFunctions @@ -103,24 +137,7 @@ false false c.f(g()) -5 -Lift.scala -covtest -SomeFunctions -Class -covtest.SomeFunctions -test -113 -114 -8 -c -Select -false -0 -false -c - -6 +7 Lift.scala covtest SomeFunctions @@ -137,7 +154,7 @@ false false g() -7 +8 Lift.scala covtest SomeFunctions diff --git a/tests/coverage/pos/Literals.scoverage.check b/tests/coverage/pos/Literals.scoverage.check index cd58a841d5b6..c6ea348844ca 100644 --- a/tests/coverage/pos/Literals.scoverage.check +++ b/tests/coverage/pos/Literals.scoverage.check @@ -42,6 +42,57 @@ Literals$package Object covtest.Literals$package block +119 +121 +5 + +Literal +false +0 +false +12 + +2 +Literals.scala +covtest +Literals$package +Object +covtest.Literals$package +block +124 +128 +6 + +Literal +false +0 +false +true + +3 +Literals.scala +covtest +Literals$package +Object +covtest.Literals$package +block +131 +135 +7 + +Literal +false +0 +false +null + +4 +Literals.scala +covtest +Literals$package +Object +covtest.Literals$package +block 17 26 3 @@ -52,7 +103,7 @@ false false def block -2 +5 Literals.scala covtest Literals$package @@ -69,7 +120,7 @@ false false ??? -3 +6 Literals.scala covtest Literals$package @@ -86,7 +137,7 @@ false false def f -4 +7 Literals.scala covtest Literals$package @@ -103,7 +154,7 @@ false false f(0,1,2)(3) -5 +8 Literals.scala covtest Literals$package diff --git a/tests/coverage/pos/MatchNumbers.scoverage.check b/tests/coverage/pos/MatchNumbers.scoverage.check index 43e01018f0ac..74dd5db8fc87 100644 --- a/tests/coverage/pos/MatchNumbers.scoverage.check +++ b/tests/coverage/pos/MatchNumbers.scoverage.check @@ -25,6 +25,40 @@ MatchNumbers Object covtest.MatchNumbers f +121 +126 +7 +< +Apply +false +0 +false +x < 0 + +1 +MatchNumbers.scala +covtest +MatchNumbers +Object +covtest.MatchNumbers +f +130 +132 +7 + +Literal +false +0 +false +-1 + +2 +MatchNumbers.scala +covtest +MatchNumbers +Object +covtest.MatchNumbers +f 127 132 7 @@ -35,7 +69,7 @@ true false => -1 -1 +3 MatchNumbers.scala covtest MatchNumbers @@ -52,7 +86,24 @@ true false => x -2 +4 +MatchNumbers.scala +covtest +MatchNumbers +Object +covtest.MatchNumbers +f +174 +181 +9 +toInt +Select +false +0 +false +y.toInt + +5 MatchNumbers.scala covtest MatchNumbers @@ -69,7 +120,7 @@ true false => y.toInt -3 +6 MatchNumbers.scala covtest MatchNumbers @@ -86,7 +137,7 @@ false false def f -4 +7 MatchNumbers.scala covtest MatchNumbers @@ -103,7 +154,7 @@ false false f(0) -5 +8 MatchNumbers.scala covtest MatchNumbers diff --git a/tests/coverage/pos/PolymorphicExtensions.scoverage.check b/tests/coverage/pos/PolymorphicExtensions.scoverage.check index 64795070b34f..6eebb8cc94fa 100644 --- a/tests/coverage/pos/PolymorphicExtensions.scoverage.check +++ b/tests/coverage/pos/PolymorphicExtensions.scoverage.check @@ -110,15 +110,15 @@ PolyExt Object covtest.PolyExt -177 -186 -11 -foo +277 +287 +12 +get Apply false 0 false -"str".foo +123.get(0) 6 PolymorphicExtensions.scala @@ -126,16 +126,16 @@ covtest PolyExt Object covtest.PolyExt - -277 -287 -12 -get -Apply +foo +385 +387 +14 + +Literal false 0 false -123.get(0) +42 7 PolymorphicExtensions.scala @@ -177,40 +177,6 @@ covtest PolyExt Object covtest.PolyExt -bar -405 -412 -15 -tap -Apply -false -0 -false -foo.tap - -10 -PolymorphicExtensions.scala -covtest -PolyExt -Object -covtest.PolyExt -bar -405 -408 -15 -foo -Ident -false -0 -false -foo - -11 -PolymorphicExtensions.scala -covtest -PolyExt -Object -covtest.PolyExt $anonfun 413 420 @@ -222,7 +188,7 @@ false false println -12 +10 PolymorphicExtensions.scala covtest PolyExt diff --git a/tests/coverage/pos/PolymorphicMethods.scoverage.check b/tests/coverage/pos/PolymorphicMethods.scoverage.check index b66aaeb92661..01cc80f6a4dc 100644 --- a/tests/coverage/pos/PolymorphicMethods.scoverage.check +++ b/tests/coverage/pos/PolymorphicMethods.scoverage.check @@ -72,19 +72,19 @@ C[String]().f("str", 0) 3 PolymorphicMethods.scala covtest -PolyMeth -Object -covtest.PolyMeth - -147 -158 -7 - -Apply +C +Class +covtest.C +f +221 +223 +10 + +Literal false 0 false -C[String]() +() 4 PolymorphicMethods.scala diff --git a/tests/coverage/pos/Select.scoverage.check b/tests/coverage/pos/Select.scoverage.check index cfe719552e44..a4830c1a6d5c 100644 --- a/tests/coverage/pos/Select.scoverage.check +++ b/tests/coverage/pos/Select.scoverage.check @@ -195,23 +195,6 @@ Select$package Object covtest.Select$package test -263 -273 -19 -instance -Select -false -0 -false -a.instance - -11 -Select.scala -covtest -Select$package -Object -covtest.Select$package -test 345 354 20 @@ -222,7 +205,7 @@ false false a.print() -12 +11 Select.scala covtest Select$package diff --git a/tests/coverage/pos/SimpleMethods.scoverage.check b/tests/coverage/pos/SimpleMethods.scoverage.check index 067bd744177b..03d3c1a440ec 100644 --- a/tests/coverage/pos/SimpleMethods.scoverage.check +++ b/tests/coverage/pos/SimpleMethods.scoverage.check @@ -42,6 +42,23 @@ C Class covtest.C b +60 +66 +5 + +Literal +false +0 +false +return + +2 +SimpleMethods.scala +covtest +C +Class +covtest.C +b 46 51 5 @@ -52,7 +69,24 @@ false false def b -2 +3 +SimpleMethods.scala +covtest +C +Class +covtest.C +c +83 +85 +6 + +Literal +false +0 +false +() + +4 SimpleMethods.scala covtest C @@ -69,7 +103,24 @@ false false def c -3 +5 +SimpleMethods.scala +covtest +C +Class +covtest.C +d +101 +103 +7 + +Literal +false +0 +false +12 + +6 SimpleMethods.scala covtest C @@ -86,7 +137,24 @@ false false def d -4 +7 +SimpleMethods.scala +covtest +C +Class +covtest.C +e +120 +124 +8 + +Literal +false +0 +false +null + +8 SimpleMethods.scala covtest C @@ -103,7 +171,41 @@ false false def e -5 +9 +SimpleMethods.scala +covtest +C +Class +covtest.C +block +149 +158 +11 + +Literal +false +0 +false +"literal" + +10 +SimpleMethods.scala +covtest +C +Class +covtest.C +block +163 +164 +12 + +Literal +false +0 +false +0 + +11 SimpleMethods.scala covtest C @@ -120,7 +222,41 @@ false false def block -6 +12 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +195 +200 +15 + +Literal +false +0 +false +false + +13 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +206 +210 +15 + +Literal +false +0 +false +true + +14 SimpleMethods.scala covtest C @@ -137,7 +273,24 @@ true false true -7 +15 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +220 +225 +16 + +Literal +false +0 +false +false + +16 SimpleMethods.scala covtest C @@ -154,7 +307,7 @@ true false false -8 +17 SimpleMethods.scala covtest C @@ -171,7 +324,41 @@ false false def cond -9 +18 +SimpleMethods.scala +covtest +C +Class +covtest.C +partialCond +260 +265 +19 + +Literal +false +0 +false +false + +19 +SimpleMethods.scala +covtest +C +Class +covtest.C +partialCond +271 +273 +19 + +Literal +false +0 +false +() + +20 SimpleMethods.scala covtest C @@ -188,7 +375,7 @@ true false () -10 +21 SimpleMethods.scala covtest C @@ -205,7 +392,7 @@ true false -11 +22 SimpleMethods.scala covtest C @@ -222,7 +409,7 @@ false false def partialCond -12 +23 SimpleMethods.scala covtest C @@ -239,7 +426,24 @@ false false def new1 -13 +24 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +330 +332 +24 + +Literal +false +0 +false +() + +25 SimpleMethods.scala covtest C @@ -256,7 +460,24 @@ true false () -14 +26 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +370 +371 +26 + +Literal +false +0 +false +1 + +27 SimpleMethods.scala covtest C @@ -273,7 +494,7 @@ true false => 1 -15 +28 SimpleMethods.scala covtest C diff --git a/tests/coverage/pos/StructuralTypes.scoverage.check b/tests/coverage/pos/StructuralTypes.scoverage.check index a487ac29c9de..2f0390044f04 100644 --- a/tests/coverage/pos/StructuralTypes.scoverage.check +++ b/tests/coverage/pos/StructuralTypes.scoverage.check @@ -41,6 +41,23 @@ covtest Record Class covtest.Record +$anonfun +159 +171 +6 +== +Apply +false +0 +false +_._1 == name + +2 +StructuralTypes.scala +covtest +Record +Class +covtest.Record selectDynamic 148 176 @@ -52,7 +69,7 @@ false false elems.find(_._1 == name).get -2 +3 StructuralTypes.scala covtest Record @@ -69,7 +86,24 @@ false false def selectDynamic -3 +4 +StructuralTypes.scala +covtest +StructuralTypes +Object +covtest.StructuralTypes +test +270 +307 +13 +apply +Apply +false +0 +false +Record("name" -> "Emma", "age" -> 42) + +5 StructuralTypes.scala covtest StructuralTypes @@ -86,7 +120,7 @@ false false "name" -> "Emma" -4 +6 StructuralTypes.scala covtest StructuralTypes @@ -103,7 +137,7 @@ false false "age" -> 42 -5 +7 StructuralTypes.scala covtest StructuralTypes @@ -120,7 +154,7 @@ false false person.name -6 +8 StructuralTypes.scala covtest StructuralTypes diff --git a/tests/coverage/pos/TypeLambdas.scoverage.check b/tests/coverage/pos/TypeLambdas.scoverage.check index de519038c367..f5779a45268b 100644 --- a/tests/coverage/pos/TypeLambdas.scoverage.check +++ b/tests/coverage/pos/TypeLambdas.scoverage.check @@ -76,6 +76,23 @@ TypeLambdas Object covtest.TypeLambdas test +367 +377 +17 +apply +Apply +false +0 +false +("a", "b") + +4 +TypeLambdas.scala +covtest +TypeLambdas +Object +covtest.TypeLambdas +test 382 396 18 @@ -86,7 +103,7 @@ false false println(tuple) -4 +5 TypeLambdas.scala covtest TypeLambdas diff --git a/tests/coverage/pos/i21695/A.scala b/tests/coverage/pos/i21695/A.scala new file mode 100644 index 000000000000..afe534eac46e --- /dev/null +++ b/tests/coverage/pos/i21695/A.scala @@ -0,0 +1,9 @@ +package example + +trait A { + def x1: Builder[?] + def x2: Service + + def create: String = + x1.addService(x2).build() +} diff --git a/tests/coverage/pos/i21695/Builder.java b/tests/coverage/pos/i21695/Builder.java new file mode 100644 index 000000000000..c7685043ec00 --- /dev/null +++ b/tests/coverage/pos/i21695/Builder.java @@ -0,0 +1,6 @@ +package example; + +public abstract class Builder> { + public abstract String build(); + public abstract T addService(Service service); +} diff --git a/tests/coverage/pos/i21695/Service.java b/tests/coverage/pos/i21695/Service.java new file mode 100644 index 000000000000..624e3c04f67f --- /dev/null +++ b/tests/coverage/pos/i21695/Service.java @@ -0,0 +1,3 @@ +package example; + +public class Service{} diff --git a/tests/coverage/pos/i21695/test.scoverage.check b/tests/coverage/pos/i21695/test.scoverage.check new file mode 100644 index 000000000000..d9ee3a172e3f --- /dev/null +++ b/tests/coverage/pos/i21695/test.scoverage.check @@ -0,0 +1,71 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +i21695/A.scala +example +A +Trait +example.A +create +94 +119 +8 +build +Apply +false +0 +false +x1.addService(x2).build() + +1 +i21695/A.scala +example +A +Trait +example.A +create +108 +110 +8 +x2 +Select +false +0 +false +x2 + +2 +i21695/A.scala +example +A +Trait +example.A +create +69 +79 +7 +create +DefDef +false +0 +false +def create + diff --git a/tests/coverage/pos/i21877.scala b/tests/coverage/pos/i21877.scala new file mode 100644 index 000000000000..13d4c9dfc8e2 --- /dev/null +++ b/tests/coverage/pos/i21877.scala @@ -0,0 +1,21 @@ + + +class Schema[T] +object Schema { + inline def derived[T]: Schema[T] = new Schema[T] +} + +case class Bar(x: Int) + +object Foo { + def foo(x: Int): String = { + val bar = Bar(x) + if (x == 5) "5" else "idk" + bar.toString + } + + implicit val schema: Schema[Bar] = Schema.derived +} + +@main def test() = + Foo.foo(4) + Foo.foo(5) diff --git a/tests/coverage/pos/i21877.scoverage.check b/tests/coverage/pos/i21877.scoverage.check new file mode 100644 index 000000000000..7f95bcb7c010 --- /dev/null +++ b/tests/coverage/pos/i21877.scoverage.check @@ -0,0 +1,224 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +i21877.scala + +Foo +Object +.Foo +foo +169 +175 +12 +apply +Apply +false +0 +false +Bar(x) + +1 +i21877.scala + +Foo +Object +.Foo +foo +184 +190 +13 +== +Apply +false +0 +false +x == 5 + +2 +i21877.scala + +Foo +Object +.Foo +foo +192 +195 +13 + +Literal +false +0 +false +"5" + +3 +i21877.scala + +Foo +Object +.Foo +foo +192 +195 +13 + +Literal +true +0 +false +"5" + +4 +i21877.scala + +Foo +Object +.Foo +foo +201 +221 +13 ++ +Apply +false +0 +false +"idk" + bar.toString + +5 +i21877.scala + +Foo +Object +.Foo +foo +201 +206 +13 + +Literal +false +0 +false +"idk" + +6 +i21877.scala + +Foo +Object +.Foo +foo +209 +221 +13 +toString +Apply +false +0 +false +bar.toString + +7 +i21877.scala + +Foo +Object +.Foo +foo +201 +221 +13 ++ +Apply +true +0 +false +"idk" + bar.toString + +8 +i21877.scala + +Foo +Object +.Foo +foo +127 +134 +11 +foo +DefDef +false +0 +false +def foo + +9 +i21877.scala + +i21877$package +Object +.i21877$package +test +303 +313 +20 +foo +Apply +false +0 +false +Foo.foo(4) + +10 +i21877.scala + +i21877$package +Object +.i21877$package +test +316 +326 +21 +foo +Apply +false +0 +false +Foo.foo(5) + +11 +i21877.scala + +i21877$package +Object +.i21877$package +test +282 +296 +19 +test +DefDef +false +0 +false +@main def test + diff --git a/tests/coverage/pos/macro-late-suspend/test.scoverage.check b/tests/coverage/pos/macro-late-suspend/test.scoverage.check index f962705ed2ce..051f30e08e67 100644 --- a/tests/coverage/pos/macro-late-suspend/test.scoverage.check +++ b/tests/coverage/pos/macro-late-suspend/test.scoverage.check @@ -53,23 +53,6 @@ false private def mkVisitorTypeImpl 3 -macro-late-suspend/Test.scala -example -Test -Object -example.Test - -102 -121 -8 - -Apply -false -0 -false -mkVisitorType[Test] - -4 macro-late-suspend/UsesTest.scala example UsesTest$package diff --git a/tests/coverage/pos/scoverage-samples-case.scoverage.check b/tests/coverage/pos/scoverage-samples-case.scoverage.check index 4b67fa77541c..a0fa8febcd99 100644 --- a/tests/coverage/pos/scoverage-samples-case.scoverage.check +++ b/tests/coverage/pos/scoverage-samples-case.scoverage.check @@ -24,6 +24,23 @@ org.scoverage.samples PriceEngine Class org.scoverage.samples.PriceEngine + +293 +298 +14 + +Literal +false +0 +false +"abc" + +1 +scoverage-samples-case.scala +org.scoverage.samples +PriceEngine +Class +org.scoverage.samples.PriceEngine $anonfun 362 368 @@ -35,7 +52,7 @@ false false stop() -1 +2 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -52,7 +69,7 @@ true false =>\n stop() -2 +3 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -69,7 +86,7 @@ false false stop() -3 +4 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -86,7 +103,7 @@ true false =>\n stop() -4 +5 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -103,7 +120,24 @@ false false def receive -5 +6 +scoverage-samples-case.scala +org.scoverage.samples +PriceEngine +Class +org.scoverage.samples.PriceEngine +stop +443 +462 +25 +!= +Apply +false +0 +false +cancellable != null + +7 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -120,7 +154,7 @@ false false println("stop") -6 +8 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -137,7 +171,7 @@ true false println("stop") -7 +9 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -154,7 +188,7 @@ true false -8 +10 scoverage-samples-case.scala org.scoverage.samples PriceEngine diff --git a/tests/coverage/pos/scoverage-samples-implicit-class.scoverage.check b/tests/coverage/pos/scoverage-samples-implicit-class.scoverage.check index f9bb9e0cd6a3..1c349a5de5c8 100644 --- a/tests/coverage/pos/scoverage-samples-implicit-class.scoverage.check +++ b/tests/coverage/pos/scoverage-samples-implicit-class.scoverage.check @@ -25,15 +25,15 @@ CreditEngine Class org.scoverage.samples.CreditEngine $anonfun -263 -276 -11 -! +245 +255 +10 +< Apply false 0 false -"if 1" ! "xd" +req < 2000 1 scoverage-samples-implicit-class.scala @@ -43,14 +43,14 @@ Class org.scoverage.samples.CreditEngine $anonfun 263 -269 +276 11 -StringOpssssss +! Apply false 0 false -"if 1" +"if 1" ! "xd" 2 scoverage-samples-implicit-class.scala @@ -161,23 +161,6 @@ StringOpssssss Class org.scoverage.samples.StringOpssssss ! -160 -167 -5 -+ -Apply -false -0 -false -s + "!" - -9 -scoverage-samples-implicit-class.scala -org.scoverage.samples -StringOpssssss -Class -org.scoverage.samples.StringOpssssss -! 124 129 5 @@ -188,7 +171,7 @@ false false def ! -10 +9 scoverage-samples-implicit-class.scala org.scoverage.samples scoverage-samples-implicit-class$package diff --git a/tests/coverage/run/currying/test.scoverage.check b/tests/coverage/run/currying/test.scoverage.check index abc1876942db..5d1b4233a226 100644 --- a/tests/coverage/run/currying/test.scoverage.check +++ b/tests/coverage/run/currying/test.scoverage.check @@ -25,6 +25,23 @@ Test Object .Test f1 +48 +53 +2 ++ +Apply +false +0 +false +a+b+c + +1 +currying/test.scala + +Test +Object +.Test +f1 15 21 2 @@ -35,7 +52,24 @@ false false def f1 -1 +2 +currying/test.scala + +Test +Object +.Test +$anonfun +105 +110 +4 ++ +Apply +false +0 +false +a+b+c + +3 currying/test.scala Test @@ -52,7 +86,24 @@ false false def f2 -2 +4 +currying/test.scala + +Test +Object +.Test +$anonfun +166 +171 +7 ++ +Apply +false +0 +false +a+b+c + +5 currying/test.scala Test @@ -69,7 +120,24 @@ false false def g1 -3 +6 +currying/test.scala + +Test +Object +.Test +g2 +226 +231 +9 ++ +Apply +false +0 +false +a+b+c + +7 currying/test.scala Test @@ -86,7 +154,7 @@ false false def g2 -4 +8 currying/test.scala Test @@ -103,7 +171,7 @@ false false println(f1(0)(1)(2)) -5 +9 currying/test.scala Test @@ -120,7 +188,7 @@ false false f1(0)(1)(2) -6 +10 currying/test.scala Test @@ -137,7 +205,7 @@ false false println(f2(0)(1)(2)) -7 +11 currying/test.scala Test @@ -154,58 +222,7 @@ false false f2(0)(1)(2) -8 -currying/test.scala - -Test -Object -.Test -main -310 -318 -13 -apply -Apply -false -0 -false -f2(0)(1) - -9 -currying/test.scala - -Test -Object -.Test -main -310 -315 -13 -apply -Apply -false -0 -false -f2(0) - -10 -currying/test.scala - -Test -Object -.Test -main -310 -312 -13 -f2 -Ident -false -0 -false -f2 - -11 +12 currying/test.scala Test @@ -222,7 +239,7 @@ false false println(g1(using 0)(using 1)(using 2)) -12 +13 currying/test.scala Test @@ -239,7 +256,7 @@ false false g1(using 0)(using 1)(using 2) -13 +14 currying/test.scala Test @@ -256,7 +273,7 @@ false false println(g2(using 0)(using 1)(using 2)) -14 +15 currying/test.scala Test @@ -273,7 +290,7 @@ false false g2(using 0)(using 1)(using 2) -15 +16 currying/test.scala Test diff --git a/tests/coverage/run/extend-case-class/test.scoverage.check b/tests/coverage/run/extend-case-class/test.scoverage.check index b355140d2520..107cef2cc371 100644 --- a/tests/coverage/run/extend-case-class/test.scoverage.check +++ b/tests/coverage/run/extend-case-class/test.scoverage.check @@ -21,6 +21,91 @@ 0 extend-case-class/test.scala +DecimalConf +Object +.DecimalConf + +194 +198 +4 + +Literal +false +0 +false +6178 + +1 +extend-case-class/test.scala + +DecimalConf +Object +.DecimalConf + +200 +203 +4 + +Literal +false +0 +false +308 + +2 +extend-case-class/test.scala + +test$package +Object +.test$package +Test +239 +279 +8 +apply +Apply +false +0 +false +DecimalConf(MathContext.DECIMAL32, 1, 0) + +3 +extend-case-class/test.scala + +test$package +Object +.test$package +Test +274 +275 +8 + +Literal +false +0 +false +1 + +4 +extend-case-class/test.scala + +test$package +Object +.test$package +Test +277 +278 +8 + +Literal +false +0 +false +0 + +5 +extend-case-class/test.scala + test$package Object .test$package @@ -35,7 +120,7 @@ false false println(c.scaleLimit) -1 +6 extend-case-class/test.scala test$package @@ -52,7 +137,7 @@ false false println(DecimalConf.scaleLimit) -2 +7 extend-case-class/test.scala test$package diff --git a/tests/coverage/run/i16940/test.scoverage.check b/tests/coverage/run/i16940/test.scoverage.check index 357080ba9da8..eb8cd5d7d61c 100644 --- a/tests/coverage/run/i16940/test.scoverage.check +++ b/tests/coverage/run/i16940/test.scoverage.check @@ -59,23 +59,6 @@ Test Object .Test -371 -454 -20 -sequence -Apply -false -0 -false -Future.sequence(Seq(brokenSynchronizedBlock(false), brokenSynchronizedBlock(true))) - -3 -i16940/i16940.scala - -Test -Object -.Test - 387 453 20 @@ -86,7 +69,7 @@ false false Seq(brokenSynchronizedBlock(false), brokenSynchronizedBlock(true)) -4 +3 i16940/i16940.scala Test @@ -103,7 +86,7 @@ false false brokenSynchronizedBlock(false) -5 +4 i16940/i16940.scala Test @@ -120,7 +103,7 @@ false false brokenSynchronizedBlock(true) -6 +5 i16940/i16940.scala Test @@ -137,75 +120,58 @@ false false println(test) -7 +6 i16940/i16940.scala Test Object .Test -$anonfun -508 -525 -23 -assertFailed + +539 +540 +25 +DurationInt Apply false 0 false -assert(test == 2) +3 -8 +7 i16940/i16940.scala Test Object .Test -$anonfun -508 -525 -23 -assertFailed -Apply -true -0 + +539 +548 +25 +seconds +Select false -assert(test == 2) - -9 -i16940/i16940.scala - -Test -Object -.Test -$anonfun -508 -525 -23 - -Literal -true 0 false -assert(test == 2) +3.seconds -10 +8 i16940/i16940.scala -Test +i16940$package Object -.Test +.i16940$package -539 -548 -25 -seconds -Select +125 +126 +5 + +Literal false 0 false -3.seconds +0 -11 +9 i16940/i16940.scala i16940$package @@ -222,7 +188,7 @@ false false Future {\n if (option) {\n Thread.sleep(500)\n }\n synchronized {\n val tmp = test\n Thread.sleep(1000)\n test = tmp + 1\n }\n} -12 +10 i16940/i16940.scala i16940$package @@ -239,7 +205,7 @@ false false Thread.sleep(500) -13 +11 i16940/i16940.scala i16940$package @@ -256,7 +222,7 @@ true false {\n Thread.sleep(500)\n } -14 +12 i16940/i16940.scala i16940$package @@ -273,7 +239,7 @@ true false -15 +13 i16940/i16940.scala i16940$package @@ -290,7 +256,7 @@ false false synchronized {\n val tmp = test\n Thread.sleep(1000)\n test = tmp + 1\n } -16 +14 i16940/i16940.scala i16940$package @@ -307,7 +273,24 @@ false false Thread.sleep(1000) -17 +15 +i16940/i16940.scala + +i16940$package +Object +.i16940$package +brokenSynchronizedBlock +310 +317 +14 ++ +Apply +false +0 +false +tmp + 1 + +16 i16940/i16940.scala i16940$package diff --git a/tests/coverage/run/i18233-min/test.scoverage.check b/tests/coverage/run/i18233-min/test.scoverage.check index 7570ebaaed96..6bc38ed61a85 100644 --- a/tests/coverage/run/i18233-min/test.scoverage.check +++ b/tests/coverage/run/i18233-min/test.scoverage.check @@ -42,23 +42,6 @@ Test Object .Test -139 -144 -11 -aList -Ident -false -0 -false -aList - -2 -i18233-min/i18233-min.scala - -Test -Object -.Test - 148 168 12 @@ -69,24 +52,7 @@ false false println(anotherList) -3 -i18233-min/i18233-min.scala - -Test -Object -.Test - -156 -167 -12 -anotherList -Ident -false -0 -false -anotherList - -4 +2 i18233-min/i18233-min.scala i18233-min$package @@ -103,7 +69,7 @@ false false List(Array[String]()*) -5 +3 i18233-min/i18233-min.scala i18233-min$package @@ -120,7 +86,7 @@ false false Array[String]() -6 +4 i18233-min/i18233-min.scala i18233-min$package @@ -137,7 +103,7 @@ false false def aList -7 +5 i18233-min/i18233-min.scala i18233-min$package @@ -154,6 +120,40 @@ false false Array("abc", "def") +6 +i18233-min/i18233-min.scala + +i18233-min$package +Object +.i18233-min$package +arr +56 +61 +5 + +Literal +false +0 +false +"abc" + +7 +i18233-min/i18233-min.scala + +i18233-min$package +Object +.i18233-min$package +arr +63 +68 +5 + +Literal +false +0 +false +"def" + 8 i18233-min/i18233-min.scala diff --git a/tests/coverage/run/inheritance/test.scoverage.check b/tests/coverage/run/inheritance/test.scoverage.check index 387a080463e2..48b8e02d59e1 100644 --- a/tests/coverage/run/inheritance/test.scoverage.check +++ b/tests/coverage/run/inheritance/test.scoverage.check @@ -21,6 +21,23 @@ 0 inheritance/test.scala +B +Class +.B + +61 +62 +2 + +Literal +false +0 +false +0 + +1 +inheritance/test.scala + C1 Class .C1 @@ -35,7 +52,24 @@ false false println("block") +2 +inheritance/test.scala + +C1 +Class +.C1 + +102 +103 +3 + +Literal +false +0 +false 1 + +3 inheritance/test.scala C2 @@ -52,7 +86,7 @@ false false A(2,2) -2 +4 inheritance/test.scala test$package @@ -69,7 +103,7 @@ false false println(C1().x) -3 +5 inheritance/test.scala test$package @@ -86,7 +120,7 @@ false false C1() -4 +6 inheritance/test.scala test$package @@ -103,7 +137,7 @@ false false println(C2().x) -5 +7 inheritance/test.scala test$package @@ -120,7 +154,7 @@ false false C2() -6 +8 inheritance/test.scala test$package diff --git a/tests/coverage/run/inline-def/test.scoverage.check b/tests/coverage/run/inline-def/test.scoverage.check index 17fa7c049107..7ca855c41008 100644 --- a/tests/coverage/run/inline-def/test.scoverage.check +++ b/tests/coverage/run/inline-def/test.scoverage.check @@ -76,23 +76,6 @@ test$package Object .test$package Test -134 -148 -8 -toString -Apply -false -0 -false -"foo".toString - -4 -inline-def/test.scala - -test$package -Object -.test$package -Test 263 277 16 @@ -103,24 +86,7 @@ false false println(a.bar) -5 -inline-def/test.scala - -test$package -Object -.test$package -Test -176 -190 -9 -toString -Apply -false -0 -false -"bar".toString - -6 +4 inline-def/test.scala test$package @@ -137,7 +103,7 @@ false false println(b.foo) -7 +5 inline-def/test.scala test$package @@ -154,7 +120,7 @@ false false b.foo -8 +6 inline-def/test.scala test$package diff --git a/tests/coverage/run/interpolation/test.scoverage.check b/tests/coverage/run/interpolation/test.scoverage.check index 37562dab5509..3147a19a61a0 100644 --- a/tests/coverage/run/interpolation/test.scoverage.check +++ b/tests/coverage/run/interpolation/test.scoverage.check @@ -127,15 +127,15 @@ Test Object .Test main -229 -278 -11 -map -Apply +200 +203 +10 + +Literal false 0 false -xs.zipWithIndex.map((s, i) => println(s"$i: $s")) +"d" 7 interpolation/test.scala @@ -144,17 +144,85 @@ Test Object .Test main +205 +208 +10 + +Literal +false +0 +false +"o" + +8 +interpolation/test.scala + +Test +Object +.Test +main +210 +213 +10 + +Literal +false +0 +false +"t" + +9 +interpolation/test.scala + +Test +Object +.Test +main +215 +218 +10 + +Literal +false +0 +false +"t" + +10 +interpolation/test.scala + +Test +Object +.Test +main +220 +223 +10 + +Literal +false +0 +false +"y" + +11 +interpolation/test.scala + +Test +Object +.Test +main 229 -244 +278 11 -zipWithIndex -Select +map +Apply false 0 false -xs.zipWithIndex +xs.zipWithIndex.map((s, i) => println(s"$i: $s")) -8 +12 interpolation/test.scala Test @@ -171,7 +239,7 @@ false false println(s"$i: $s") -9 +13 interpolation/test.scala Test @@ -188,7 +256,7 @@ false false s"$i: $s" -10 +14 interpolation/test.scala Test @@ -205,7 +273,7 @@ false false println(simple(1, "abc")) -11 +15 interpolation/test.scala Test @@ -222,7 +290,7 @@ false false simple(1, "abc") -12 +16 interpolation/test.scala Test @@ -239,7 +307,7 @@ false false println(hexa(127)) -13 +17 interpolation/test.scala Test @@ -256,7 +324,7 @@ false false hexa(127) -14 +18 interpolation/test.scala Test @@ -273,7 +341,7 @@ false false println(raw"a\\nb") -15 +19 interpolation/test.scala Test @@ -290,7 +358,7 @@ false false raw"a\\nb" -16 +20 interpolation/test.scala Test diff --git a/tests/coverage/run/java-methods/test.scoverage.check b/tests/coverage/run/java-methods/test.scoverage.check index 891af1804831..679a5b7dbff4 100644 --- a/tests/coverage/run/java-methods/test.scoverage.check +++ b/tests/coverage/run/java-methods/test.scoverage.check @@ -58,6 +58,23 @@ java-methods/test.scala test$package Object .test$package +$anonfun +124 +126 +6 + +Literal +false +0 +false +() + +3 +java-methods/test.scala + +test$package +Object +.test$package Test 140 152 @@ -69,7 +86,7 @@ false false JavaObject() -3 +4 java-methods/test.scala test$package @@ -86,7 +103,7 @@ false false obj.f() -4 +5 java-methods/test.scala test$package @@ -103,7 +120,7 @@ false false println(obj.identity[Int](0)) -5 +6 java-methods/test.scala test$package @@ -120,7 +137,7 @@ false false obj.identity[Int](0) -6 +7 java-methods/test.scala test$package @@ -137,7 +154,7 @@ false false println("ok!") -7 +8 java-methods/test.scala test$package diff --git a/tests/coverage/run/lifting-bool/test.scoverage.check b/tests/coverage/run/lifting-bool/test.scoverage.check index 5eb3d864939f..7ed81d8b535c 100644 --- a/tests/coverage/run/lifting-bool/test.scoverage.check +++ b/tests/coverage/run/lifting-bool/test.scoverage.check @@ -76,6 +76,40 @@ test$package Object .test$package Test +101 +120 +8 +|| +Apply +false +0 +false +true || notCalled() + +4 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +101 +105 +8 + +Literal +false +0 +false +true + +5 +lifting-bool/test.scala + +test$package +Object +.test$package +Test 109 120 8 @@ -86,7 +120,41 @@ false false notCalled() -4 +6 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +150 +170 +9 +&& +Apply +false +0 +false +false && notCalled() + +7 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +150 +155 +9 + +Literal +false +0 +false +false + +8 lifting-bool/test.scala test$package @@ -103,7 +171,41 @@ false false notCalled() -5 +9 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +200 +230 +10 +|| +Apply +false +0 +false +(true || false) || notCalled() + +10 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +201 +205 +10 + +Literal +false +0 +false +true + +11 lifting-bool/test.scala test$package @@ -120,7 +222,75 @@ false false notCalled() -6 +12 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +249 +278 +11 +&& +Apply +false +0 +false +true && (false && notCalled() + +13 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +249 +253 +11 + +Literal +false +0 +false +true + +14 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +258 +278 +11 +&& +Apply +false +0 +false +false && notCalled() + +15 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +258 +263 +11 + +Literal +false +0 +false +false + +16 lifting-bool/test.scala test$package @@ -137,7 +307,41 @@ false false notCalled() -7 +17 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +299 +329 +12 +&& +Apply +false +0 +false +(true && false) && notCalled() + +18 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +300 +304 +12 + +Literal +false +0 +false +true + +19 lifting-bool/test.scala test$package @@ -154,7 +358,7 @@ false false notCalled() -8 +20 lifting-bool/test.scala test$package @@ -171,7 +375,7 @@ false false println(s"$a $b $c $d $e") -9 +21 lifting-bool/test.scala test$package @@ -188,7 +392,7 @@ false false s"$a $b $c $d $e" -10 +22 lifting-bool/test.scala test$package @@ -205,7 +409,7 @@ false false f(true, false) -11 +23 lifting-bool/test.scala test$package @@ -222,7 +426,7 @@ false false println(x) -12 +24 lifting-bool/test.scala test$package @@ -239,7 +443,41 @@ false false f(true || notCalled(), false && notCalled()) -13 +25 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +424 +443 +18 +|| +Apply +false +0 +false +true || notCalled() + +26 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +424 +428 +18 + +Literal +false +0 +false +true + +27 lifting-bool/test.scala test$package @@ -256,7 +494,41 @@ false false notCalled() -14 +28 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +445 +465 +18 +&& +Apply +false +0 +false +false && notCalled() + +29 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +445 +450 +18 + +Literal +false +0 +false +false + +30 lifting-bool/test.scala test$package @@ -273,7 +545,7 @@ false false notCalled() -15 +31 lifting-bool/test.scala test$package @@ -290,7 +562,7 @@ false false println(x) -16 +32 lifting-bool/test.scala test$package diff --git a/tests/coverage/run/lifting/test.scoverage.check b/tests/coverage/run/lifting/test.scoverage.check index 136b8e2e4fbb..f876ba013f46 100644 --- a/tests/coverage/run/lifting/test.scoverage.check +++ b/tests/coverage/run/lifting/test.scoverage.check @@ -42,15 +42,15 @@ Vals Class .Vals -41 -57 -3 -:: -Apply +27 +28 +2 + +Literal false 0 false -l :: List(1,2,3) +1 2 lifting/test.scala @@ -59,66 +59,66 @@ Vals Class .Vals -46 +41 57 3 -apply +:: Apply false 0 false -List(1,2,3) +l :: List(1,2,3) 3 lifting/test.scala -A +Vals Class -.A -msg -104 -136 -6 -+ -Apply +.Vals + +51 +52 +3 + +Literal false 0 false -"string" + a + "." + b + "." + c +1 4 lifting/test.scala -A +Vals Class -.A -msg -104 -132 -6 -+ -Apply +.Vals + +53 +54 +3 + +Literal false 0 false -"string" + a + "." + b + "." +2 5 lifting/test.scala -A +Vals Class -.A -msg -104 -126 -6 -+ -Apply +.Vals + +55 +56 +3 + +Literal false 0 false -"string" + a + "." + b +3 6 lifting/test.scala @@ -128,14 +128,14 @@ Class .A msg 104 -122 +136 6 + Apply false 0 false -"string" + a + "." +"string" + a + "." + b + "." + c 7 lifting/test.scala @@ -145,14 +145,14 @@ Class .A msg 104 -116 +112 6 -+ -Apply + +Literal false 0 false -"string" + a +"string" 8 lifting/test.scala @@ -178,6 +178,23 @@ A Class .A integer +158 +159 +7 + +Literal +false +0 +false +0 + +10 +lifting/test.scala + +A +Class +.A +integer 139 150 7 @@ -188,7 +205,7 @@ false false def integer -10 +11 lifting/test.scala A @@ -205,7 +222,7 @@ false false def ex -11 +12 lifting/test.scala test$package @@ -222,7 +239,41 @@ false false A() -12 +13 +lifting/test.scala + +test$package +Object +.test$package +Test +235 +238 +13 + +Literal +false +0 +false +123 + +14 +lifting/test.scala + +test$package +Object +.test$package +f +251 +253 +14 + +Literal +false +0 +false +-1 + +15 lifting/test.scala test$package @@ -239,7 +290,7 @@ false false def f -13 +16 lifting/test.scala test$package @@ -256,7 +307,24 @@ false false a.msg(i, 0, a.integer) -14 +17 +lifting/test.scala + +test$package +Object +.test$package +Test +273 +274 +15 + +Literal +false +0 +false +0 + +18 lifting/test.scala test$package @@ -273,7 +341,7 @@ false false a.integer -15 +19 lifting/test.scala test$package @@ -290,7 +358,7 @@ false false println(x) -16 +20 lifting/test.scala test$package @@ -307,24 +375,24 @@ false false a.ex.msg(i, 0, a.ex.integer) -17 +21 lifting/test.scala test$package Object .test$package Test -306 -310 +318 +319 17 -ex -Select + +Literal false 0 false -a.ex +0 -18 +22 lifting/test.scala test$package @@ -341,7 +409,7 @@ false false a.ex -19 +23 lifting/test.scala test$package @@ -358,7 +426,7 @@ false false a.ex.integer -20 +24 lifting/test.scala test$package @@ -375,7 +443,7 @@ false false println(x) -21 +25 lifting/test.scala test$package @@ -392,7 +460,7 @@ false false a.msg(f(), 0, i) -22 +26 lifting/test.scala test$package @@ -409,7 +477,24 @@ false false f() -23 +27 +lifting/test.scala + +test$package +Object +.test$package +Test +365 +366 +19 + +Literal +false +0 +false +0 + +28 lifting/test.scala test$package @@ -426,7 +511,7 @@ false false println(x) -24 +29 lifting/test.scala test$package diff --git a/tests/coverage/run/macro-suspend/test.scoverage.check b/tests/coverage/run/macro-suspend/test.scoverage.check index 759897eb7747..4cae5a319598 100644 --- a/tests/coverage/run/macro-suspend/test.scoverage.check +++ b/tests/coverage/run/macro-suspend/test.scoverage.check @@ -76,6 +76,23 @@ Greeting Object .Greeting greet +252 +259 +8 + +Literal +false +0 +false +"hello" + +4 +macro-suspend/Macro.scala + +Greeting +Object +.Greeting +greet 238 247 8 @@ -86,7 +103,7 @@ false false def greet -4 +5 macro-suspend/Test.scala Test @@ -103,23 +120,6 @@ false false println(Macro.decorate(Greeting.greet())) -5 -macro-suspend/Test.scala - -Test -Object -.Test -main -65 -97 -3 -+ -Apply -false -0 -false -Macro.decorate(Greeting.greet()) - 6 macro-suspend/Test.scala @@ -127,40 +127,6 @@ Test Object .Test main -65 -97 -3 -+ -Apply -false -0 -false -Macro.decorate(Greeting.greet()) - -7 -macro-suspend/Test.scala - -Test -Object -.Test -main -80 -96 -3 -greet -Apply -false -0 -false -Greeting.greet() - -8 -macro-suspend/Test.scala - -Test -Object -.Test -main 15 23 2 diff --git a/tests/coverage/run/parameterless/test.scoverage.check b/tests/coverage/run/parameterless/test.scoverage.check index 5050180e7886..b792ce9c73ea 100644 --- a/tests/coverage/run/parameterless/test.scoverage.check +++ b/tests/coverage/run/parameterless/test.scoverage.check @@ -42,6 +42,23 @@ O Object .O f +51 +60 +4 + +Literal +false +0 +false +"O.f_res" + +2 +parameterless/test.scala + +O +Object +.O +f 12 17 2 @@ -52,7 +69,7 @@ false false def f -2 +3 parameterless/test.scala O @@ -69,7 +86,24 @@ false false println("O.g") -3 +4 +parameterless/test.scala + +O +Object +.O +g +106 +115 +8 + +Literal +false +0 +false +"O.g_res" + +5 parameterless/test.scala O @@ -86,7 +120,7 @@ false false def g -4 +6 parameterless/test.scala test$package @@ -103,7 +137,24 @@ false false println("f") -5 +7 +parameterless/test.scala + +test$package +Object +.test$package +f +179 +186 +14 + +Literal +false +0 +false +"f_res" + +8 parameterless/test.scala test$package @@ -120,7 +171,7 @@ false false def f -6 +9 parameterless/test.scala test$package @@ -137,7 +188,24 @@ false false println("g") -7 +10 +parameterless/test.scala + +test$package +Object +.test$package +g +230 +237 +18 + +Literal +false +0 +false +"g_res" + +11 parameterless/test.scala test$package @@ -154,7 +222,7 @@ false false def g -8 +12 parameterless/test.scala test$package @@ -171,7 +239,7 @@ false false f -9 +13 parameterless/test.scala test$package @@ -188,7 +256,7 @@ false false g -10 +14 parameterless/test.scala test$package @@ -205,24 +273,7 @@ false false println(f) -11 -parameterless/test.scala - -test$package -Object -.test$package -Test -273 -274 -22 -f -Ident -false -0 -false -f - -12 +15 parameterless/test.scala test$package @@ -239,7 +290,7 @@ false false println(g) -13 +16 parameterless/test.scala test$package @@ -256,7 +307,7 @@ false false g -14 +17 parameterless/test.scala test$package @@ -273,7 +324,7 @@ false false println(O.f) -15 +18 parameterless/test.scala test$package @@ -290,7 +341,7 @@ false false O.f -16 +19 parameterless/test.scala test$package @@ -307,7 +358,7 @@ false false println(O.g) -17 +20 parameterless/test.scala test$package @@ -324,7 +375,7 @@ false false O.g -18 +21 parameterless/test.scala test$package diff --git a/tests/coverage/run/trait/test.scoverage.check b/tests/coverage/run/trait/test.scoverage.check index 19a88ebc7f6f..1f921a3bfae8 100644 --- a/tests/coverage/run/trait/test.scoverage.check +++ b/tests/coverage/run/trait/test.scoverage.check @@ -25,6 +25,23 @@ T1 Trait .T1 x +20 +21 +2 + +Literal +false +0 +false +0 + +1 +trait/test.scala + +T1 +Trait +.T1 +x 12 17 2 @@ -35,7 +52,24 @@ false false def x -1 +2 +trait/test.scala + +Impl2 +Class +.Impl2 + +94 +100 +7 + +Literal +false +0 +false +"test" + +3 trait/test.scala Impl3 @@ -52,7 +86,7 @@ false false Impl2() -2 +4 trait/test.scala test$package @@ -69,7 +103,7 @@ false false println(Impl1().x) -3 +5 trait/test.scala test$package @@ -86,7 +120,7 @@ false false Impl1() -4 +6 trait/test.scala test$package @@ -103,7 +137,7 @@ false false Impl1().x -5 +7 trait/test.scala test$package @@ -120,7 +154,7 @@ false false println(Impl2().p) -6 +8 trait/test.scala test$package @@ -137,7 +171,7 @@ false false Impl2() -7 +9 trait/test.scala test$package @@ -154,7 +188,7 @@ false false println(Impl3().p) -8 +10 trait/test.scala test$package @@ -171,7 +205,7 @@ false false Impl3() -9 +11 trait/test.scala test$package diff --git a/tests/coverage/run/type-apply/test.measurement.check b/tests/coverage/run/type-apply/test.measurement.check index f1d6ee365359..e199fee6b817 100644 --- a/tests/coverage/run/type-apply/test.measurement.check +++ b/tests/coverage/run/type-apply/test.measurement.check @@ -1,5 +1,7 @@ -4 +6 1 2 3 +4 +5 0 diff --git a/tests/coverage/run/type-apply/test.scoverage.check b/tests/coverage/run/type-apply/test.scoverage.check index 7d76b11f2f8b..c20e88c0351d 100644 --- a/tests/coverage/run/type-apply/test.scoverage.check +++ b/tests/coverage/run/type-apply/test.scoverage.check @@ -59,15 +59,15 @@ test$package Object .test$package Test -171 -182 +176 +177 5 -apply -Apply + +Literal false 0 false -List(1,2,3) +1 3 type-apply/test.scala @@ -75,6 +75,40 @@ type-apply/test.scala test$package Object .test$package +Test +178 +179 +5 + +Literal +false +0 +false +2 + +4 +type-apply/test.scala + +test$package +Object +.test$package +Test +180 +181 +5 + +Literal +false +0 +false +3 + +5 +type-apply/test.scala + +test$package +Object +.test$package $anonfun 192 199 @@ -86,7 +120,7 @@ false false List(a) -4 +6 type-apply/test.scala test$package diff --git a/tests/coverage/run/varargs/test.scoverage.check b/tests/coverage/run/varargs/test.scoverage.check index 3c31f9388409..024156dcfeb6 100644 --- a/tests/coverage/run/varargs/test.scoverage.check +++ b/tests/coverage/run/varargs/test.scoverage.check @@ -25,6 +25,23 @@ test_1$package Object .test_1$package repeated +75 +77 +4 + +Literal +false +0 +false +() + +1 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +repeated 48 60 4 @@ -35,7 +52,7 @@ false false def repeated -1 +2 varargs/test_1.scala test_1$package @@ -52,7 +69,7 @@ false false def f -2 +3 varargs/test_1.scala test_1$package @@ -69,7 +86,7 @@ false false repeated() -3 +4 varargs/test_1.scala test_1$package @@ -86,7 +103,7 @@ false false repeated(f(""), "b") -4 +5 varargs/test_1.scala test_1$package @@ -103,7 +120,24 @@ false false f("") -5 +6 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +Test +149 +152 +11 + +Literal +false +0 +false +"b" + +7 varargs/test_1.scala test_1$package @@ -120,7 +154,7 @@ false false JavaVarargs_1.method() -6 +8 varargs/test_1.scala test_1$package @@ -137,7 +171,24 @@ false false JavaVarargs_1.method("") -7 +9 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +Test +202 +204 +13 + +Literal +false +0 +false +"" + +10 varargs/test_1.scala test_1$package @@ -154,7 +205,24 @@ false false JavaVarargs_1.multiple("first") -8 +11 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +Test +240 +247 +15 + +Literal +false +0 +false +"first" + +12 varargs/test_1.scala test_1$package @@ -171,7 +239,7 @@ false false println(m) -9 +13 varargs/test_1.scala test_1$package @@ -188,7 +256,7 @@ false false JavaVarargs_1.multiple(f("first")) -10 +14 varargs/test_1.scala test_1$package @@ -205,7 +273,7 @@ false false f("first") -11 +15 varargs/test_1.scala test_1$package @@ -222,7 +290,7 @@ false false println(m) -12 +16 varargs/test_1.scala test_1$package @@ -239,7 +307,7 @@ false false JavaVarargs_1.multiple(f("first"), "a", "b", "c") -13 +17 varargs/test_1.scala test_1$package @@ -256,7 +324,58 @@ false false f("first") -14 +18 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +Test +357 +360 +19 + +Literal +false +0 +false +"a" + +19 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +Test +362 +365 +19 + +Literal +false +0 +false +"b" + +20 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +Test +367 +370 +19 + +Literal +false +0 +false +"c" + +21 varargs/test_1.scala test_1$package @@ -273,7 +392,7 @@ false false println(m) -15 +22 varargs/test_1.scala test_1$package From fafb5c1d5ae5186d0c2dd66049f589dd8a7e943b Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Tue, 19 Aug 2025 09:14:11 +0100 Subject: [PATCH 070/111] fix: allow postfix setters under language.postfixOps (#23775) Allow for postfix operators to be followed by assigns. This enables the definition and use of the following syntax (more precisely the parsing of the `>_=` method as a `postfix operator + assign`): ```scala val v = new Vector(1, 2, 3) println(v) // prints <1, 2, 3> v<1> = 10 // assign 10 to element at index 1 println(v) // prints <1, 10, 3> println(v<1>) // prints: value at 1 is 10 // Definition of Vector: class Vector(values: Int*) { val data = values.toArray class Getter(i: Int) { def `>_=`(x: Int) = data(i) = x def > : Int = data(i) } def < (i:Int) = new Getter(i) override def toString = data.mkString("<", ", ", ">") } ``` [Cherry-picked de18af4dc1ea9c66cb98bfda80fa167d0112800c] --- .../dotty/tools/dotc/parsing/Parsers.scala | 3 ++- docs/_docs/internals/syntax.md | 1 + docs/_docs/reference/syntax.md | 1 + tests/pos/vec-access-syntax.scala | 23 +++++++++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 tests/pos/vec-access-syntax.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index f6dd3c2396d4..c9a3f04371a2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2370,6 +2370,7 @@ object Parsers { * | ForExpr * | [SimpleExpr `.'] id `=' Expr * | PrefixOperator SimpleExpr `=' Expr + * | InfixExpr id [nl] `=' Expr -- only if language.postfixOps is enabled * | SimpleExpr1 ArgumentExprs `=' Expr * | PostfixExpr [Ascription] * | ‘inline’ InfixExpr MatchClause @@ -2525,7 +2526,7 @@ object Parsers { def expr1Rest(t: Tree, location: Location): Tree = if in.token == EQUALS then t match - case Ident(_) | Select(_, _) | Apply(_, _) | PrefixOp(_, _) => + case Ident(_) | Select(_, _) | Apply(_, _) | PrefixOp(_, _) | PostfixOp(_, _) => atSpan(startOffset(t), in.skipToken()) { val loc = if location.inArgs then location else Location.ElseWhere Assign(t, subPart(() => expr(loc))) diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 9a5d3d4b2776..50cc178b86a5 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -261,6 +261,7 @@ Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[ | ForExpr | [SimpleExpr ‘.’] id ‘=’ Expr Assign(expr, expr) | PrefixOperator SimpleExpr ‘=’ Expr Assign(expr, expr) + | InfixExpr id [nl] `=' Expr Assign(expr, expr) -- only if language.postfixOps is enabled | SimpleExpr ArgumentExprs ‘=’ Expr Assign(expr, expr) | PostfixExpr [Ascription] | ‘inline’ InfixExpr MatchClause diff --git a/docs/_docs/reference/syntax.md b/docs/_docs/reference/syntax.md index f2be16f3351c..188294d7a08a 100644 --- a/docs/_docs/reference/syntax.md +++ b/docs/_docs/reference/syntax.md @@ -246,6 +246,7 @@ Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[ | ForExpr | [SimpleExpr ‘.’] id ‘=’ Expr | PrefixOperator SimpleExpr ‘=’ Expr + | InfixExpr id [nl] `=' Expr -- only if language.postfixOps is enabled | SimpleExpr ArgumentExprs ‘=’ Expr | PostfixExpr [Ascription] | ‘inline’ InfixExpr MatchClause diff --git a/tests/pos/vec-access-syntax.scala b/tests/pos/vec-access-syntax.scala new file mode 100644 index 000000000000..524ede685529 --- /dev/null +++ b/tests/pos/vec-access-syntax.scala @@ -0,0 +1,23 @@ +import scala.language.postfixOps + +class Vector(values: Int*) { + val data = values.toArray + class Getter(i: Int) { + def `>_=`(x: Int) = + data(i) = x + def > : Int = + data(i) + } + def < (i:Int) = new Getter(i) + override def toString = data.mkString("<", ", ", ">") +} + +object Test { + def main(args: Array[String]): Unit = { + val v = new Vector(1, 2, 3) + println(v) // prints <1, 2, 3> + v<1> = 10 // assign 10 to element at index 1 + println(v) // prints <1, 10, 3> + println(v<1>) // prints: value at 1 is 10 + } +} From 6349b46eeb2c9eb817b7dcf24c78f8bb703c10a7 Mon Sep 17 00:00:00 2001 From: Vadim Chelyshov Date: Tue, 26 Aug 2025 19:00:16 +0300 Subject: [PATCH 071/111] pc: completions - do not add `[]` for `... derives TC@@` (#23811) Currently it incorectly adds completion members with square brackets. Exmaple: ```scala class X derives CanEqua@@ // returns `CanEqual[@@]` and `CanEqual` // should return only `CanEqual` ``` --- .../dotty/tools/pc/tests/completion/CompletionSuite.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index 277a579ba4ce..4dad18a78181 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -2285,3 +2285,11 @@ class CompletionSuite extends BaseCompletionSuite: |""".stripMargin, "asTerm: Term" ) + + @Test def `derives-no-square-brackets` = + check( + """ + |case class Miau(y: Int) derives Ordering, CanEqu@@ + |""".stripMargin, + "CanEqual scala" + ) From 8d06170ec4f0e67be86f34fd7ee62a2b89282eda Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 22 Sep 2025 22:44:32 +0200 Subject: [PATCH 072/111] pc: completions - do not add `[]` for `... derives TC@@` (#23811) Currently it incorectly adds completion members with square brackets. Exmaple: ```scala class X derives CanEqua@@ // returns `CanEqual[@@]` and `CanEqual` // should return only `CanEqual` ``` [Cherry-picked 17f18031d82bc7345b7bb0af8971de85d66d7c11][modified] From 963f55e014e614a42e376e353aa386fb222c7c9a Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 23 Sep 2025 19:43:20 +0200 Subject: [PATCH 073/111] Test rig handles NL at EOF in neg [Cherry-picked 98a319a1e85198698a8226ba89ff9392275c05a5][modified] --- .../dotty/tools/vulpix/ParallelTesting.scala | 49 ++++++++++++------- tests/neg/parser-stability-11.scala | 2 +- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 41db48272937..5ec53fcb207f 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -41,8 +41,8 @@ import dotty.tools.vulpix.TestConfiguration.defaultOptions * using this, you should be running your JUnit tests **sequentially**, as the * test suite itself runs with a high level of concurrency. */ -trait ParallelTesting extends RunnerOrchestration { self => - import ParallelTesting._ +trait ParallelTesting extends RunnerOrchestration: + import ParallelTesting.* /** If the running environment supports an interactive terminal, each `Test` * will be run with a progress bar and real time feedback @@ -996,29 +996,33 @@ trait ParallelTesting extends RunnerOrchestration { self => (errorMap, expectedErrors) end getErrorMapAndExpectedCount - // return unfulfilled expected errors and unexpected diagnostics + // return unfulfilled expected errors and unexpected diagnostics. + // the errorMap of expected errors is drained and returned as unfulfilled. + // a diagnostic at EOF after NL is recorded at the preceding line, + // to obviate `anypos-error` in that case. def getMissingExpectedErrors(errorMap: HashMap[String, Integer], reporterErrors: Iterator[Diagnostic]): (List[String], List[String]) = val unexpected, unpositioned = ListBuffer.empty[String] // For some reason, absolute paths leak from the compiler itself... def relativize(path: String): String = path.split(JFile.separatorChar).dropWhile(_ != "tests").mkString(JFile.separator) def seenAt(key: String): Boolean = errorMap.get(key) match - case null => false - case 1 => errorMap.remove(key); true - case n => errorMap.put(key, n - 1); true + case null => false + case 1 => errorMap.remove(key); true + case n => errorMap.put(key, n - 1); true def sawDiagnostic(d: Diagnostic): Unit = - d.pos.nonInlined match - case srcpos if srcpos.exists => - val key = s"${relativize(srcpos.source.file.toString)}:${srcpos.line + 1}" - if !seenAt(key) then unexpected += key - case srcpos => - if !seenAt("nopos") then unpositioned += relativize(srcpos.source.file.toString) + val srcpos = d.pos.nonInlined.adjustedAtEOF + val relatively = relativize(srcpos.source.file.toString) + if srcpos.exists then + val key = s"${relatively}:${srcpos.line + 1}" + if !seenAt(key) then unexpected += key + else + if !seenAt("nopos") then unpositioned += relatively reporterErrors.foreach(sawDiagnostic) - errorMap.get("anypos") match - case n if n == unexpected.size => errorMap.remove("anypos") ; unexpected.clear() - case _ => + if errorMap.get("anypos") == unexpected.size then + errorMap.remove("anypos") + unexpected.clear() (errorMap.asScala.keys.toList, (unexpected ++ unpositioned).toList) end getMissingExpectedErrors @@ -1838,9 +1842,8 @@ trait ParallelTesting extends RunnerOrchestration { self => def isUserDebugging: Boolean = val mxBean = ManagementFactory.getRuntimeMXBean mxBean.getInputArguments.asScala.exists(_.contains("jdwp")) -} -object ParallelTesting { +object ParallelTesting: def defaultOutputDir: String = "out"+JFile.separator @@ -1855,4 +1858,14 @@ object ParallelTesting { def isBestEffortTastyFile(f: JFile): Boolean = f.getName.endsWith(".betasty") -} + extension (pos: SourcePosition) + private def adjustedAtEOF: SourcePosition = + if pos.span.isSynthetic + && pos.span.isZeroExtent + && pos.span.exists + && pos.span.start == pos.source.length + && pos.source(pos.span.start - 1) == '\n' + then + pos.withSpan(pos.span.shift(-1)) + else + pos diff --git a/tests/neg/parser-stability-11.scala b/tests/neg/parser-stability-11.scala index 582bcfb88117..b2ea61e594de 100644 --- a/tests/neg/parser-stability-11.scala +++ b/tests/neg/parser-stability-11.scala @@ -3,4 +3,4 @@ case class x0 // error // error } package x0 class x0 // error -// error \ No newline at end of file +// error From e4789deecf0461ab799fe406db6a57b7811dff25 Mon Sep 17 00:00:00 2001 From: som-snytt Date: Wed, 3 Sep 2025 23:40:53 -0700 Subject: [PATCH 074/111] Lint function arrow intended context function (#23847) Fixes #21187 If a function literal `x => body` has an expected type `X ?=> ?` then maybe they intended to write `x ?=> body`. As shown in the test, maybe types will misalign in other ways to emit warnings. [Cherry-picked e0ff32987c6bd608326e9a5eb315150dd0fb7635] --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 +- .../tools/dotc/config/ScalaSettings.scala | 10 +++++---- .../src/dotty/tools/dotc/core/Symbols.scala | 4 ++-- .../dotc/reporting/ConsoleReporter.scala | 6 ++--- .../dotty/tools/dotc/reporting/Reporter.scala | 2 +- .../tools/dotc/transform/init/Checker.scala | 6 ++--- .../src/dotty/tools/dotc/typer/Typer.scala | 10 +++++++++ .../dotty/tools/dotc/CompilationTests.scala | 22 +++++++++---------- .../dotc/config/ScalaSettingsTests.scala | 6 ++--- tests/warn/i21187.scala | 22 +++++++++++++++++++ 10 files changed, 62 insertions(+), 28 deletions(-) create mode 100644 tests/warn/i21187.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index f82f7956b34b..66044dd9462d 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -171,7 +171,7 @@ class Compiler { val rctx = if ctx.settings.Xsemanticdb.value then ctx.addMode(Mode.ReadPositions) - else if ctx.settings.YcheckInitGlobal.value then + else if ctx.settings.YsafeInitGlobal.value then ctx.addMode(Mode.ReadPositions) else ctx diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index a2c557ea2987..2d048befe171 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -159,7 +159,7 @@ private sealed trait WarningSettings: self: SettingGroup => val Whelp: Setting[Boolean] = BooleanSetting(WarningSetting, "W", "Print a synopsis of warning options.") - val XfatalWarnings: Setting[Boolean] = BooleanSetting(WarningSetting, "Werror", "Fail the compilation if there are any warnings.", aliases = List("-Xfatal-warnings")) + val Werror: Setting[Boolean] = BooleanSetting(WarningSetting, "Werror", "Fail the compilation if there are any warnings.", aliases = List("-Xfatal-warnings")) val Wall: Setting[Boolean] = BooleanSetting(WarningSetting, "Wall", "Enable all warning settings.") private val WvalueDiscard: Setting[Boolean] = BooleanSetting(WarningSetting, "Wvalue-discard", "Warn when non-Unit expression results are unused.") private val WNonUnitStatement = BooleanSetting(WarningSetting, "Wnonunit-statement", "Warn when block statements are non-Unit expressions.") @@ -168,6 +168,7 @@ private sealed trait WarningSettings: private val WunstableInlineAccessors = BooleanSetting(WarningSetting, "WunstableInlineAccessors", "Warn an inline methods has references to non-stable binary APIs.") private val WtoStringInterpolated = BooleanSetting(WarningSetting, "Wtostring-interpolated", "Warn a standard interpolator used toString on a reference type.") private val WrecurseWithDefault = BooleanSetting(WarningSetting, "Wrecurse-with-default", "Warn when a method calls itself with a default argument.") + private val WwrongArrow = BooleanSetting(WarningSetting, "Wwrong-arrow", "Warn if function arrow was used instead of context literal ?=>.") private val Wunused: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting( WarningSetting, name = "Wunused", @@ -299,7 +300,7 @@ private sealed trait WarningSettings: def typeParameterShadow(using Context) = allOr("type-parameter-shadow") - val WcheckInit: Setting[Boolean] = BooleanSetting(WarningSetting, "Wsafe-init", "Ensure safe initialization of objects.") + val WsafeInit: Setting[Boolean] = BooleanSetting(WarningSetting, "Wsafe-init", "Ensure safe initialization of objects.") object Whas: def allOr(s: Setting[Boolean])(using Context): Boolean = @@ -311,7 +312,8 @@ private sealed trait WarningSettings: def unstableInlineAccessors(using Context): Boolean = allOr(WunstableInlineAccessors) def toStringInterpolated(using Context): Boolean = allOr(WtoStringInterpolated) def recurseWithDefault(using Context): Boolean = allOr(WrecurseWithDefault) - def checkInit(using Context): Boolean = allOr(WcheckInit) + def wrongArrow(using Context): Boolean = allOr(WwrongArrow) + def safeInit(using Context): Boolean = allOr(WsafeInit) /** -X "Extended" or "Advanced" settings */ private sealed trait XSettings: @@ -453,7 +455,7 @@ private sealed trait YSettings: val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-kind-polymorphism", "Disable kind polymorphism. (This flag has no effect)", deprecation = Deprecation.removed()) val YexplicitNulls: Setting[Boolean] = BooleanSetting(ForkSetting, "Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.") val YnoFlexibleTypes: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-flexible-types", "Disable turning nullable Java return types and parameter types into flexible types, which behave like abstract types with a nullable lower bound and non-nullable upper bound.") - val YcheckInitGlobal: Setting[Boolean] = BooleanSetting(ForkSetting, "Ysafe-init-global", "Check safe initialization of global objects.") + val YsafeInitGlobal: Setting[Boolean] = BooleanSetting(ForkSetting, "Ysafe-init-global", "Check safe initialization of global objects.") val YrequireTargetName: Setting[Boolean] = BooleanSetting(ForkSetting, "Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation.") val YrecheckTest: Setting[Boolean] = BooleanSetting(ForkSetting, "Yrecheck-test", "Run basic rechecking (internal test only).") val YccDebug: Setting[Boolean] = BooleanSetting(ForkSetting, "Ycc-debug", "Used in conjunction with captureChecking language import, debug info for captured references.") diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index c8ede8bfdec2..d86c50cbe2e3 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -84,8 +84,8 @@ object Symbols extends SymUtils { ctx.settings.YretainTrees.value || denot.owner.isTerm || // no risk of leaking memory after a run for these denot.isOneOf(InlineOrProxy) || // need to keep inline info - ctx.settings.Whas.checkInit || // initialization check - ctx.settings.YcheckInitGlobal.value + ctx.settings.Whas.safeInit || // initialization check + ctx.settings.YsafeInitGlobal.value /** The last denotation of this symbol */ private var lastDenot: SymDenotation = uninitialized diff --git a/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 3dc73983056a..3604b5b6ebf9 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -23,9 +23,9 @@ class ConsoleReporter( super.doReport(dia) if ctx.settings.Xprompt.value then dia match - case _: Error => Reporter.displayPrompt(reader, writer) - case _: Warning if ctx.settings.XfatalWarnings.value => Reporter.displayPrompt(reader, writer) - case _ => + case _: Error => Reporter.displayPrompt(reader, writer) + case _: Warning => if ctx.settings.Werror.value then Reporter.displayPrompt(reader, writer) + case _ => } } diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index aadac68b37e1..af92d0e0efdf 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -224,7 +224,7 @@ abstract class Reporter extends interfaces.ReporterResult { incompleteHandler(dia, ctx) def finalizeReporting()(using Context) = - if (hasWarnings && ctx.settings.XfatalWarnings.value) + if (hasWarnings && ctx.settings.Werror.value) report(new Error("No warnings can be incurred under -Werror (or -Xfatal-warnings)", NoSourcePosition)) /** Summary of warnings and errors */ diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 4d5c467cf4fe..d34f95bedef1 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -29,7 +29,7 @@ class Checker extends Phase: override val runsAfter = Set(Pickler.name) override def isEnabled(using Context): Boolean = - super.isEnabled && (ctx.settings.Whas.checkInit || ctx.settings.YcheckInitGlobal.value) + super.isEnabled && (ctx.settings.Whas.safeInit || ctx.settings.YsafeInitGlobal.value) def traverse(traverser: InitTreeTraverser)(using Context): Boolean = monitor(phaseName): val unit = ctx.compilationUnit @@ -50,10 +50,10 @@ class Checker extends Phase: cancellable { val classes = traverser.getClasses() - if ctx.settings.Whas.checkInit then + if ctx.settings.Whas.safeInit then Semantic.checkClasses(classes)(using checkCtx) - if ctx.settings.YcheckInitGlobal.value then + if ctx.settings.YsafeInitGlobal.value then val obj = new Objects obj.checkClasses(classes)(using checkCtx) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 57221d907011..84e79db6be5e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3826,6 +3826,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val ifun = desugar.makeContextualFunction(paramTypes, paramNamesOrNil, tree, erasedParams) typr.println(i"make contextual function $tree / $pt ---> $ifun") typedFunctionValue(ifun, pt) + .tap: + case tree @ Block((m1: DefDef) :: _, _: Closure) if ctx.settings.Whas.wrongArrow => + m1.rhs match + case Block((m2: DefDef) :: _, _: Closure) if m1.paramss.lengthCompare(m2.paramss) == 0 => + val p1s = m1.symbol.info.asInstanceOf[MethodType].paramInfos + val p2s = m2.symbol.info.asInstanceOf[MethodType].paramInfos + if p1s.corresponds(p2s)(_ =:= _) then + report.warning(em"Context function adapts a lambda with the same parameter types, possibly ?=> was intended.", tree.srcPos) + case _ => + case _ => } /** Typecheck and adapt tree, returning a typed tree. Parameters as for `typedUnadapted` */ diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index c49efceff73f..de200a0f774a 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -62,7 +62,7 @@ class CompilationTests { aggregateTests( compileFile("tests/rewrites/rewrites.scala", defaultOptions.and("-source", "3.0-migration").and("-rewrite", "-indent")), compileFile("tests/rewrites/rewrites3x.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), - compileFile("tests/rewrites/rewrites3x-fatal-warnings.scala", defaultOptions.and("-rewrite", "-source", "future-migration", "-Xfatal-warnings")), + compileFile("tests/rewrites/rewrites3x-fatal-warnings.scala", defaultOptions.and("-rewrite", "-source", "future-migration", "-Werror")), compileFile("tests/rewrites/i21394.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), compileFile("tests/rewrites/uninitialized-var.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), compileFile("tests/rewrites/with-type-operator.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), @@ -151,7 +151,7 @@ class CompilationTests { compileFilesInDir("tests/neg-deep-subtype", allowDeepSubtypes), compileFilesInDir("tests/neg-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking", "-language:experimental.separationChecking")), compileFile("tests/neg-custom-args/sourcepath/outer/nested/Test1.scala", defaultOptions.and("-sourcepath", "tests/neg-custom-args/sourcepath")), - compileDir("tests/neg-custom-args/sourcepath2/hi", defaultOptions.and("-sourcepath", "tests/neg-custom-args/sourcepath2", "-Xfatal-warnings")), + compileDir("tests/neg-custom-args/sourcepath2/hi", defaultOptions.and("-sourcepath", "tests/neg-custom-args/sourcepath2", "-Werror")), compileList("duplicate source", List( "tests/neg-custom-args/toplevel-samesource/S.scala", "tests/neg-custom-args/toplevel-samesource/nested/S.scala"), @@ -237,21 +237,21 @@ class CompilationTests { @Test def checkInitGlobal: Unit = { implicit val testGroup: TestGroup = TestGroup("checkInitGlobal") compileFilesInDir("tests/init-global/warn", defaultOptions.and("-Ysafe-init-global"), FileFilter.exclude(TestSources.negInitGlobalScala2LibraryTastyExcludelisted)).checkWarnings() - compileFilesInDir("tests/init-global/pos", defaultOptions.and("-Ysafe-init-global", "-Xfatal-warnings"), FileFilter.exclude(TestSources.posInitGlobalScala2LibraryTastyExcludelisted)).checkCompile() + compileFilesInDir("tests/init-global/pos", defaultOptions.and("-Ysafe-init-global", "-Werror"), FileFilter.exclude(TestSources.posInitGlobalScala2LibraryTastyExcludelisted)).checkCompile() if Properties.usingScalaLibraryTasty && !Properties.usingScalaLibraryCCTasty then compileFilesInDir("tests/init-global/warn-tasty", defaultOptions.and("-Ysafe-init-global"), FileFilter.exclude(TestSources.negInitGlobalScala2LibraryTastyExcludelisted)).checkWarnings() - compileFilesInDir("tests/init-global/pos-tasty", defaultOptions.and("-Ysafe-init-global", "-Xfatal-warnings"), FileFilter.exclude(TestSources.posInitGlobalScala2LibraryTastyExcludelisted)).checkCompile() + compileFilesInDir("tests/init-global/pos-tasty", defaultOptions.and("-Ysafe-init-global", "-Werror"), FileFilter.exclude(TestSources.posInitGlobalScala2LibraryTastyExcludelisted)).checkCompile() end if } // initialization tests - @Test def checkInit: Unit = { - implicit val testGroup: TestGroup = TestGroup("checkInit") - val options = defaultOptions.and("-Wsafe-init", "-Xfatal-warnings") + @Test def safeInit: Unit = { + given TestGroup = TestGroup("safeInit") + val options = defaultOptions.and("-Wsafe-init", "-Werror") compileFilesInDir("tests/init/neg", options).checkExpectedErrors() compileFilesInDir("tests/init/warn", defaultOptions.and("-Wsafe-init")).checkWarnings() compileFilesInDir("tests/init/pos", options).checkCompile() - compileFilesInDir("tests/init/crash", options.without("-Xfatal-warnings")).checkCompile() + compileFilesInDir("tests/init/crash", options.without("-Werror")).checkCompile() // The regression test for i12128 has some atypical classpath requirements. // The test consists of three files: (a) Reflect_1 (b) Macro_2 (c) Test_3 // which must be compiled separately. In addition: @@ -260,7 +260,7 @@ class CompilationTests { // - the output from (a) _must not_ be on the classpath while compiling (c) locally { val i12128Group = TestGroup("checkInit/i12128") - val i12128Options = options.without("-Xfatal-warnings") + val i12128Options = options.without("-Werror") val outDir1 = defaultOutputDir + i12128Group + "/Reflect_1/i12128/Reflect_1" val outDir2 = defaultOutputDir + i12128Group + "/Macro_2/i12128/Macro_2" @@ -279,7 +279,7 @@ class CompilationTests { * an error when reading the files' TASTy trees. */ locally { val tastyErrorGroup = TestGroup("checkInit/tasty-error/val-or-defdef") - val tastyErrorOptions = options.without("-Xfatal-warnings") + val tastyErrorOptions = options.without("-Werror") val classA0 = defaultOutputDir + tastyErrorGroup + "/A/v0/A" val classA1 = defaultOutputDir + tastyErrorGroup + "/A/v1/A" @@ -302,7 +302,7 @@ class CompilationTests { * an error when reading the files' TASTy trees. This fact is demonstrated by the compilation of Main. */ locally { val tastyErrorGroup = TestGroup("checkInit/tasty-error/typedef") - val tastyErrorOptions = options.without("-Xfatal-warnings").without("-Ycheck:all") + val tastyErrorOptions = options.without("-Werror").without("-Ycheck:all") val classC = defaultOutputDir + tastyErrorGroup + "/C/typedef/C" val classA0 = defaultOutputDir + tastyErrorGroup + "/A/v0/A" diff --git a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala index c74be4901137..c7b030f0805c 100644 --- a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala @@ -111,7 +111,7 @@ class ScalaSettingsTests: // createTestCase(settings.YjavaTasty , settings.XjavaTasty), // createTestCase(settings.YearlyTastyOutput , settings.XearlyTastyOutput, ":./"), // createTestCase(settings.YallowOutlineFromTasty, settings.XallowOutlineFromTasty), - createTestCase(settings.YcheckInit , settings.WcheckInit), + createTestCase(settings.YcheckInit , settings.WsafeInit), // createTestCase(settings.Xlint , settings.Wshadow, ":all"), // this setting is not going to be mapped to replacement. Read more in the commit message ).map: (deprecatedArgument, newSetting) => val args = List(deprecatedArgument) @@ -140,7 +140,7 @@ class ScalaSettingsTests: // createTestCase(settings.YjavaTasty , settings.XjavaTasty), // createTestCase(settings.YearlyTastyOutput , settings.XearlyTastyOutput), // createTestCase(settings.YallowOutlineFromTasty, settings.XallowOutlineFromTasty), - createTestCase(settings.YcheckInit , settings.WcheckInit), + createTestCase(settings.YcheckInit , settings.WsafeInit), createTestCase(settings.Xlint , settings.Wshadow), ).map: (deprecatedArgument, newSetting) => val args = List(deprecatedArgument) @@ -181,7 +181,7 @@ class ScalaSettingsTests: // createTestCase(settings.YjavaTasty , settings.XjavaTasty), // createTestCase(settings.YearlyTastyOutput , settings.XearlyTastyOutput, ":./"), // createTestCase(settings.YallowOutlineFromTasty, settings.XallowOutlineFromTasty), - createTestCase(settings.YcheckInit , settings.WcheckInit), + createTestCase(settings.YcheckInit , settings.WsafeInit), // createTestCase(settings.Xlint , settings.Wshadow, ":all"), // this setting is not going to be mapped to replacement. Read more in the commit message ).flatten.map: (deprecatedArgument, newSetting) => val args = List(deprecatedArgument) diff --git a/tests/warn/i21187.scala b/tests/warn/i21187.scala new file mode 100644 index 000000000000..d6ea131afdfb --- /dev/null +++ b/tests/warn/i21187.scala @@ -0,0 +1,22 @@ +//> using options -Wall + +def oops(msg: String) = sys.error(msg) + +class Zone +object Zone: + inline def apply[T](inline f: Zone ?=> T): T = f(using new Zone) + +inline def zone[A](inline f: Zone ?=> A) = Zone.apply(z => f(using z)) // warn suspicious contextualizing + +def zone_?[A](f: Zone ?=> A) = Zone.apply(z => f(using z)) // warn + +// intended +//inline def zone[A](inline f: Zone ?=> A): A = Zone.apply(z ?=> f(using z)) + +@main def hello = + // this swallows exceptions! + zone(oops("here")) // warn function value is not used + zone_?(oops("here")) // warn + + // this doesn't + Zone(oops("not here")) From a09d41dc2b036ad44819bad13c30838b765ea55b Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Tue, 2 Sep 2025 17:08:26 +0200 Subject: [PATCH 075/111] Compute the right span for abstract error messages (#23853) Fixes #22941. Done during the compiler spree of September 1st, 2025. Co-authored-by: HarrisL2 Co-authored-by: kalil0321 [Cherry-picked 13d4963607953ea84572cd23995d836d0f3371e8] --- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 11 ++++++++++- tests/neg/i10666.check | 2 +- tests/neg/i12828.check | 2 +- tests/neg/i13466.check | 2 +- tests/neg/i19731.check | 6 +++--- tests/neg/i21335.check | 4 ++-- tests/neg/i22941.check | 4 ++++ tests/neg/i22941.scala | 5 +++++ tests/neg/i9329.check | 2 +- tests/neg/targetName-override.check | 2 +- 10 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 tests/neg/i22941.check create mode 100644 tests/neg/i22941.scala diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index f1dce00c4fc2..ba472512a953 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -329,6 +329,15 @@ object RefChecks { val mixinOverrideErrors = new mutable.ListBuffer[MixinOverrideError]() + /** Returns a `SourcePosition` containing the full span (with the correct + * end) of the class name. */ + def clazzNamePos = + if clazz.name == tpnme.ANON_CLASS then + clazz.srcPos + else + val clazzNameEnd = clazz.srcPos.span.start + clazz.name.stripModuleClassSuffix.lastPart.length + clazz.srcPos.sourcePos.copy(span = clazz.srcPos.span.withEnd(clazzNameEnd)) + def printMixinOverrideErrors(): Unit = mixinOverrideErrors.toList match { case Nil => @@ -914,7 +923,7 @@ object RefChecks { checkNoAbstractDecls(clazz) if (abstractErrors.nonEmpty) - report.error(abstractErrorMessage, clazz.srcPos) + report.error(abstractErrorMessage, clazzNamePos) checkMemberTypesOK() checkCaseClassInheritanceInvariant() diff --git a/tests/neg/i10666.check b/tests/neg/i10666.check index a70aa9815dc5..491b88f1ffa5 100644 --- a/tests/neg/i10666.check +++ b/tests/neg/i10666.check @@ -1,6 +1,6 @@ -- Error: tests/neg/i10666.scala:8:6 ----------------------------------------------------------------------------------- 8 |class Bar extends Foo { // error - | ^ + | ^^^ | class Bar needs to be abstract, since def foo[T <: B](tx: T): Unit in trait Foo is not defined | (Note that | parameter T in def foo[T <: B](tx: T): Unit in trait Foo does not match diff --git a/tests/neg/i12828.check b/tests/neg/i12828.check index 070633fc35b3..e2a1cdb92dcd 100644 --- a/tests/neg/i12828.check +++ b/tests/neg/i12828.check @@ -1,6 +1,6 @@ -- Error: tests/neg/i12828.scala:7:7 ----------------------------------------------------------------------------------- 7 |object Baz extends Bar[Int] // error: not implemented - | ^ + | ^^^ | object creation impossible, since def foo(x: A): Unit in trait Foo is not defined | (Note that | parameter A in def foo(x: A): Unit in trait Foo does not match diff --git a/tests/neg/i13466.check b/tests/neg/i13466.check index a15ae059427f..ad097ddae96b 100644 --- a/tests/neg/i13466.check +++ b/tests/neg/i13466.check @@ -1,6 +1,6 @@ -- Error: tests/neg/i13466.scala:9:6 ----------------------------------------------------------------------------------- 9 |given none: SomeTrait[Finally] with {} // error - | ^ + | ^^^^ | object creation impossible, since: | it has 3 unimplemented members. | /** As seen from module class none$, the missing signatures are as follows. diff --git a/tests/neg/i19731.check b/tests/neg/i19731.check index eebfb924d199..5c6ef5246b1d 100644 --- a/tests/neg/i19731.check +++ b/tests/neg/i19731.check @@ -1,10 +1,10 @@ -- Error: tests/neg/i19731.scala:4:6 ----------------------------------------------------------------------------------- 4 |class F1 extends Foo: // error - | ^ + | ^^ | class F1 needs to be abstract, since def foo(): Unit in class F1 is not defined -- Error: tests/neg/i19731.scala:7:6 ----------------------------------------------------------------------------------- 7 |class F2 extends Foo: // error - | ^ + | ^^ | class F2 needs to be abstract, since: | it has 2 unimplemented members. | /** As seen from class F2, the missing signatures are as follows. @@ -14,7 +14,7 @@ | def foo(x: Int): Unit = ??? -- Error: tests/neg/i19731.scala:16:6 ---------------------------------------------------------------------------------- 16 |class B1 extends Bar: // error - | ^ + | ^^ | class B1 needs to be abstract, since: | it has 2 unimplemented members. | /** As seen from class B1, the missing signatures are as follows. diff --git a/tests/neg/i21335.check b/tests/neg/i21335.check index a7ee092eec0e..ae2e09df1f61 100644 --- a/tests/neg/i21335.check +++ b/tests/neg/i21335.check @@ -1,8 +1,8 @@ -- Error: tests/neg/i21335.scala:7:6 ----------------------------------------------------------------------------------- 7 |class Z1 extends Bar1 // error - | ^ + | ^^ | class Z1 needs to be abstract, since override def bar(): Bar1 in trait Bar1 is not defined -- Error: tests/neg/i21335.scala:12:6 ---------------------------------------------------------------------------------- 12 |class Z2 extends Bar2 // error - | ^ + | ^^ | class Z2 needs to be abstract, since def bar(): Bar2 in trait Bar2 is not defined diff --git a/tests/neg/i22941.check b/tests/neg/i22941.check new file mode 100644 index 000000000000..81edebd098d3 --- /dev/null +++ b/tests/neg/i22941.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i22941.scala:4:6 ----------------------------------------------------------------------------------- +4 |class Baz extends Foo: // error + | ^^^ + | class Baz needs to be abstract, since def bar: String in trait Foo is not defined diff --git a/tests/neg/i22941.scala b/tests/neg/i22941.scala new file mode 100644 index 000000000000..3e8eb39777ab --- /dev/null +++ b/tests/neg/i22941.scala @@ -0,0 +1,5 @@ +trait Foo: + def bar: String + +class Baz extends Foo: // error + val a = "hello" diff --git a/tests/neg/i9329.check b/tests/neg/i9329.check index 7e4968edf607..e604a1b22888 100644 --- a/tests/neg/i9329.check +++ b/tests/neg/i9329.check @@ -1,5 +1,5 @@ -- Error: tests/neg/i9329.scala:8:6 ------------------------------------------------------------------------------------ 8 |class GrandSon extends Son // error - | ^ + | ^^^^^^^^ |class GrandSon needs to be abstract, since def name: String in trait Parent is not defined |(The class implements abstract override def name: String in trait Son but that definition still needs an implementation) diff --git a/tests/neg/targetName-override.check b/tests/neg/targetName-override.check index 2d21e8cbfbd4..230b7fe77745 100644 --- a/tests/neg/targetName-override.check +++ b/tests/neg/targetName-override.check @@ -15,5 +15,5 @@ | method ++ of type (xs: Alpha[String]): Alpha[String] misses a target name annotation @targetName(append) -- Error: tests/neg/targetName-override.scala:14:6 --------------------------------------------------------------------- 14 |class Beta extends Alpha[String] { // error: needs to be abstract - | ^ + | ^^^^ |class Beta needs to be abstract, since there is a deferred declaration of method foo in class Alpha of type (x: String): String which is not implemented in a subclass From 7015a77391a826dd64a41565d9d751566918ecf1 Mon Sep 17 00:00:00 2001 From: Yoonjae Jeon Date: Thu, 4 Sep 2025 16:54:52 +0900 Subject: [PATCH 076/111] Prevent crash in SAM conversion with mismatched arity rename test file fix fix test address reviews [Cherry-picked 8adc2842c39cab02cc8fdb5e90e94ca19ea7467f] --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 14 +++++++++----- tests/neg/i123577.check | 7 +++++++ tests/neg/i123577.scala | 13 +++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 tests/neg/i123577.check create mode 100644 tests/neg/i123577.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 84e79db6be5e..904dfbdde13b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1683,11 +1683,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val restpe = mt.resultType match case mt: MethodType => mt.toFunctionType(isJava = samParent.classSymbol.is(JavaDefined)) case tp => tp - (formals, - if (mt.isResultDependent) - untpd.InLambdaTypeTree(isResult = true, (_, syms) => restpe.substParams(mt, syms.map(_.termRef))) - else - typeTree(restpe)) + val tree = + if (mt.isResultDependent) { + if (formals.length != defaultArity) + typeTree(WildcardType) + else + untpd.InLambdaTypeTree(isResult = true, (_, syms) => restpe.substParams(mt, syms.map(_.termRef))) + } else + typeTree(restpe) + (formals, tree) case _ => (List.tabulate(defaultArity)(alwaysWildcardType), untpd.TypeTree()) } diff --git a/tests/neg/i123577.check b/tests/neg/i123577.check new file mode 100644 index 000000000000..6d8f1403b819 --- /dev/null +++ b/tests/neg/i123577.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/i123577.scala:11:4 ------------------------------------------------------------ +11 | (msg: String) => ??? // error + | ^^^^^^^^^^^^^^^^^^^^ + | Found: String => Nothing + | Required: MillRpcChannel + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i123577.scala b/tests/neg/i123577.scala new file mode 100644 index 000000000000..fe69d3477757 --- /dev/null +++ b/tests/neg/i123577.scala @@ -0,0 +1,13 @@ +trait MillRpcMessage { + type Response +} + +trait MillRpcChannel { + def apply(requestId: Long, input: MillRpcMessage): input.Response +} + +object MillRpcChannel { + def createChannel: MillRpcChannel = { + (msg: String) => ??? // error + } +} From fba6b351a6e7130f3fef27f2f7790d6e5bc7df70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Per=C5=82akowski?= <17816164+Perl99@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:12:22 +0200 Subject: [PATCH 077/111] Improve symbol order in completions provided by the presentation compiler (#23888) Extension methods that are not in the same file are placed after all Product methods and even after extension methods like "ensuring". This PR penalizes the following methods, so that they are no longer at the top of the suggestions: - scala.Product.* - scala.Equals.* - scala.Predef.ArrowAssoc.* - scala.Predef.Ensuring.* - scala.Predef.StringFormat.* - scala.Predef.nn - scala.Predef.runtimeChecked Resolves https://github.com/scalameta/metals/issues/7642 [Cherry-picked 7d27633c72325ec8e436fba3674c88438d86e3b9] --- .../tools/pc/completions/Completions.scala | 23 ++++++++++- .../completion/CompletionExtensionSuite.scala | 41 +++++++++++++++++++ .../pc/tests/completion/CompletionSuite.scala | 18 ++++---- 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala index 80e551dacd31..b594c6fbdb2f 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -15,6 +15,7 @@ import dotty.tools.dotc.ast.untpd import dotty.tools.dotc.core.Comments.Comment import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Decorators.toTermName import dotty.tools.dotc.core.Denotations.SingleDenotation import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.Flags.* @@ -765,6 +766,13 @@ class Completions( ).flatMap(_.alternatives.map(_.symbol)).toSet ) + private lazy val EqualsClass: ClassSymbol = requiredClass("scala.Equals") + private lazy val ArrowAssocClass: ClassSymbol = requiredClass("scala.Predef.ArrowAssoc") + private lazy val EnsuringClass: ClassSymbol = requiredClass("scala.Predef.Ensuring") + private lazy val StringFormatClass: ClassSymbol = requiredClass("scala.Predef.StringFormat") + private lazy val nnMethod: Symbol = defn.ScalaPredefModule.info.member("nn".toTermName).symbol + private lazy val runtimeCheckedMethod: Symbol = defn.ScalaPredefModule.info.member("runtimeChecked".toTermName).symbol + private def isNotLocalForwardReference(sym: Symbol)(using Context): Boolean = !sym.isLocalToBlock || !sym.srcPos.isAfter(completionPos.originalCursorPosition) || @@ -783,6 +791,17 @@ class Completions( (sym.isField && !isJavaClass && !isModuleOrClass) || sym.getter != NoSymbol catch case _ => false + def isInheritedFromScalaLibrary(sym: Symbol) = + sym.owner == defn.AnyClass || + sym.owner == defn.ObjectClass || + sym.owner == defn.ProductClass || + sym.owner == EqualsClass || + sym.owner == ArrowAssocClass || + sym.owner == EnsuringClass || + sym.owner == StringFormatClass || + sym == nnMethod || + sym == runtimeCheckedMethod + def symbolRelevance(sym: Symbol): Int = var relevance = 0 // symbols defined in this file are more relevant @@ -800,7 +819,7 @@ class Completions( case _ => // symbols whose owner is a base class are less relevant - if sym.owner == defn.AnyClass || sym.owner == defn.ObjectClass + if isInheritedFromScalaLibrary(sym) then relevance |= IsInheritedBaseMethod // symbols not provided via an implicit are more relevant if sym.is(Implicit) || @@ -812,7 +831,7 @@ class Completions( // accessors of case class members are more relevant if !sym.is(CaseAccessor) then relevance |= IsNotCaseAccessor // public symbols are more relevant - if !sym.isPublic then relevance |= IsNotCaseAccessor + if !sym.isPublic then relevance |= IsNotPublic // synthetic symbols are less relevant (e.g. `copy` on case classes) if sym.is(Synthetic) && !sym.isAllOf(EnumCase) then relevance |= IsSynthetic diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala index b41084af4a8e..16535381165c 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala @@ -437,3 +437,44 @@ class CompletionExtensionSuite extends BaseCompletionSuite: |""".stripMargin, assertSingleItem = false ) + + @Test def `extension-for-case-class` = + check( + """|case class Bar(): + | def baz(): Unit = ??? + | + |object Bar: + | extension (f: Bar) + | def qux: Unit = ??? + | + |object Main: + | val _ = Bar().@@ + |""".stripMargin, + """|baz(): Unit + |copy(): Bar + |qux: Unit + |asInstanceOf[X0]: X0 + |canEqual(that: Any): Boolean + |equals(x$0: Any): Boolean + |getClass[X0 >: Bar](): Class[? <: X0] + |hashCode(): Int + |isInstanceOf[X0]: Boolean + |productArity: Int + |productElement(n: Int): Any + |productElementName(n: Int): String + |productElementNames: Iterator[String] + |productIterator: Iterator[Any] + |productPrefix: String + |synchronized[X0](x$0: X0): X0 + |toString(): String + |->[B](y: B): (Bar, B) + |ensuring(cond: Boolean): Bar + |ensuring(cond: Bar => Boolean): Bar + |ensuring(cond: Boolean, msg: => Any): Bar + |ensuring(cond: Bar => Boolean, msg: => Any): Bar + |nn: `?1`.type + |runtimeChecked: `?2`.type + |formatted(fmtstr: String): String + |→[B](y: B): (Bar, B) + | """.stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index 4dad18a78181..c02d217e45d9 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -109,18 +109,9 @@ class CompletionSuite extends BaseCompletionSuite: |tabulate[A](n: Int)(f: Int => A): List[A] |unapplySeq[A](x: List[A] @uncheckedVariance): UnapplySeqWrapper[A] |unfold[A, S](init: S)(f: S => Option[(A, S)]): List[A] - |->[B](y: B): (List.type, B) - |ensuring(cond: Boolean): List.type - |ensuring(cond: List.type => Boolean): List.type - |ensuring(cond: Boolean, msg: => Any): List.type - |ensuring(cond: List.type => Boolean, msg: => Any): List.type |fromSpecific(from: Any)(it: IterableOnce[Nothing]): List[Nothing] |fromSpecific(it: IterableOnce[Nothing]): List[Nothing] - |nn: List.type - |runtimeChecked scala.collection.immutable |toFactory(from: Any): Factory[Nothing, List[Nothing]] - |formatted(fmtstr: String): String - |→[B](y: B): (List.type, B) |iterableFactory[A]: Factory[A, List[A]] |asInstanceOf[X0]: X0 |equals(x$0: Any): Boolean @@ -129,6 +120,15 @@ class CompletionSuite extends BaseCompletionSuite: |isInstanceOf[X0]: Boolean |synchronized[X0](x$0: X0): X0 |toString(): String + |->[B](y: B): (List.type, B) + |ensuring(cond: Boolean): List.type + |ensuring(cond: List.type => Boolean): List.type + |ensuring(cond: Boolean, msg: => Any): List.type + |ensuring(cond: List.type => Boolean, msg: => Any): List.type + |nn: List.type + |runtimeChecked scala.collection.immutable + |formatted(fmtstr: String): String + |→[B](y: B): (List.type, B) |""".stripMargin ) From 2bbdff7f476da62af5bf4835fadb4b3e0a260df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zieli=C5=84ski=20Patryk?= <75637004+zielinsky@users.noreply.github.com> Date: Fri, 12 Sep 2025 15:07:02 +0200 Subject: [PATCH 078/111] Porting XRayModeHints (#23891) Porting https://github.com/scalameta/metals/pull/7639 Co-authored-by: Henry Parker [Cherry-picked 1315eda333b083e6f473cfcc31d87bdda52da991] --- .../dotty/tools/pc/PcInlayHintsProvider.scala | 84 ++++- .../tools/pc/base/BaseInlayHintsSuite.scala | 1 + .../pc/tests/inlayHints/InlayHintsSuite.scala | 334 ++++++++++++++++++ project/Build.scala | 2 +- 4 files changed, 410 insertions(+), 11 deletions(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala index 395548822d96..71aa1626bb18 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala @@ -54,7 +54,7 @@ class PcInlayHintsProvider( val pos = driver.sourcePosition(params) def provide(): List[InlayHint] = - val deepFolder = DeepFolder[InlayHints](collectDecorations) + val deepFolder = PcCollector.DeepFolderWithParent[InlayHints](collectDecorations) Interactive .pathTo(driver.openedTrees(uri), pos)(using driver.currentCtx) .headOption @@ -68,11 +68,23 @@ class PcInlayHintsProvider( def collectDecorations( inlayHints: InlayHints, tree: Tree, + parent: Option[Tree] ): InlayHints = + // XRay hints are not mutually exclusive with other hints, so they must be matched separately + val firstPassHints = (tree, parent) match { + case XRayModeHint(tpe, pos) => + inlayHints.addToBlock( + adjustPos(pos).toLsp, + LabelPart(": ") :: toLabelParts(tpe, pos), + InlayHintKind.Type + ) + case _ => inlayHints + } + tree match case ImplicitConversion(symbol, range) => val adjusted = adjustPos(range) - inlayHints + firstPassHints .add( adjusted.startPos.toLsp, labelPart(symbol, symbol.decodedName) :: LabelPart("(") :: Nil, @@ -84,17 +96,17 @@ class PcInlayHintsProvider( InlayHintKind.Parameter, ) case ImplicitParameters(trees, pos) => - inlayHints.add( + firstPassHints.add( adjustPos(pos).toLsp, ImplicitParameters.partsFromImplicitArgs(trees).map((label, maybeSymbol) => maybeSymbol match case Some(symbol) => labelPart(symbol, label) case None => LabelPart(label) ), - InlayHintKind.Parameter + InlayHintKind.Parameter, ) case ValueOf(label, pos) => - inlayHints.add( + firstPassHints.add( adjustPos(pos).toLsp, LabelPart("(") :: LabelPart(label) :: List(LabelPart(")")), InlayHintKind.Parameter, @@ -102,7 +114,7 @@ class PcInlayHintsProvider( case TypeParameters(tpes, pos, sel) if !syntheticTupleApply(sel) => val label = tpes.map(toLabelParts(_, pos)).separated("[", ", ", "]") - inlayHints.add( + firstPassHints.add( adjustPos(pos).endPos.toLsp, label, InlayHintKind.Type, @@ -110,9 +122,9 @@ class PcInlayHintsProvider( case InferredType(tpe, pos, defTree) if !isErrorTpe(tpe) => val adjustedPos = adjustPos(pos).endPos - if inlayHints.containsDef(adjustedPos.start) then inlayHints + if firstPassHints.containsDef(adjustedPos.start) then firstPassHints else - inlayHints + firstPassHints .add( adjustedPos.toLsp, LabelPart(": ") :: toLabelParts(tpe, pos), @@ -138,7 +150,7 @@ class PcInlayHintsProvider( pos.withStart(pos.start + 1) - args.foldLeft(inlayHints) { + args.foldLeft(firstPassHints) { case (ih, (name, pos0, isByName)) => val pos = adjustPos(pos0) val isBlock = isBlockParam(pos) @@ -158,7 +170,7 @@ class PcInlayHintsProvider( ) else ih } - case _ => inlayHints + case _ => firstPassHints private def toLabelParts( tpe: Type, @@ -491,3 +503,55 @@ object Parameters: case _ => None else None end Parameters + +object XRayModeHint: + def unapply(trees: (Tree, Option[Tree]))(using params: InlayHintsParams, ctx: Context): Option[(Type, SourcePosition)] = + if params.hintsXRayMode() then + val (tree, parent) = trees + val isParentApply = parent match + case Some(_: Apply) => true + case _ => false + val isParentOnSameLine = parent match + case Some(sel: Select) if sel.isForComprehensionMethod => false + case Some(par) if par.sourcePos.exists && par.sourcePos.line == tree.sourcePos.line => true + case _ => false + + tree match + /* + anotherTree + .innerSelect() + */ + case a @ Apply(inner, _) + if inner.sourcePos.exists && !isParentOnSameLine && !isParentApply && + endsInSimpleSelect(a) && isEndOfLine(tree.sourcePos) => + Some((a.tpe.widen.deepDealiasAndSimplify, tree.sourcePos)) + /* + innerTree + .select + */ + case select @ Select(innerTree, _) + if innerTree.sourcePos.exists && !isParentOnSameLine && !isParentApply && + isEndOfLine(tree.sourcePos) => + Some((select.tpe.widen.deepDealiasAndSimplify, tree.sourcePos)) + case _ => None + else None + + @tailrec + private def endsInSimpleSelect(ap: Tree)(using ctx: Context): Boolean = + ap match + case Apply(sel: Select, _) => + sel.name != nme.apply && !isInfix(sel) + case Apply(TypeApply(sel: Select, _), _) => + sel.name != nme.apply && !isInfix(sel) + case Apply(innerTree @ Apply(_, _), _) => + endsInSimpleSelect(innerTree) + case _ => false + + private def isEndOfLine(pos: SourcePosition): Boolean = + if pos.exists then + val source = pos.source + val end = pos.end + end >= source.length || source(end) == '\n' || source(end) == '\r' + else false + +end XRayModeHint diff --git a/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala b/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala index 32c6ad26c6a5..431e4908013a 100644 --- a/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala @@ -34,6 +34,7 @@ class BaseInlayHintsSuite extends BasePCSuite { inferredTypes = true, typeParameters = true, implicitParameters = true, + hintsXRayMode = true, byNameParameters = true, implicitConversions = true, namedParameters = true, diff --git a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala index d9c10080581f..daad8b974620 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala @@ -1334,4 +1334,338 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |""".stripMargin ) + @Test def `xray-single-chain-same-line` = + check( + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar.bar + |val thing2: Foo = foo.foo() + |} + |""".stripMargin, + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar.bar + |val thing2: Foo = foo.foo() + |} + |""".stripMargin + ) + + @Test def `xray-multi-chain-same-line` = + check( + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar.bar.bar + |val thing2: Foo = foo.foo().foo() + |} + |""".stripMargin, + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar.bar.bar + |val thing2: Foo = foo.foo().foo() + |} + |""".stripMargin + ) + + @Test def `xray-single-chain-new-line` = + check( + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar + | .bar + |val thing2: Foo = foo + | .foo() + |} + |""".stripMargin, + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar + | .bar + |val thing2: Foo = foo + | .foo() + |} + |""".stripMargin + ) + + @Test def `xray-simple-chain` = + check( + """|object Main{ + | trait Foo { + | def bar: Bar + | } + | + | trait Bar { + | def foo(): Foo + | } + | + |val foo: Foo = ??? + | + |val thingy: Bar = foo + | .bar + | .foo() + | .bar + |} + |""".stripMargin, + """|object Main{ + | trait Foo { + | def bar: Bar + | } + | + | trait Bar { + | def foo(): Foo + | } + | + |val foo: Foo = ??? + | + |val thingy: Bar = foo + | .bar/* : Bar<<(6:8)>>*/ + | .foo()/*: Foo<<(2:8)>>*/ + | .bar/* : Bar<<(6:8)>>*/ + |} + |""".stripMargin + ) + + @Test def `xray-long-chain` = + check( + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String*): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify + | .stringListify( + | "Hello", + | "World" + | ) + | .stringListify( + | "Hello", + | "World" + | ) + | .intify + | .intify + |} + |""".stripMargin, + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String*): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + | .stringListify( + | /*s = */"Hello", + | "World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .stringListify( + | /*s = */"Hello", + | "World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + |} + |""".stripMargin + ) + + @Test def `xray-long-chain-same-line` = + check( + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String*): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify + | .stringListify( + | "Hello", + | "World" + | ) + | .stringListify( + | "Hello", + | "World" + | ) + | .intify.intify + |} + |""".stripMargin, + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String*): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify/* : Foo<<(2:8)>>[Int<>]*/ + | .stringListify( + | /*s = */"Hello", + | "World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .stringListify( + | /*s = */"Hello", + | "World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .intify.intify/*: Foo<<(2:8)>>[Int<>]*/ + |} + |""".stripMargin + ) + + @Test def `xray-tikka-masala-curried` = + check( + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String)(s2: String): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify + | .stringListify( + | "Hello" + | )( + | "World" + | ) + | .stringListify( + | "Hello" + | )( + | "World" + | ) + | .intify + | .intify + |} + |""".stripMargin, + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String)(s2: String): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + | .stringListify( + | /*s = */"Hello" + | )( + | /*s2 = */"World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .stringListify( + | /*s = */"Hello" + | )( + | /*s2 = */"World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + |} + |""".stripMargin + ) + + @Test def `xray-for-comprehension` = + check( + """|object Main{ + |trait Foo[A]{ + | def flatMap[B](f: A => Foo[B]): Foo[B] + | def map[B](f: A => B): Foo[B] + | def bar(s: String): Foo[A] + |} + |val foo1: Foo[String] = ??? + |val foo2: Foo[Int] = ??? + |val result = for { + | foo <- foo1 + | bar <- foo2 + | .bar(s = foo) + | .bar(s = foo) + | .bar(s = foo) + |} yield bar + |} + |""".stripMargin, + """|object Main{ + |trait Foo[A]{ + | def flatMap[B](f: A => Foo[B]): Foo[B] + | def map[B](f: A => B): Foo[B] + | def bar(s: String): Foo[A] + |} + |val foo1: Foo[String] = ??? + |val foo2: Foo[Int] = ??? + |val result/*: Foo<<(2:6)>>[Int<>]*/ = for { + | foo <- foo1 + | bar <- foo2 + | .bar(s = foo)/*: Foo<<(2:6)>>[Int<>]*/ + | .bar(s = foo)/*: Foo<<(2:6)>>[Int<>]*/ + | .bar(s = foo)/*: Foo<<(2:6)>>[Int<>]*/ + |} yield bar + |} + |""".stripMargin + ) + } diff --git a/project/Build.scala b/project/Build.scala index ac09da0326da..0cae5da4a344 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2580,7 +2580,7 @@ object Build { BuildInfoPlugin.buildInfoDefaultSettings lazy val presentationCompilerSettings = { - val mtagsVersion = "1.5.3" + val mtagsVersion = "1.6.2" Seq( libraryDependencies ++= Seq( "org.lz4" % "lz4-java" % "1.8.0", From a67481cc96e6b2eac299ef7b1cfc5dfe13cc3266 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Wed, 17 Sep 2025 12:28:57 +0200 Subject: [PATCH 079/111] fix: make vals created in desugaring of n-ary lambdas non-synthetic (#23896) resolves: https://github.com/scala/scala3/issues/16110 resolves: https://github.com/scalameta/metals/issues/7594 [Cherry-picked 5f470ea60b75da792d86cb71db644035c04cec73] --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 1 - .../tools/pc/PcConvertToNamedLambdaParameters.scala | 2 +- .../dotty/tools/pc/tests/hover/HoverTermSuite.scala | 12 ++++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 398b3af607ef..2ed3415ebe70 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1968,7 +1968,6 @@ object desugar { ValDef(param.name, param.tpt, selector(idx)) .withSpan(param.span) .withAttachment(UntupledParam, ()) - .withFlags(Synthetic) } Function(param :: Nil, Block(vdefs, body)) } diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcConvertToNamedLambdaParameters.scala b/presentation-compiler/src/main/dotty/tools/pc/PcConvertToNamedLambdaParameters.scala index 2ca50107c36b..ca5f336b7bcd 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcConvertToNamedLambdaParameters.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcConvertToNamedLambdaParameters.scala @@ -120,7 +120,7 @@ object PcConvertToNamedLambdaParameters: } def isWildcardParam(param: tpd.ValDef)(using Context): Boolean = - param.name.toString.startsWith("_$") && param.symbol.is(Flags.Synthetic) + param.name.toString.startsWith("_$") def findParamReferencePosition(param: tpd.ValDef, lambda: tpd.Tree)(using Context): Option[SourcePosition] = var pos: Option[SourcePosition] = None diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala index f288215aa077..16d92e0e2282 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala @@ -6,6 +6,18 @@ import org.junit.Test class HoverTermSuite extends BaseHoverSuite: + @Test def `n-ary lamba` = + check( + """|object testRepor { + | val listOfTuples = List(1 -> 1, 2 -> 2, 3 -> 3) + | + | listOfTuples.map((k@@ey, value) => key + value) + |} + |""".stripMargin, + """|val key: Int + |""".stripMargin.hover + ) + @Test def `map` = check( """object a { From e066183dfaa0636291b5a5235b6e84e592fa6ec6 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Fri, 19 Sep 2025 10:06:14 +0200 Subject: [PATCH 080/111] Bump Scala CLI to v1.9.1 (was v1.9.0) (#23962) https://github.com/VirtusLab/scala-cli/releases/tag/v1.9.1 - https://github.com/VirtusLab/scala-cli/issues/3869 [Cherry-picked 8ee54c3f1e795831cd67daa4d2b196d0f66a998e] --- .github/workflows/lts-backport.yaml | 2 +- project/Build.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lts-backport.yaml b/.github/workflows/lts-backport.yaml index 6c8353435b50..9f5bb992440b 100644 --- a/.github/workflows/lts-backport.yaml +++ b/.github/workflows/lts-backport.yaml @@ -16,7 +16,7 @@ jobs: with: fetch-depth: 0 - uses: coursier/cache-action@v6 - - uses: VirtusLab/scala-cli-setup@v1.9.0 + - uses: VirtusLab/scala-cli-setup@v1.9.1 - run: scala-cli ./project/scripts/addToBackportingProject.scala -- ${{ github.sha }} env: GRAPHQL_API_TOKEN: ${{ secrets.GRAPHQL_API_TOKEN }} diff --git a/project/Build.scala b/project/Build.scala index 0cae5da4a344..c71de55a6724 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -137,7 +137,7 @@ object Build { val mimaPreviousLTSDottyVersion = "3.3.0" /** Version of Scala CLI to download */ - val scalaCliLauncherVersion = "1.9.0" + val scalaCliLauncherVersion = "1.9.1" /** Version of Coursier to download for initializing the local maven repo of Scala command */ val coursierJarVersion = "2.1.24" From 1ba26884c2b5e4e65335e4a13c265bab33e9596e Mon Sep 17 00:00:00 2001 From: Yoonjae Jeon Date: Fri, 19 Sep 2025 17:18:49 +0900 Subject: [PATCH 081/111] Call inhabited for AppliedType recursively [Cherry-picked cfab282e97dd66bba93049fbe665dd87c91b2689] --- .../tools/dotc/transform/patmat/Space.scala | 1 + tests/pos/i23734.scala | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/pos/i23734.scala diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 40054d73a357..4379002d2210 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -702,6 +702,7 @@ object SpaceEngine { case OrType(tp1, tp2) => inhabited(tp1) || inhabited(tp2) case tp: RefinedType => inhabited(tp.parent) case tp: TypeRef => !containsUninhabitedField(tp) && inhabited(tp.prefix) + case tp: AppliedType => !containsUninhabitedField(tp) && inhabited(tp.tycon) case _ => !containsUninhabitedField(tp) if inhabited(refined) then refined diff --git a/tests/pos/i23734.scala b/tests/pos/i23734.scala new file mode 100644 index 000000000000..308bfdae3fa9 --- /dev/null +++ b/tests/pos/i23734.scala @@ -0,0 +1,18 @@ +trait Nodes1 { + sealed trait B + final case class R1() extends B +} + +trait Nodes2 extends Nodes1 { + final case class R2[T]() extends B +} + + +object Impl1 extends Nodes1 + +object test2 { + val a: Impl1.B = ??? + a match { + case Impl1.R1() => ??? + } +} From f1d2e56d1e54973952284a2067b8fe0a0fb35482 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 22 Sep 2025 19:17:04 +0200 Subject: [PATCH 082/111] Make isExactlyNothing and isExactlyAny work for And/OrTypes Might fix #24013. [Cherry-picked 2fa9003a9747ced5661dc8159f88163ac1de5b31] --- compiler/src/dotty/tools/dotc/core/Types.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5b195802f969..a094bc77a854 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -244,6 +244,10 @@ object Types extends TypeUtils { def isExactlyNothing(using Context): Boolean = this match { case tp: TypeRef => tp.name == tpnme.Nothing && (tp.symbol eq defn.NothingClass) + case AndType(tp1, tp2) => + tp1.isExactlyNothing || tp2.isExactlyNothing + case OrType(tp1, tp2) => + tp1.isExactlyNothing && tp2.isExactlyNothing case _ => false } @@ -251,6 +255,10 @@ object Types extends TypeUtils { def isExactlyAny(using Context): Boolean = this match { case tp: TypeRef => tp.name == tpnme.Any && (tp.symbol eq defn.AnyClass) + case AndType(tp1, tp2) => + tp1.isExactlyAny && tp2.isExactlyAny + case OrType(tp1, tp2) => + tp1.isExactlyAny || tp2.isExactlyAny case _ => false } From d9658a2adb01bc5ee48bd26d046b76b081beab4e Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Wed, 24 Sep 2025 18:00:37 +0200 Subject: [PATCH 083/111] fix: make the pattern matcher understand this types [Cherry-picked 01eab3b9be7065cfc1fc241223b9b6ceea8dc9e6] --- .../dotty/tools/dotc/transform/PatternMatcher.scala | 8 +++++++- tests/run/i23875.check | 3 +++ tests/run/i23875.scala | 11 +++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 tests/run/i23875.check create mode 100644 tests/run/i23875.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index b4d766a1bd24..10aeea40b13b 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -478,7 +478,13 @@ object PatternMatcher { onSuccess ) } - case WildcardPattern() => + // When match against a `this.type` (say case a: this.type => ???), + // the typer will transform the pattern to a `Bind(..., Typed(Ident(a), ThisType(...)))`, + // then post typer will change all the `Ident` with a `ThisType` to a `This`. + // Therefore, after pattern matching, we will have the following tree `Bind(..., Typed(This(...), ThisType(...)))`. + // We handle now here the case were the pattern was transformed to a `This`, relying on the fact that the logic for + // `Typed` above will create the correct type test. + case WildcardPattern() | This(_) => onSuccess case SeqLiteral(pats, _) => matchElemsPlan(scrutinee, pats, exact = true, onSuccess) diff --git a/tests/run/i23875.check b/tests/run/i23875.check new file mode 100644 index 000000000000..36bc6136b8bf --- /dev/null +++ b/tests/run/i23875.check @@ -0,0 +1,3 @@ +true +false +false diff --git a/tests/run/i23875.scala b/tests/run/i23875.scala new file mode 100644 index 000000000000..6993fb39f50b --- /dev/null +++ b/tests/run/i23875.scala @@ -0,0 +1,11 @@ +object Foo { + override def equals(that : Any) = that match { + case _: this.type => true + case _ => false + } +} + +@main def Test = + println(Foo.equals(Foo)) + println(Foo.equals(new AnyRef {})) + println(Foo.equals(0)) From 7a9fa6e27e327de6995f29ec3d604d326644c05f Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 25 Sep 2025 13:52:33 +0200 Subject: [PATCH 084/111] Fix presentation compiler build for JDK 8 --- project/Build.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index c71de55a6724..6daa13933306 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -670,6 +670,13 @@ object Build { recur(lines, false) } + /** Replaces JDK 9+ methods with their JDK 8 alterantive */ + def replaceJDKIncompatibilities(lines: List[String]): List[String] = { + lines.map( + _.replaceAll("""(\").repeat\((\w+)\)""", """$1 * $2""") + ) + } + /** replace imports of `com.google.protobuf.*` with compiler implemented version */ def replaceProtobuf(lines: List[String]): List[String] = { def recur(ls: List[String]): List[String] = ls match { @@ -2623,7 +2630,7 @@ object Build { val mtagsSharedSources = (targetDir ** "*.scala").get.toSet mtagsSharedSources.foreach(f => { val lines = IO.readLines(f) - val substitutions = (replaceProtobuf(_)) andThen (insertUnsafeNullsImport(_)) + val substitutions = (replaceProtobuf(_)) andThen (insertUnsafeNullsImport(_)) andThen (replaceJDKIncompatibilities(_)) IO.writeLines(f, substitutions(lines)) }) mtagsSharedSources From 9c4dfd6e13460a41f129a566001997c29d275cbf Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 25 Sep 2025 11:31:12 +0200 Subject: [PATCH 085/111] Add changelog for 3.7.4-RC1 Signed-off-by: Wojciech Mazur --- changelogs/3.7.4-RC1.md | 128 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 changelogs/3.7.4-RC1.md diff --git a/changelogs/3.7.4-RC1.md b/changelogs/3.7.4-RC1.md new file mode 100644 index 000000000000..518addfa3400 --- /dev/null +++ b/changelogs/3.7.4-RC1.md @@ -0,0 +1,128 @@ +# Highlights of the release + +- Bump Scala CLI to v1.9.1 (was v1.9.0) [#23962](https://github.com/scala/scala3/pull/23962) +- Make coverage more similar to the one in Scala 2 [#23722](https://github.com/scala/scala3/pull/23722) + +# Other changes and fixes + +## Context Functions + +- Explain no expansion of ContextFunction0 [#23844](https://github.com/scala/scala3/pull/23844) + +## Experimental: Capture Checking + +- Fix #23737: Update superCallContext to include dummy capture parameters in scope [#23740](https://github.com/scala/scala3/pull/23740) +- Fix separation checking for function results [#23927](https://github.com/scala/scala3/pull/23927) +- Simple enhancement for pattern matching with capturing types [#23524](https://github.com/scala/scala3/pull/23524) +- Don't check bounds in match type cases at CC [#23738](https://github.com/scala/scala3/pull/23738) + +## Experimental: Explicit Nulls + +- Add warnings for inferred flexible types in public methods and fields [#23880](https://github.com/scala/scala3/pull/23880) + +## Exports + +- Refine isEffectivelyFinal to avoid no-owner crash [#23675](https://github.com/scala/scala3/pull/23675) + +## Implicits + +- Fix LiftToAnchors for higher-kinded type applications [#23672](https://github.com/scala/scala3/pull/23672) +- Fix implicit scope liftToAnchors for parameter lower bounds [#23679](https://github.com/scala/scala3/pull/23679) + +## Linting + +- Invent given pattern name in for comprehension [#23121](https://github.com/scala/scala3/pull/23121) +- Unused var message mentions unread or unset [#23719](https://github.com/scala/scala3/pull/23719) +- Lint function arrow intended context function [#23847](https://github.com/scala/scala3/pull/23847) + +## Match Types + +- Fix `derivesFrom` false negative in `provablyDisjointClasses` [#23834](https://github.com/scala/scala3/pull/23834) + + +## Parser + +- Improve message for nested package missing braces [#23816](https://github.com/scala/scala3/pull/23816) +- Fix: allow postfix setters under language.postfixOps [#23775](https://github.com/scala/scala3/pull/23775) + +## Pattern Matching + +- Fix: do not transform `Ident` to `This` in PostTyper anymore [#23899](https://github.com/scala/scala3/pull/23899) +- Call inhabited for AppliedType recursively [#23964](https://github.com/scala/scala3/pull/23964) +- Fix false unreachable case warning [#23800](https://github.com/scala/scala3/pull/23800) +- Add subtype-based fallback in inferPrefixMap and recalculate constraints after application [#23771](https://github.com/scala/scala3/pull/23771) + +## Presentation Compiler + +- Additional completions for using clause [#23647](https://github.com/scala/scala3/pull/23647) +- Completions - do not add `[]` for `... derives TC@@` [#23811](https://github.com/scala/scala3/pull/23811) +- Improve symbol order in completions provided by the presentation compiler [#23888](https://github.com/scala/scala3/pull/23888) +- Porting XRayModeHints [#23891](https://github.com/scala/scala3/pull/23891) +- Go to definition and hover for named args in pattern match [#23956](https://github.com/scala/scala3/pull/23956) + +## Reporting + +- Do not discard amended format when f-interpolator warns [#23697](https://github.com/scala/scala3/pull/23697) +- Mention named givens in double def explainer [#23833](https://github.com/scala/scala3/pull/23833) +- Compute the right span for abstract error messages [#23853](https://github.com/scala/scala3/pull/23853) +- Add quick fix to add .nn [#23598](https://github.com/scala/scala3/pull/23598) +- Add addendum to `private val` parameter variance error message [#23876](https://github.com/scala/scala3/pull/23876) + +## Scaladoc + +- Indicate optional parameters with `= ...` [#23676](https://github.com/scala/scala3/pull/23676) +- Scaladoc Support for Capture & Separation Checking [#23607](https://github.com/scala/scala3/pull/23607) +- Capture Calcuclus: don't eagerly drop caps on parameters [#23759](https://github.com/scala/scala3/pull/23759) + +## SemanticDB + +- Add context parameters to SemanticDB synthetics [#23381](https://github.com/scala/scala3/pull/23381) +- Include synthetic apply in semanticdb [#23629](https://github.com/scala/scala3/pull/23629) + +## Tuples + +- Fix: make vals created in desugaring of n-ary lambdas non-synthetic [#23896](https://github.com/scala/scala3/pull/23896) + +## Typer + +- Prevent crash in SAM conversion with mismatched arity [#23877](https://github.com/scala/scala3/pull/23877) +- Handle assertion error in TyperState [#23665](https://github.com/scala/scala3/pull/23665) +- Correctly require a `ClassTag` when building a multidimensional `Array` [#23902](https://github.com/scala/scala3/pull/23902) +- Make isExactlyNothing and isExactlyAny work for And/OrTypes [#24016](https://github.com/scala/scala3/pull/24016) + + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.3..3.7.4-RC1` these are: + +``` + 12 Som Snytt + 11 noti0na1 + 11 Wojciech Mazur + 6 Martin Odersky + 5 Eugene Flesselle + 4 Hamza Remmal + 4 Natsu Kagami + 4 Seyon Sivatharan + 3 Oliver Bračevac + 3 Yoonjae Jeon + 3 dependabot[bot] + 2 Jan Chyb + 2 Katarzyna Marek + 2 Matt Bovel + 1 HarrisL2 + 1 Kacper Korban + 1 Martin Duhem + 1 Paweł Perłakowski + 1 Piotr Chabelski + 1 Tomasz Godzik + 1 Vadim Chelyshov + 1 Yichen Xu + 1 Zieliński Patryk + 1 aherlihy + 1 katrinafyi + 1 vder + 1 zielinsky +``` \ No newline at end of file From 39ebf6bdbd97dcce2f115ecafd4dedcd6d80fbe7 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 25 Sep 2025 11:33:01 +0200 Subject: [PATCH 086/111] Release 3.7.4-RC1 Signed-off-by: Wojciech Mazur From d6cf0578a83cc0248fc69ff6fee7f1ce402aa0b5 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 21 Oct 2025 11:52:25 +0200 Subject: [PATCH 087/111] Deduplicate patches before applying them to sources (#24215) Fixes #24213 - deduplicate patches by content and span. Similar issues were spotted for code `using` rewrites resulting in invalid `using using` introduced by compiler. Issue might arise if some reason compiler typechecks the same code twice [Cherry-picked 2af74542b628e8e04d17e5d202f8258bd6417692] --- compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala | 4 ++-- compiler/test/dotty/tools/dotc/CompilationTests.scala | 1 + tests/rewrites/i24213.check | 5 +++++ tests/rewrites/i24213.scala | 5 +++++ 4 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 tests/rewrites/i24213.check create mode 100644 tests/rewrites/i24213.scala diff --git a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala index 272db26bdd3c..da6788901f0d 100644 --- a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala +++ b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala @@ -43,8 +43,8 @@ object Rewrites { pbuf.filterInPlace(x => !p(x.span)) def apply(cs: Array[Char]): Array[Char] = { - val delta = pbuf.map(_.delta).sum - val patches = pbuf.toList.sortBy(_.span.start) + val patches = pbuf.toList.distinct.sortBy(_.span.start) + val delta = patches.map(_.delta).sum if (patches.nonEmpty) patches.reduceLeft {(p1, p2) => assert(p1.span.end <= p2.span.start, s"overlapping patches in $source: $p1 and $p2") diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index de200a0f774a..cb8768a1cb81 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -89,6 +89,7 @@ class CompilationTests { compileFile("tests/rewrites/implicit-to-given.scala", defaultOptions.and("-rewrite", "-Yimplicit-to-given")), compileFile("tests/rewrites/i22792.scala", defaultOptions.and("-rewrite")), compileFile("tests/rewrites/i23449.scala", defaultOptions.and("-rewrite", "-source:3.4-migration")), + compileFile("tests/rewrites/i24213.scala", defaultOptions.and("-rewrite", "-source:3.4-migration")), ).checkRewrites() } diff --git a/tests/rewrites/i24213.check b/tests/rewrites/i24213.check new file mode 100644 index 000000000000..d06d2f63c7c2 --- /dev/null +++ b/tests/rewrites/i24213.check @@ -0,0 +1,5 @@ +def Test = + try () + catch { + case x: Throwable if x.getMessage `contains` "error" => ??? + } \ No newline at end of file diff --git a/tests/rewrites/i24213.scala b/tests/rewrites/i24213.scala new file mode 100644 index 000000000000..49148abace00 --- /dev/null +++ b/tests/rewrites/i24213.scala @@ -0,0 +1,5 @@ +def Test = + try () + catch { + case x: Throwable if x.getMessage contains "error" => ??? + } \ No newline at end of file From ff14efd396e7d24ec1b8970c312e996e761a0c6a Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Sun, 12 Oct 2025 23:41:48 +0200 Subject: [PATCH 088/111] bugfix: Fix possible SuspendException thrown when using macros Fixes https://github.com/scalameta/metals/issues/7872 [Cherry-picked 3dfa5ee919029b88ffcb2f366e6564327811d608] --- .../tools/dotc/interactive/Completion.scala | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 3d1c595877df..1ea19f88bb99 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -591,12 +591,15 @@ object Completion: case _: MethodOrPoly => tpe case _ => ExprType(tpe) + // Try added due to https://github.com/scalameta/metals/issues/7872 def tryApplyingReceiverToExtension(termRef: TermRef): Option[SingleDenotation] = - ctx.typer.tryApplyingExtensionMethod(termRef, qual) - .map { tree => - val tpe = asDefLikeType(tree.typeOpt.dealias) - termRef.denot.asSingleDenotation.mapInfo(_ => tpe) - } + try + ctx.typer.tryApplyingExtensionMethod(termRef, qual) + .map { tree => + val tpe = asDefLikeType(tree.typeOpt.dealias) + termRef.denot.asSingleDenotation.mapInfo(_ => tpe) + } + catch case NonFatal(_) => None def extractMemberExtensionMethods(types: Seq[Type]): Seq[(TermRef, TermName)] = object DenotWithMatchingName: @@ -694,13 +697,13 @@ object Completion: * @param qual The argument to which the implicit conversion should be applied. * @return The set of types after `qual` implicit conversion. */ - private def implicitConversionTargets(qual: tpd.Tree)(using Context): Set[SearchSuccess] = { + private def implicitConversionTargets(qual: tpd.Tree)(using Context): Set[SearchSuccess] = try { val typer = ctx.typer val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span, Set.empty).allImplicits interactiv.println(i"implicit conversion targets considered: ${conversions.toList}%, %") conversions - } + } catch case NonFatal(_) => Set.empty /** Filter for names that should appear when looking for completions. */ private object completionsFilter extends NameFilter: From 66b9b1b2089471da5c9e8bb4fb5a1cb56684d838 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Mon, 13 Oct 2025 14:09:30 +0200 Subject: [PATCH 089/111] chore: Add logget.warn in case where it breaks [Cherry-picked 078be56f0625c52a274161703b0b7d56eb257ad2] --- .../tools/dotc/interactive/Completion.scala | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 1ea19f88bb99..5cbe430b38e8 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -35,6 +35,8 @@ import dotty.tools.dotc.core.Constants import dotty.tools.dotc.core.TypeOps import dotty.tools.dotc.core.StdNames +import java.util.logging.Logger + /** * One of the results of a completion query. * @@ -48,6 +50,8 @@ case class Completion(label: String, description: String, symbols: List[Symbol]) object Completion: + private val logger = Logger.getLogger(this.getClass.getName) + def scopeContext(pos: SourcePosition)(using Context): CompletionResult = val tpdPath = Interactive.pathTo(ctx.compilationUnit.tpdTree, pos.span) val completionContext = Interactive.contextOfPath(tpdPath).withPhase(Phases.typerPhase) @@ -599,7 +603,11 @@ object Completion: val tpe = asDefLikeType(tree.typeOpt.dealias) termRef.denot.asSingleDenotation.mapInfo(_ => tpe) } - catch case NonFatal(_) => None + catch case NonFatal(ex) => + logger.warning( + s"Exception when trying to apply extension method:\n ${ex.getMessage()}\n${ex.getStackTrace().mkString("\n")}" + ) + None def extractMemberExtensionMethods(types: Seq[Type]): Seq[(TermRef, TermName)] = object DenotWithMatchingName: @@ -703,7 +711,11 @@ object Completion: interactiv.println(i"implicit conversion targets considered: ${conversions.toList}%, %") conversions - } catch case NonFatal(_) => Set.empty + } catch case NonFatal(ex) => + logger.warning( + s"Exception when searching for implicit conversions:\n ${ex.getMessage()}\n${ex.getStackTrace().mkString("\n")}" + ) + Set.empty /** Filter for names that should appear when looking for completions. */ private object completionsFilter extends NameFilter: From 768e52807309a45c92a20782a683575cf787b313 Mon Sep 17 00:00:00 2001 From: Florian3k Date: Sun, 12 Oct 2025 23:59:48 +0200 Subject: [PATCH 090/111] fix java record varargs field accessor [Cherry-picked 93f0e65b113e0850de46e3758b8c4e8290240fef] --- compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala | 7 ++++++- tests/pos-java16+/java-records/FromScala.scala | 4 ++++ tests/pos-java16+/java-records/R5.java | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/pos-java16+/java-records/R5.java diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index d1164d4742af..ef20eafe367f 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -879,9 +879,14 @@ object JavaParsers { fieldsByName -= name end for + // accessor for record's vararg field (T...) returns array type (T[]) + def adaptVarargsType(tpt: Tree) = tpt match + case PostfixOp(tpt2, Ident(tpnme.raw.STAR)) => arrayOf(tpt2) + case _ => tpt + val accessors = (for (name, (tpt, annots)) <- fieldsByName yield - DefDef(name, List(Nil), tpt, unimplementedExpr) + DefDef(name, List(Nil), adaptVarargsType(tpt), unimplementedExpr) .withMods(Modifiers(Flags.JavaDefined | Flags.Method | Flags.Synthetic)) ).toList diff --git a/tests/pos-java16+/java-records/FromScala.scala b/tests/pos-java16+/java-records/FromScala.scala index 8654f88f1835..14f54b6d3868 100644 --- a/tests/pos-java16+/java-records/FromScala.scala +++ b/tests/pos-java16+/java-records/FromScala.scala @@ -49,3 +49,7 @@ object C: def useR4: Unit = val r4 = R4(1) val i: Int = r4.t + + def useR5: Unit = + val r5 = R5("hi", 1, 2, 3) + val xs: Array[Int] = r5.values diff --git a/tests/pos-java16+/java-records/R5.java b/tests/pos-java16+/java-records/R5.java new file mode 100644 index 000000000000..ee63bda40198 --- /dev/null +++ b/tests/pos-java16+/java-records/R5.java @@ -0,0 +1 @@ +public record R5(String s, int... values) {} From 59dd7bf6a4820ebe186807bae4b72e728e13e5d0 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Sun, 12 Oct 2025 12:57:51 +0200 Subject: [PATCH 091/111] Fix completions for named tuples (#24169) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes scalameta/metals#7764 This fix was partially implemented during the [Scala Tooling Spree](https://scalameta.org/scala-tooling-spree/) Co-authored-by: Jakub Kozłowski [Cherry-picked 4494a766f9c0171390949b0463659ebd9776f5ca] --- .../tools/dotc/interactive/Completion.scala | 12 +++++++++- .../pc/tests/completion/CompletionSuite.scala | 24 ++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 5cbe430b38e8..7b2c5977fdbd 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -191,6 +191,15 @@ object Completion: Some(qual) case _ => None + private object NamedTupleSelection: + def unapply(path: List[tpd.Tree])(using Context): Option[tpd.Tree] = + path match + case (tpd.Apply(tpd.Apply(tpd.TypeApply(fun, _), List(qual)), _)) :: _ + if fun.symbol.exists && fun.symbol.name == nme.apply && + fun.symbol.owner.exists && fun.symbol.owner == defn.NamedTupleModule.moduleClass => + Some(qual) + case _ => None + /** Inspect `path` to determine the offset where the completion result should be inserted. */ def completionOffset(untpdPath: List[untpd.Tree]): Int = @@ -199,7 +208,7 @@ object Completion: case _ => 0 /** Handle case when cursor position is inside extension method construct. - * The extension method construct is then desugared into methods, and consturct parameters + * The extension method construct is then desugared into methods, and construct parameters * are no longer a part of a typed tree, but instead are prepended to method parameters. * * @param untpdPath The typed or untyped path to the tree that is being completed @@ -246,6 +255,7 @@ object Completion: completer.scopeCompletions.names ++ completer.selectionCompletions(qual) case tpd.Select(qual, _) :: _ => completer.selectionCompletions(qual) case (tree: tpd.ImportOrExport) :: _ => completer.directMemberCompletions(tree.expr) + case NamedTupleSelection(qual) => completer.selectionCompletions(qual) case _ => completer.scopeCompletions.names interactiv.println(i"""completion info with pos = $pos, diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index c02d217e45d9..08e1b293a4ce 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -2053,6 +2053,28 @@ class CompletionSuite extends BaseCompletionSuite: """.stripMargin, ) + @Test def `namedTuple completions-3` = + check( + """|import scala.NamedTuple.* + | + |val person = (name = "Jakub", city = "Wrocław") + | + |val n = person.@@name""".stripMargin, + "name: String", + filter = _ == "name: String" + ) + + @Test def `namedTuple completions-4` = + check( + """|import scala.NamedTuple.* + | + |val person = (name = "Jakub", city = "Wrocław") + | + |val n = person.n@@ame""".stripMargin, + "name: String", + filter = _ == "name: String" + ) + @Test def `Selectable with namedTuple Fields member` = check( """|import scala.NamedTuple.* @@ -2285,7 +2307,7 @@ class CompletionSuite extends BaseCompletionSuite: |""".stripMargin, "asTerm: Term" ) - + @Test def `derives-no-square-brackets` = check( """ From 1f54dd14ab9989b8ee5c9d6816dd19a15bf61385 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Wed, 8 Oct 2025 17:21:36 +0200 Subject: [PATCH 092/111] Fix parameter untupling for named tuples (#23440) [Cherry-picked 04a970b34a473d52d39ae4ce80044a706dfac108] --- .../dotty/tools/dotc/typer/Applications.scala | 22 ++++++++++--------- .../src/dotty/tools/dotc/typer/Typer.scala | 5 +++-- tests/run/i23440.scala | 12 ++++++++++ 3 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 tests/run/i23440.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 15e2bcb7427d..d8320ba1e389 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -140,16 +140,18 @@ object Applications { def tupleComponentTypes(tp: Type)(using Context): List[Type] = tp.widenExpr.dealias.normalized match - case tp: AppliedType => - if defn.isTupleClass(tp.tycon.typeSymbol) then - tp.args - else if tp.tycon.derivesFrom(defn.PairClass) then - val List(head, tail) = tp.args - head :: tupleComponentTypes(tail) - else + case defn.NamedTuple(_, vals) => + tupleComponentTypes(vals) + case tp: AppliedType => + if defn.isTupleClass(tp.tycon.typeSymbol) then + tp.args + else if tp.tycon.derivesFrom(defn.PairClass) then + val List(head, tail) = tp.args + head :: tupleComponentTypes(tail) + else + Nil + case _ => Nil - case _ => - Nil def productArity(tp: Type, errorPos: SrcPos = NoSourcePosition)(using Context): Int = if (defn.isProductSubType(tp)) productSelectorTypes(tp, errorPos).size else -1 @@ -2558,7 +2560,7 @@ trait Applications extends Compatibility { /** Is `formal` a product type which is elementwise compatible with `params`? */ def ptIsCorrectProduct(formal: Type, params: List[untpd.ValDef])(using Context): Boolean = isFullyDefined(formal, ForceDegree.flipBottom) - && defn.isProductSubType(formal) + && (defn.isProductSubType(formal) || formal.isNamedTupleType) && tupleComponentTypes(formal).corresponds(params): (argType, param) => param.tpt.isEmpty || argType.widenExpr <:< typedAheadType(param.tpt).tpe diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 904dfbdde13b..da37367c0f3a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1933,8 +1933,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val firstFormal = protoFormals.head.loBound if ptIsCorrectProduct(firstFormal, params) then val isGenericTuple = - firstFormal.derivesFrom(defn.TupleClass) - && !defn.isTupleClass(firstFormal.typeSymbol) + firstFormal.isNamedTupleType + || (firstFormal.derivesFrom(defn.TupleClass) + && !defn.isTupleClass(firstFormal.typeSymbol)) desugared = desugar.makeTupledFunction(params, fnBody, isGenericTuple) else if protoFormals.length > 1 && params.length == 1 then def isParamRef(scrut: untpd.Tree): Boolean = scrut match diff --git a/tests/run/i23440.scala b/tests/run/i23440.scala new file mode 100644 index 000000000000..58885bd3bf05 --- /dev/null +++ b/tests/run/i23440.scala @@ -0,0 +1,12 @@ +@main def Test: Unit = + List((42, "asdads")) match + case List((a, b)) => 1 + List((a = 42, b = "asdads")) match + case List((a, b)) => 1 + val tuple_list = List((42, "asdads")) + tuple_list.map((a, b) => println(s"$a $b")) + val named_tuple_list = List((a = 42, b = "asdads")) + named_tuple_list.foreach((a, b) => println(s"$a $b")) + named_tuple_list.foreach { case (a, b) => println(s"$a $b") } + val l = Seq.empty[(name: String, age: Int)] + l.map((name, i) => name + i) \ No newline at end of file From 6bd1bb2fd0e02e31e2c5c6add3c0ac211e9a7430 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 2 Oct 2025 08:42:42 -0600 Subject: [PATCH 093/111] . [Cherry-picked 8ac4db2c31419d77d507ffc164f29b452728782d] --- compiler/src/dotty/tools/dotc/ast/Positioned.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index 5b57733eaeb1..a56b02a13cfc 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -55,11 +55,11 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src def sourcePos(using Context): SourcePosition = val info = WrappedSourceFile.locateMagicHeader(source) info match - case HasHeader(offset, originalFile) => - if span.start >= offset then // This span is in user code + // This span is in user code + case HasHeader(offset, originalFile) + if span != NoSpan && span.start >= offset => originalFile.atSpan(span.shift(-offset)) - else // Otherwise, return the source position in the wrapper code - source.atSpan(span) + // Otherwise, return the source position in the wrapper code case _ => source.atSpan(span) /** This positioned item, widened to `SrcPos`. Used to make clear we only need the From 9a62360ec7e87ec87dfa83ecbb41f3597112b3ed Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 2 Oct 2025 08:46:08 -0600 Subject: [PATCH 094/111] . [Cherry-picked 55b470f8cb946bd7032b39ff51b8833d0e3bef64] --- compiler/src/dotty/tools/dotc/ast/Positioned.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index a56b02a13cfc..1396ee928e7f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -57,7 +57,7 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src info match // This span is in user code case HasHeader(offset, originalFile) - if span != NoSpan && span.start >= offset => + if span.exists && span.start >= offset => originalFile.atSpan(span.shift(-offset)) // Otherwise, return the source position in the wrapper code case _ => source.atSpan(span) From adf375730a14d21d71778b035483fb655617074c Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 3 Oct 2025 15:27:15 -0700 Subject: [PATCH 095/111] Always traverse Inlined.call in linter Use enclosingInlineds Register expansion def for lint if in user code Always resolve but restrict imports --- .../tools/dotc/transform/CheckUnused.scala | 80 ++++++++------- tests/warn/i15503a.scala | 13 ++- tests/warn/i24034/circe.scala | 97 +++++++++++++++++++ tests/warn/i24034/iron.scala | 30 ++++++ tests/warn/i24034/test.scala | 10 ++ 5 files changed, 182 insertions(+), 48 deletions(-) create mode 100644 tests/warn/i24034/circe.scala create mode 100644 tests/warn/i24034/iron.scala create mode 100644 tests/warn/i24034/test.scala diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 35310dd91ccc..11a4f33cb9ba 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -1,27 +1,26 @@ -package dotty.tools.dotc.transform - -import dotty.tools.dotc.ast.desugar.{ForArtifact, PatternVar} -import dotty.tools.dotc.ast.tpd.* -import dotty.tools.dotc.ast.untpd, untpd.ImportSelector -import dotty.tools.dotc.config.ScalaSettings -import dotty.tools.dotc.core.Contexts.* -import dotty.tools.dotc.core.Flags.* -import dotty.tools.dotc.core.Names.{Name, SimpleName, DerivedName, TermName, termName} -import dotty.tools.dotc.core.NameOps.{isAnonymousFunctionName, isReplWrapperName, setterName} -import dotty.tools.dotc.core.NameKinds.{ - BodyRetainerName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName} -import dotty.tools.dotc.core.StdNames.nme -import dotty.tools.dotc.core.Symbols.{ClassSymbol, NoSymbol, Symbol, defn, isDeprecated, requiredClass, requiredModule} -import dotty.tools.dotc.core.Types.* -import dotty.tools.dotc.report -import dotty.tools.dotc.reporting.{CodeAction, UnusedSymbol} -import dotty.tools.dotc.rewrites.Rewrites -import dotty.tools.dotc.transform.MegaPhase.MiniPhase -import dotty.tools.dotc.typer.{ImportInfo, Typer} -import dotty.tools.dotc.typer.Deriving.OriginalTypeClass -import dotty.tools.dotc.util.{Property, Spans, SrcPos}, Spans.Span -import dotty.tools.dotc.util.Chars.{isLineBreakChar, isWhitespace} -import dotty.tools.dotc.util.chaining.* +package dotty.tools.dotc +package transform + +import ast.*, desugar.{ForArtifact, PatternVar}, tpd.*, untpd.ImportSelector +import config.ScalaSettings +import core.*, Contexts.*, Flags.* +import Names.{Name, SimpleName, DerivedName, TermName, termName} +import NameKinds.{BodyRetainerName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName} +import NameOps.{isAnonymousFunctionName, isReplWrapperName, setterName} +import Scopes.newScope +import StdNames.nme +import Symbols.{ClassSymbol, NoSymbol, Symbol, defn, isDeprecated, requiredClass, requiredModule} +import Types.* +import reporting.{CodeAction, UnusedSymbol} +import rewrites.Rewrites + +import MegaPhase.MiniPhase +import typer.{ImportInfo, Typer} +import typer.Deriving.OriginalTypeClass +import typer.Implicits.{ContextualImplicits, RenamedImplicitRef} +import util.{Property, Spans, SrcPos}, Spans.Span +import util.Chars.{isLineBreakChar, isWhitespace} +import util.chaining.* import java.util.IdentityHashMap @@ -56,17 +55,16 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha if tree.symbol.exists then // if in an inline expansion, resolve at summonInline (synthetic pos) or in an enclosing call site val resolving = - refInfos.inlined.isEmpty - || tree.srcPos.isZeroExtentSynthetic - || refInfos.inlined.exists(_.sourcePos.contains(tree.srcPos.sourcePos)) - if resolving && !ignoreTree(tree) then + tree.srcPos.isUserCode + || tree.srcPos.isZeroExtentSynthetic // take as summonInline + if !ignoreTree(tree) then def loopOverPrefixes(prefix: Type, depth: Int): Unit = if depth < 10 && prefix.exists && !prefix.classSymbol.isEffectiveRoot then - resolveUsage(prefix.classSymbol, nme.NO_NAME, NoPrefix) + resolveUsage(prefix.classSymbol, nme.NO_NAME, NoPrefix, imports = resolving) loopOverPrefixes(prefix.normalizedPrefix, depth + 1) if tree.srcPos.isZeroExtentSynthetic then loopOverPrefixes(tree.typeOpt.normalizedPrefix, depth = 0) - resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject) + resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject, imports = resolving) else if tree.hasType then resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject) refInfos.isAssignment = false @@ -142,14 +140,8 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha case _ => tree - override def prepareForInlined(tree: Inlined)(using Context): Context = - refInfos.inlined.push(tree.call.srcPos) - ctx override def transformInlined(tree: Inlined)(using Context): tree.type = - //transformAllDeep(tree.expansion) // traverse expansion with nonempty inlined stack to avoid registering defs - val _ = refInfos.inlined.pop() - if !tree.call.isEmpty && phaseMode.eq(PhaseMode.Aggregate) then - transformAllDeep(tree.call) + transformAllDeep(tree.call) tree override def prepareForBind(tree: Bind)(using Context): Context = @@ -297,8 +289,11 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha * e.g., in `scala.Int`, `scala` is in scope for typer, but here we reverse-engineer the attribution. * For Select, lint does not look up `.scala` (so top-level syms look like magic) but records `scala.Int`. * For Ident, look-up finds the root import as usual. A competing import is OK because higher precedence. + * + * The `imports` flag is whether an identifier can mark an import as used: the flag is false + * for inlined code, except for `summonInline` (and related constructs) which are resolved at inlining. */ - def resolveUsage(sym0: Symbol, name: Name, prefix: Type)(using Context): Unit = + def resolveUsage(sym0: Symbol, name: Name, prefix: Type, imports: Boolean = true)(using Context): Unit = import PrecedenceLevels.* val sym = sym0.userSymbol @@ -392,7 +387,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha // record usage and possibly an import if !enclosed then refInfos.addRef(sym) - if candidate != NoContext && candidate.isImportContext && importer != null then + if imports && candidate != NoContext && candidate.isImportContext && importer != null then refInfos.sels.put(importer, ()) end resolveUsage end CheckUnused @@ -432,7 +427,7 @@ object CheckUnused: val nowarn = mutable.Set.empty[Symbol] // marked @nowarn val imps = new IdentityHashMap[Import, Unit] // imports val sels = new IdentityHashMap[ImportSelector, Unit] // matched selectors - def register(tree: Tree)(using Context): Unit = if inlined.isEmpty then + def register(tree: Tree)(using Context): Unit = if tree.srcPos.isUserCode then tree match case imp: Import => if inliners == 0 @@ -461,7 +456,6 @@ object CheckUnused: if tree.symbol ne NoSymbol then defs.addOne((tree.symbol, tree.srcPos)) // TODO is this a code path - val inlined = Stack.empty[SrcPos] // enclosing call.srcPos of inlined code (expansions) var inliners = 0 // depth of inline def (not inlined yet) // instead of refs.addOne, use addRef to distinguish a read from a write to var @@ -999,6 +993,10 @@ object CheckUnused: extension (pos: SrcPos) def isZeroExtentSynthetic: Boolean = pos.span.isSynthetic && pos.span.isZeroExtent def isSynthetic: Boolean = pos.span.isSynthetic && pos.span.exists + def isUserCode(using Context): Boolean = + val inlineds = enclosingInlineds // per current context + inlineds.isEmpty + || inlineds.last.srcPos.sourcePos.contains(pos.sourcePos) extension [A <: AnyRef](arr: Array[A]) // returns `until` if not satisfied diff --git a/tests/warn/i15503a.scala b/tests/warn/i15503a.scala index 8fc97888b584..78234e6e777b 100644 --- a/tests/warn/i15503a.scala +++ b/tests/warn/i15503a.scala @@ -39,6 +39,10 @@ object FooGiven: val foo = summon[Int] +object SomeGivenImports: + given Int = 0 + given String = "foo" + /** * Import used as type name are considered * as used. @@ -69,7 +73,7 @@ object InlineChecks: object InlinedBar: import collection.mutable.Set // warn (don't be fooled by inline expansion) import collection.mutable.Map // warn - val a = InlineFoo.getSet + val a = InlineFoo.getSet // expansion is attributed mutable.Set.apply(1) object MacroChecks: object StringInterpol: @@ -91,12 +95,7 @@ object IgnoreExclusion: def check = val a = Set(1) val b = Map(1 -> 2) -/** - * Some given values for the test - */ -object SomeGivenImports: - given Int = 0 - given String = "foo" + def c = Seq(42) /* BEGIN : Check on packages*/ package nestedpackageimport: diff --git a/tests/warn/i24034/circe.scala b/tests/warn/i24034/circe.scala new file mode 100644 index 000000000000..5d2e646f67b2 --- /dev/null +++ b/tests/warn/i24034/circe.scala @@ -0,0 +1,97 @@ + +// circe.scala + +package io.circe + +import scala.compiletime.* +import scala.deriving.Mirror +import scala.quoted.* + +trait Encoder[A]: + def encode(value: A): String + +object Encoder: + trait AsObject[A] extends Encoder[A] + given Encoder[String] = ??? + +trait Configuration +object Configuration: + val default: Configuration = ??? + +object Default: + given [A]: Default[A] = ??? +trait Default[T] + +trait Codec[A] extends Encoder[A] +object Codec: + trait AsObject[A] extends Encoder.AsObject[A] + object AsObject: + inline final def derived[A](using inline A: Mirror.Of[A]): Codec.AsObject[A] = + ConfiguredCodec.derived[A](using Configuration.default) + inline final def derivedConfigured[A](using + inline A: Mirror.Of[A], + inline conf: Configuration + ): Codec.AsObject[A] = ConfiguredCodec.derived[A] + +trait ConfiguredEncoder[A](using conf: Configuration) extends Encoder.AsObject[A] +trait ConfiguredCodec[A] extends Codec.AsObject[A], ConfiguredEncoder[A] +object ConfiguredCodec: + inline final def derive[A: Mirror.Of](): ConfiguredCodec[A] = + derived[A](using Configuration.default) + inline final def derived[A](using + conf: Configuration, + inline mirror: Mirror.Of[A] + ): ConfiguredCodec[A] = ${ derivedImpl[A]('conf, 'mirror) } + def ofProduct[A]( + encoders: => List[Encoder[?]] + )(using Configuration, Default[A]): ConfiguredCodec[A] = ??? + def derivedImpl[A: Type](conf: Expr[Configuration], mirror: Expr[Mirror.Of[A]])(using + q: Quotes + ): Expr[ConfiguredCodec[A]] = { + mirror match { + case '{ + ${ _ }: Mirror.ProductOf[A] { + type MirroredLabel = l + type MirroredElemLabels = el + type MirroredElemTypes = et + } + } => + '{ + ConfiguredCodec.ofProduct[A]( + derivation.summonEncoders[et & Tuple](false)(using $conf) + )(using $conf) + } + } + } + +object derivation: + sealed trait Inliner[A, Arg]: + inline def apply[T](inline arg: Arg): A + + class EncoderNotDeriveSum(using config: Configuration) extends Inliner[Encoder[?], Unit]: + inline def apply[T](inline arg: Unit): Encoder[?] = summonEncoder[T](false) + + inline final def loopUnrolled[A, Arg, T <: Tuple](f: Inliner[A, Arg], inline arg: Arg): List[A] = + inline erasedValue[T] match + case _: EmptyTuple => Nil + case _: (h *: ts) => f[h](arg) :: loopUnrolled[A, Arg, ts](f, arg) + + inline def loopUnrolledNoArg[A, T <: Tuple](f: Inliner[A, Unit]): List[A] = + loopUnrolled[A, Unit, T](f, ()) + + inline final def summonEncoders[T <: Tuple](inline derivingForSum: Boolean)(using + Configuration + ): List[Encoder[?]] = + loopUnrolledNoArg[Encoder[?], T]( + inline if (derivingForSum) compiletime.error("unreachable") + else new EncoderNotDeriveSum + ) + + private[circe] inline final def summonEncoder[A]( + inline derivingForSum: Boolean + )(using Configuration): Encoder[A] = summonFrom { + case encodeA: Encoder[A] => encodeA + case _: Mirror.Of[A] => + inline if (derivingForSum) compiletime.error("unreachable") + else error("Failed to find an instance of Encoder[]") + } diff --git a/tests/warn/i24034/iron.scala b/tests/warn/i24034/iron.scala new file mode 100644 index 000000000000..17cb26bf6408 --- /dev/null +++ b/tests/warn/i24034/iron.scala @@ -0,0 +1,30 @@ + +// iron.scala + +package iron + +import io.circe.* + +opaque type IronType[A, C] <: A = A +type :|[A, C] = IronType[A, C] +trait Constraint[A, C] + +package constraint: + object string: + final class StartWith[V <: String] + object StartWith: + inline given [V <: String]: Constraint[String, StartWith[V]] = ??? + +object circe: + inline given XXX[A, B](using inline encoder: Encoder[A]): Encoder[A :| B] = ??? + inline given YYY[A, B](using inline encoder: Encoder[A], dummy: scala.util.NotGiven[DummyImplicit]): Encoder[A :| B] = ??? + // inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Encoder[mirror.IronType]): Encoder[T] = ??? + +// trait RefinedTypeOps[A, C, T]: +// inline given RefinedTypeOps.Mirror[T] = ??? +// object RefinedTypeOps: +// trait Mirror[T]: +// type BaseType +// type ConstraintType +// type IronType = BaseType :| ConstraintType + diff --git a/tests/warn/i24034/test.scala b/tests/warn/i24034/test.scala new file mode 100644 index 000000000000..f87be8da2c36 --- /dev/null +++ b/tests/warn/i24034/test.scala @@ -0,0 +1,10 @@ +//> using options -Wunused:all + +import io.circe.Codec + +import iron.:| +import iron.circe.given +import iron.constraint.string.StartWith + +case class Alien(name: String :| StartWith["alen"]) derives Codec.AsObject + From b650ed678ad66bf8652d2c004d71671226cb2799 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 22 Oct 2025 14:37:44 +0200 Subject: [PATCH 096/111] Always resolve but restrict imports [Cherry-picked eb67a9c7172e9f4fc366cf6e9c69216893f3c05f][modified] From ff3035fe7769007cd2464c4a699a672dfd3a4507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Tue, 23 Sep 2025 17:56:30 +0200 Subject: [PATCH 097/111] scaladoc: Fix rendering of function-type aliases Fixes #23456 We now eagerly dealias (modulo opaque types) type aliases that are known to be concrete function types. [Cherry-picked 43d9285d3293ec6e2f4ec4bd87459455e0101057] --- scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 482807010d40..f347513120b8 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -274,7 +274,12 @@ trait TypesSupport: ++ inParens(inner(rhs, skipThisTypePrefix), shouldWrapInParens(rhs, t, false)) case t @ AppliedType(tpe, args) if t.isFunctionType => - functionType(tpe, args, skipThisTypePrefix) + val dealiased = t.dealiasKeepOpaques + if t == dealiased then + functionType(tpe, args, skipThisTypePrefix) + else + val AppliedType(tpe, args) = dealiased.asInstanceOf[AppliedType] + functionType(tpe, args, skipThisTypePrefix) case t @ AppliedType(tpe, typeList) => inner(tpe, skipThisTypePrefix) ++ plain("[").l ++ commas(typeList.map { t => t match From 067e096bf9dacea5742ddad3324b22db51923e3b Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 15 Oct 2025 10:36:35 -0700 Subject: [PATCH 098/111] Ignore compiletime.testing for now Import of compiletime.testing nullifies unused lint --- .../tools/dotc/transform/CheckUnused.scala | 5 +++ tests/warn/i21805.scala | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/warn/i21805.scala diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 11a4f33cb9ba..2e870f447893 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -433,6 +433,7 @@ object CheckUnused: if inliners == 0 && languageImport(imp.expr).isEmpty && !imp.isGeneratedByEnum + && !imp.isCompiletimeTesting && !ctx.owner.name.isReplWrapperName then imps.put(imp, ()) @@ -990,6 +991,10 @@ object CheckUnused: then imp.expr.tpe.allMembers.exists(_.symbol.isCanEqual) else imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isCanEqual) + /** No mechanism for detection yet. */ + def isCompiletimeTesting: Boolean = + imp.expr.symbol == defn.CompiletimeTestingPackage//.moduleClass + extension (pos: SrcPos) def isZeroExtentSynthetic: Boolean = pos.span.isSynthetic && pos.span.isZeroExtent def isSynthetic: Boolean = pos.span.isSynthetic && pos.span.exists diff --git a/tests/warn/i21805.scala b/tests/warn/i21805.scala new file mode 100644 index 000000000000..4fdcae85910d --- /dev/null +++ b/tests/warn/i21805.scala @@ -0,0 +1,31 @@ +//> using options -Wunused:imports + +def i23967: Boolean = { + //import scala.compiletime.testing.typeCheckErrors + import scala.compiletime.testing.* // nowarn + typeChecks("2 + 2") +} + +package p: + val code = """"hello, world"""" +package c: + class C(i: Int) + +package q: + import c.* // warn should be nowarn + import p.* // warn should be nowarn + import scala.compiletime.testing.* + def test() = typeCheckErrors("""println(C("hello, world"))""") + def ok() = typeChecks("println(code)") + inline def f(inline i: Int) = 42 + i + +package i23967b: + package ok: + import scala.compiletime.testing.* // nowarn + def test() = typeChecks("42 + 27") + package nok: + import scala.compiletime.testing.typeChecks // nowarn + def test() = typeChecks("42 + 27") + +@main def Test = println: + q.f(27) From 18f1a570a0e9c9b2220da238183ca4f422d63bc7 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 22 Oct 2025 14:42:47 +0200 Subject: [PATCH 099/111] Import of compiletime.testing nullifies unused lint [Cherry-picked b3256812e6da4cba643a2685ce606ded40374acf][modified] From 606fc4b9e35389512ecb4f7bbca302c46951e419 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 22 Oct 2025 19:55:42 +0200 Subject: [PATCH 100/111] Add changelog for 3.7.4-RC2 Signed-off-by: Wojciech Mazur --- changelogs/3.7.4-RC2.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 changelogs/3.7.4-RC2.md diff --git a/changelogs/3.7.4-RC2.md b/changelogs/3.7.4-RC2.md new file mode 100644 index 000000000000..9627af49dd99 --- /dev/null +++ b/changelogs/3.7.4-RC2.md @@ -0,0 +1,28 @@ +# Backported chnages + +- Always traverse Inlined.call in linter [#24043](https://github.com/scala/scala3/pull/24043) +- Deduplicate patches before applying them to sources [#24215](https://github.com/scala/scala3/pull/24215) +- Fix compiler crash with `-Ymagic-offset-header` [#24124](https://github.com/scala/scala3/pull/24124) +- Fix completions for named tuples [#24169](https://github.com/scala/scala3/pull/24169) +- Fix java record varargs field accessor [#24172](https://github.com/scala/scala3/pull/24172) +- Fix parameter untupling for named tuples (#23440) [#24152](https://github.com/scala/scala3/pull/24152) +- Fix possible SuspendException thrown when using macros [#24174](https://github.com/scala/scala3/pull/24174) +- Fix rendering of function-type aliases [#24042](https://github.com/scala/scala3/pull/24042) +- Ignore warnings when compiletime.testing is imported [#24036](https://github.com/scala/scala3/pull/24036) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.4-RC1..3.7.4-RC2` these are: + +``` + 5 Wojciech Mazur + 2 Li Haoyi + 2 Som Snytt + 2 Tomasz Godzik + 1 Florian3k + 1 Kacper Korban + 1 Oliver Bračevac + 1 aherlihy +``` From 9f5ca71ae493f45c9cf3ec515f7d08b495522863 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 22 Oct 2025 19:57:48 +0200 Subject: [PATCH 101/111] Release Scala 3.7.4-RC2 Signed-off-by: Wojciech Mazur --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 6daa13933306..51b93ee6789c 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -72,7 +72,7 @@ object Build { * During release candidate cycle incremented by the release officer before publishing a subsequent RC version; * During final, stable release is set exactly to `developedVersion`. */ - val baseVersion = s"$developedVersion-RC1" + val baseVersion = s"$developedVersion-RC2" /** The version of TASTY that should be emitted, checked in runtime test * For defails on how TASTY version should be set see related discussions: From c2ff5d19d8ac1c123ded829c05bb1bffd2ac1776 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Mon, 27 Oct 2025 13:40:56 +0100 Subject: [PATCH 102/111] Update `MainGenericRunner` deprecation message for removal in 3.8.0 --- compiler/src/dotty/tools/MainGenericRunner.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/MainGenericRunner.scala b/compiler/src/dotty/tools/MainGenericRunner.scala index 9ff96f812bca..fcb69c59d840 100644 --- a/compiler/src/dotty/tools/MainGenericRunner.scala +++ b/compiler/src/dotty/tools/MainGenericRunner.scala @@ -273,12 +273,11 @@ object MainGenericRunner { val silenced = sys.props.get("scala.use_legacy_launcher") == Some("true") if !silenced then - Console.err.println(s"[warning] MainGenericRunner class is deprecated since Scala 3.5.0, and Scala CLI features will not work.") - Console.err.println(s"[warning] Please be sure to update to the Scala CLI launcher to use the new features.") + Console.err.println(s"[warning] The MainGenericRunner class and 'legacy_scala' command have been deprecated for removal in Scala 3.8.0.") + Console.err.println(s"[warning] Please be sure to migrate to the scala command (Scala CLI).") if ranByCoursierBootstrap then Console.err.println(s"[warning] It appears that your Coursier-based Scala installation is misconfigured.") Console.err.println(s"[warning] To update to the new Scala CLI runner, please update (coursier, cs) commands first before re-installing scala.") - Console.err.println(s"[warning] Check the Scala 3.5.0 release notes to troubleshoot your installation.") run(settings) match From 843a6b0f5602a1f9c4f63cec1f49790bfb5d9f4b Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Mon, 27 Oct 2025 13:50:24 +0100 Subject: [PATCH 103/111] Deprecate the `-run` and `-repl` command line options for removal in 3.8.0 --- compiler/src/dotty/tools/MainGenericCompiler.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/src/dotty/tools/MainGenericCompiler.scala b/compiler/src/dotty/tools/MainGenericCompiler.scala index 98bd9078c397..c84052df75de 100644 --- a/compiler/src/dotty/tools/MainGenericCompiler.scala +++ b/compiler/src/dotty/tools/MainGenericCompiler.scala @@ -100,6 +100,8 @@ object MainGenericCompiler { case ("-q" | "-quiet") :: tail => process(tail, settings.withQuiet) case "-repl" :: tail => + Console.err.println(s"[warning] The -repl command line option has been deprecated removal in Scala 3.8.0.") + Console.err.println(s"[warning] Please use the 'scala repl' command (Scala CLI) to run the REPL.") process(tail, settings.withCompileMode(CompileMode.Repl)) case "-script" :: targetScript :: tail => process(Nil, settings @@ -114,6 +116,8 @@ object MainGenericCompiler { case "-print-tasty" :: tail => process(tail, settings.withCompileMode(CompileMode.PrintTasty)) case "-run" :: tail => + Console.err.println(s"[warning] The -run command line option has been deprecated removal in Scala 3.8.0.") + Console.err.println(s"[warning] Please use the 'scala' command (Scala CLI) instead.") process(tail, settings.withCompileMode(CompileMode.Run)) case "-colors" :: tail => process(tail, settings.withColors) From 30127f01843210f768d8a67fcbfe78de172be106 Mon Sep 17 00:00:00 2001 From: som-snytt Date: Tue, 28 Oct 2025 03:36:32 -0700 Subject: [PATCH 104/111] Lint avoids revisiting Inlined.call (#24277) Fixes #24266 Restores empty check and "already seen" check for Inlined.call sites. [Cherry-picked b14afef63d0abe10ba036c28fbf12ebd5ca12772] --- compiler/src/dotty/tools/dotc/transform/CheckUnused.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 2e870f447893..b27505de4349 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -141,7 +141,10 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha tree override def transformInlined(tree: Inlined)(using Context): tree.type = - transformAllDeep(tree.call) + if !tree.call.isEmpty then + if !refInfos.calls.containsKey(tree.call) then + refInfos.calls.put(tree.call, ()) + transformAllDeep(tree.call) tree override def prepareForBind(tree: Bind)(using Context): Context = @@ -425,6 +428,7 @@ object CheckUnused: val asss = mutable.Set.empty[Symbol] // targets of assignment val skip = mutable.Set.empty[Symbol] // methods to skip (don't warn about their params) val nowarn = mutable.Set.empty[Symbol] // marked @nowarn + val calls = new IdentityHashMap[Tree, Unit] // inlined call already seen val imps = new IdentityHashMap[Import, Unit] // imports val sels = new IdentityHashMap[ImportSelector, Unit] // matched selectors def register(tree: Tree)(using Context): Unit = if tree.srcPos.isUserCode then From d209f5af2bf83cd512dfcce859564090cb739d0d Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 28 Oct 2025 12:02:04 +0100 Subject: [PATCH 105/111] Register no elements for lint after inlining (#24279) Fixes #24265 The lint previously registered imports only after typer, but no new symbols after inlining should be subject to the unused lint. [Cherry-picked 54a48632d46a43abb7d11feec59b78081cd58e56][modified] --- .../tools/dotc/transform/CheckUnused.scala | 21 ++++++++++++------- tests/warn/i24265.scala | 10 +++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 tests/warn/i24265.scala diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index b27505de4349..7be02b6b19ec 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -3,7 +3,7 @@ package transform import ast.*, desugar.{ForArtifact, PatternVar}, tpd.*, untpd.ImportSelector import config.ScalaSettings -import core.*, Contexts.*, Flags.* +import core.*, Contexts.*, Decorators.*, Flags.* import Names.{Name, SimpleName, DerivedName, TermName, termName} import NameKinds.{BodyRetainerName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName} import NameOps.{isAnonymousFunctionName, isReplWrapperName, setterName} @@ -148,7 +148,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha tree override def prepareForBind(tree: Bind)(using Context): Context = - refInfos.register(tree) + register(tree) ctx /* cf QuotePattern override def transformBind(tree: Bind)(using Context): tree.type = @@ -166,7 +166,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha override def prepareForValDef(tree: ValDef)(using Context): Context = if !tree.symbol.is(Deferred) && tree.rhs.symbol != defn.Predef_undefined then - refInfos.register(tree) + register(tree) relax(tree.rhs, tree.tpt.tpe) ctx override def transformValDef(tree: ValDef)(using Context): tree.type = @@ -190,7 +190,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha if tree.symbol.is(Inline) then refInfos.inliners += 1 else if !tree.symbol.is(Deferred) && tree.rhs.symbol != defn.Predef_undefined then - refInfos.register(tree) + register(tree) relax(tree.rhs, tree.tpt.tpe) ctx override def transformDefDef(tree: DefDef)(using Context): tree.type = @@ -204,14 +204,13 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha override def transformTypeDef(tree: TypeDef)(using Context): tree.type = traverseAnnotations(tree.symbol) if !tree.symbol.is(Param) then // type parameter to do? - refInfos.register(tree) + register(tree) tree override def transformOther(tree: Tree)(using Context): tree.type = tree match case imp: Import => - if phaseMode eq PhaseMode.Aggregate then - refInfos.register(imp) + register(imp) transformAllDeep(imp.expr) for selector <- imp.selectors do if selector.isGiven then @@ -353,7 +352,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha while !done && ctxs.hasNext do val cur = ctxs.next() if cur.owner.userSymbol == sym && !sym.is(Package) then - enclosed = true // found enclosing definition, don't register the reference + enclosed = true // found enclosing definition, don't record the reference if isLocal then if cur.owner eq sym.owner then done = true // for local def, just checking that it is not enclosing @@ -393,6 +392,12 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha if imports && candidate != NoContext && candidate.isImportContext && importer != null then refInfos.sels.put(importer, ()) end resolveUsage + + /** Register new element for warnings only at typer */ + def register(tree: Tree)(using Context): Unit = + if phaseMode eq PhaseMode.Aggregate then + refInfos.register(tree) + end CheckUnused object CheckUnused: diff --git a/tests/warn/i24265.scala b/tests/warn/i24265.scala new file mode 100644 index 000000000000..9c5f189b26d6 --- /dev/null +++ b/tests/warn/i24265.scala @@ -0,0 +1,10 @@ +//> using options -Wall -Werror + +object test { + inline def f(testFun: => Any) = testFun + + f { + val i = 1 + summon[i.type <:< Int] + } +} From 6a1e1a5db25be68f52fa36a615a8fa943b3181df Mon Sep 17 00:00:00 2001 From: som-snytt Date: Tue, 28 Oct 2025 01:11:47 -0700 Subject: [PATCH 106/111] Use enclosing enclosingInlineds for empty call (#24281) Fixes #24248 Adjust enclosing inlineds for inlined parameter, and do look-up in imports if tree position is enclosed by any enclosing inlined position (any intermediate position, not just outermost). [Cherry-picked 2f320b874137403b7319f909a65ecb3430bc09ca] --- .../tools/dotc/transform/CheckUnused.scala | 11 +++++++++-- tests/warn/i24248/lib.scala | 18 ++++++++++++++++++ tests/warn/i24248/test.scala | 6 ++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 tests/warn/i24248/lib.scala create mode 100644 tests/warn/i24248/test.scala diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 7be02b6b19ec..21515695f2e6 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -55,7 +55,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha if tree.symbol.exists then // if in an inline expansion, resolve at summonInline (synthetic pos) or in an enclosing call site val resolving = - tree.srcPos.isUserCode + tree.srcPos.isUserCode(using if tree.hasAttachment(InlinedParameter) then ctx.outer else ctx) || tree.srcPos.isZeroExtentSynthetic // take as summonInline if !ignoreTree(tree) then def loopOverPrefixes(prefix: Type, depth: Int): Unit = @@ -140,6 +140,10 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha case _ => tree + override def prepareForInlined(tree: Inlined)(using Context): Context = + if tree.inlinedFromOuterScope then + tree.expansion.putAttachment(InlinedParameter, ()) + ctx override def transformInlined(tree: Inlined)(using Context): tree.type = if !tree.call.isEmpty then if !refInfos.calls.containsKey(tree.call) then @@ -422,6 +426,9 @@ object CheckUnused: /** Tree is LHS of Assign. */ val AssignmentTarget = Property.StickyKey[Unit] + /** Tree is an inlined parameter. */ + val InlinedParameter = Property.StickyKey[Unit] + class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper") class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining") @@ -1010,7 +1017,7 @@ object CheckUnused: def isUserCode(using Context): Boolean = val inlineds = enclosingInlineds // per current context inlineds.isEmpty - || inlineds.last.srcPos.sourcePos.contains(pos.sourcePos) + || inlineds.exists(_.srcPos.sourcePos.contains(pos.sourcePos)) // include intermediate inlinings or quotes extension [A <: AnyRef](arr: Array[A]) // returns `until` if not satisfied diff --git a/tests/warn/i24248/lib.scala b/tests/warn/i24248/lib.scala new file mode 100644 index 000000000000..8244d509f8f4 --- /dev/null +++ b/tests/warn/i24248/lib.scala @@ -0,0 +1,18 @@ + +import scala.quoted.* + +trait Thing +object Stuff: + given Thing() + +object lib: + inline def m: Thing = ${ mImpl[Thing] } + + def mImpl[T](using Quotes, Type[T]): Expr[T] = + import quotes.reflect.* + val thing = Implicits.search(TypeRepr.of[T]) match + case iss: ImplicitSearchSuccess => iss.tree.asExprOf[T] + '{ + val res = $thing + res + } diff --git a/tests/warn/i24248/test.scala b/tests/warn/i24248/test.scala new file mode 100644 index 000000000000..35137a5646c9 --- /dev/null +++ b/tests/warn/i24248/test.scala @@ -0,0 +1,6 @@ +//> using options -Werror -Wunused:all + +import Stuff.given + +@main def test = println: + lib.m From 0f1e1c7d1fa7f1cabc7f8ff061d60c6b4d5937ae Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 28 Oct 2025 12:14:35 +0100 Subject: [PATCH 107/111] Exclude synthetic opaque proxy from lint (#24264) Fixes #24263 [Cherry-picked 55f235c61f6a8bf76ef69c3778b972f0ecd6a881][modified] --- compiler/src/dotty/tools/dotc/transform/CheckUnused.scala | 2 +- tests/warn/i24263.scala | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 tests/warn/i24263.scala diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 21515695f2e6..b4098ef24221 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -642,7 +642,7 @@ object CheckUnused: def checkLocal(sym: Symbol, pos: SrcPos) = if ctx.settings.WunusedHas.locals - && !sym.is(InlineProxy) + && !sym.isOneOf(InlineProxy | Synthetic) && !sym.isCanEqual then if sym.is(Mutable) && infos.asss(sym) then diff --git a/tests/warn/i24263.scala b/tests/warn/i24263.scala new file mode 100644 index 000000000000..fc4768de2597 --- /dev/null +++ b/tests/warn/i24263.scala @@ -0,0 +1,6 @@ +//> using options -Werror -Wunused:all + +object test { + def f(t: Tuple): Nothing = ??? + val _ = (inputTuple: NamedTuple.NamedTuple[Tuple, Tuple]) => f(inputTuple) +} From 997b993668fbdb8d0deea474eeb056207aa12fa5 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 28 Oct 2025 22:23:42 +0100 Subject: [PATCH 108/111] Add changelog for 3.7.4-RC3 --- changelogs/3.7.4-RC3.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 changelogs/3.7.4-RC3.md diff --git a/changelogs/3.7.4-RC3.md b/changelogs/3.7.4-RC3.md new file mode 100644 index 000000000000..c7f0c1654d51 --- /dev/null +++ b/changelogs/3.7.4-RC3.md @@ -0,0 +1,19 @@ +# Backported chnages + +- Lint avoids revisiting Inlined.call [#24277](https://github.com/scala/scala3/pull/24277) +- Register no elements for lint after inlining [#24279](https://github.com/scala/scala3/pull/24279) +- Use enclosing enclosingInlineds for empty call [#24281](https://github.com/scala/scala3/pull/24281) +- Exclude synthetic opaque proxy from lint [#24264](https://github.com/scala/scala3/pull/24264) +- Deprecate `scala_legacy`/`MainGenericRunner`/`scalac -run`/`scalac -repl` for removal [#24267](https://github.com/scala/scala3/pull/24267) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.4-RC2..3.7.4-RC4` these are: + +``` + 4 Wojciech Mazur + 2 Piotr Chabelski + 2 Som Snytt +``` From b50034709248601efb23e4bb416d0d03abc78fc6 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 28 Oct 2025 22:24:35 +0100 Subject: [PATCH 109/111] Release Scala 3.7.4-RC3 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 51b93ee6789c..5a319f0e2d34 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -72,7 +72,7 @@ object Build { * During release candidate cycle incremented by the release officer before publishing a subsequent RC version; * During final, stable release is set exactly to `developedVersion`. */ - val baseVersion = s"$developedVersion-RC2" + val baseVersion = s"$developedVersion-RC3" /** The version of TASTY that should be emitted, checked in runtime test * For defails on how TASTY version should be set see related discussions: From 6f4bcef44f1fbd2089c59e901e37f4dee0968599 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 5 Nov 2025 12:17:22 +0100 Subject: [PATCH 110/111] Add changelog for Scala 3.7.4 --- changelogs/3.7.4.md | 149 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 changelogs/3.7.4.md diff --git a/changelogs/3.7.4.md b/changelogs/3.7.4.md new file mode 100644 index 000000000000..1a72af451008 --- /dev/null +++ b/changelogs/3.7.4.md @@ -0,0 +1,149 @@ +# Highlights of the release + +- Bump Scala CLI to v1.9.1 (was v1.9.0) [#23962](https://github.com/scala/scala3/pull/23962) +- Make coverage more similar to the one in Scala 2 [#23722](https://github.com/scala/scala3/pull/23722) + +# Deprecations for removal +- Deprecate `scala_legacy`/`MainGenericRunner`/`scalac -run`/`scalac -repl` for removal [#24267](https://github.com/scala/scala3/pull/24267) + +# Other changes and fixes + +## Context Functions + +- Explain no expansion of ContextFunction0 [#23844](https://github.com/scala/scala3/pull/23844) + +## Experimental: Capture Checking + +- Fix #23737: Update superCallContext to include dummy capture parameters in scope [#23740](https://github.com/scala/scala3/pull/23740) +- Fix separation checking for function results [#23927](https://github.com/scala/scala3/pull/23927) +- Simple enhancement for pattern matching with capturing types [#23524](https://github.com/scala/scala3/pull/23524) +- Don't check bounds in match type cases at CC [#23738](https://github.com/scala/scala3/pull/23738) + +## Experimental: Explicit Nulls + +- Add warnings for inferred flexible types in public methods and fields [#23880](https://github.com/scala/scala3/pull/23880) + +## Exports + +- Refine isEffectivelyFinal to avoid no-owner crash [#23675](https://github.com/scala/scala3/pull/23675) + +## Implicits + +- Fix LiftToAnchors for higher-kinded type applications [#23672](https://github.com/scala/scala3/pull/23672) +- Fix implicit scope liftToAnchors for parameter lower bounds [#23679](https://github.com/scala/scala3/pull/23679) + +## Linting + +- Invent given pattern name in for comprehension [#23121](https://github.com/scala/scala3/pull/23121) +- Unused var message mentions unread or unset [#23719](https://github.com/scala/scala3/pull/23719) +- Lint function arrow intended context function [#23847](https://github.com/scala/scala3/pull/23847) +- Always traverse Inlined.call in linter [#24043](https://github.com/scala/scala3/pull/24043) +- Ignore warnings when compiletime.testing is imported [#24036](https://github.com/scala/scala3/pull/24036) +- Lint avoids revisiting Inlined.call [#24277](https://github.com/scala/scala3/pull/24277) +- Register no elements for lint after inlining [#24279](https://github.com/scala/scala3/pull/24279) +- Use enclosing enclosingInlineds for empty call [#24281](https://github.com/scala/scala3/pull/24281) +- Exclude synthetic opaque proxy from lint [#24264](https://github.com/scala/scala3/pull/24264) + +## Match Types + +- Fix `derivesFrom` false negative in `provablyDisjointClasses` [#23834](https://github.com/scala/scala3/pull/23834) + + +## Parser + +- Improve message for nested package missing braces [#23816](https://github.com/scala/scala3/pull/23816) +- Fix: Allow postfix setters under language.postfixOps [#23775](https://github.com/scala/scala3/pull/23775) +- Fix Java record varargs field accessor [#24172](https://github.com/scala/scala3/pull/24172) + +## Pattern Matching + +- Fix: do not transform `Ident` to `This` in PostTyper anymore [#23899](https://github.com/scala/scala3/pull/23899) +- Call inhabited for AppliedType recursively [#23964](https://github.com/scala/scala3/pull/23964) +- Fix false unreachable case warning [#23800](https://github.com/scala/scala3/pull/23800) +- Add subtype-based fallback in inferPrefixMap and recalculate constraints after application [#23771](https://github.com/scala/scala3/pull/23771) + +## Presentation Compiler + +- Additional completions for using clause [#23647](https://github.com/scala/scala3/pull/23647) +- Completions - do not add `[]` for `... derives TC@@` [#23811](https://github.com/scala/scala3/pull/23811) +- Improve symbol order in completions provided by the presentation compiler [#23888](https://github.com/scala/scala3/pull/23888) +- Porting XRayModeHints [#23891](https://github.com/scala/scala3/pull/23891) +- Go to definition and hover for named args in pattern match [#23956](https://github.com/scala/scala3/pull/23956) +- Fix parameter untupling for named tuples (#23440) [#24152](https://github.com/scala/scala3/pull/24152) +- Fix possible SuspendException thrown when using macros [#24174](https://github.com/scala/scala3/pull/24174) +- Fix completions for named tuples [#24169](https://github.com/scala/scala3/pull/24169) + +## Reporting + +- Do not discard amended format when f-interpolator warns [#23697](https://github.com/scala/scala3/pull/23697) +- Mention named givens in double def explainer [#23833](https://github.com/scala/scala3/pull/23833) +- Compute the right span for abstract error messages [#23853](https://github.com/scala/scala3/pull/23853) +- Add quick fix to add .nn [#23598](https://github.com/scala/scala3/pull/23598) +- Add addendum to `private val` parameter variance error message [#23876](https://github.com/scala/scala3/pull/23876) +- Fix compiler crash with `-Ymagic-offset-header` [#24124](https://github.com/scala/scala3/pull/24124) + +## Rewrites + +- Deduplicate patches before applying them to sources [#24215](https://github.com/scala/scala3/pull/24215) + +## Scaladoc + +- Indicate optional parameters with `= ...` [#23676](https://github.com/scala/scala3/pull/23676) +- Scaladoc Support for Capture & Separation Checking [#23607](https://github.com/scala/scala3/pull/23607) +- Capture Calcuclus: don't eagerly drop caps on parameters [#23759](https://github.com/scala/scala3/pull/23759) +- Fix rendering of function-type aliases [#24042](https://github.com/scala/scala3/pull/24042) + +## SemanticDB + +- Add context parameters to SemanticDB synthetics [#23381](https://github.com/scala/scala3/pull/23381) +- Include synthetic apply in semanticdb [#23629](https://github.com/scala/scala3/pull/23629) + +## Tuples + +- Fix: make vals created in desugaring of n-ary lambdas non-synthetic [#23896](https://github.com/scala/scala3/pull/23896) + +## Typer + +- Prevent crash in SAM conversion with mismatched arity [#23877](https://github.com/scala/scala3/pull/23877) +- Handle assertion error in TyperState [#23665](https://github.com/scala/scala3/pull/23665) +- Correctly require a `ClassTag` when building a multidimensional `Array` [#23902](https://github.com/scala/scala3/pull/23902) +- Make isExactlyNothing and isExactlyAny work for And/OrTypes [#24016](https://github.com/scala/scala3/pull/24016) + + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.3..3.7.4` these are: + +``` + 23 Wojciech Mazur + 16 Som Snytt + 11 noti0na1 + 6 Martin Odersky + 5 Eugene Flesselle + 4 Hamza Remmal + 4 Natsu Kagami + 4 Oliver Bračevac + 4 Seyon Sivatharan + 3 Piotr Chabelski + 3 Tomasz Godzik + 3 Yoonjae Jeon + 3 dependabot[bot] + 2 Jan Chyb + 2 Kacper Korban + 2 Katarzyna Marek + 2 Li Haoyi + 2 Matt Bovel + 2 aherlihy + 1 Florian3k + 1 HarrisL2 + 1 Martin Duhem + 1 Paweł Perłakowski + 1 Vadim Chelyshov + 1 Yichen Xu + 1 Zieliński Patryk + 1 katrinafyi + 1 vder + 1 zielinsky +``` \ No newline at end of file From 40be7608a48477951218ae3a8ac8749fe02ba988 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 5 Nov 2025 12:18:34 +0100 Subject: [PATCH 111/111] Release Scala 3.7.4 Signed-off-by: Wojciech Mazur --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 5a319f0e2d34..db4966cb4231 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -72,7 +72,7 @@ object Build { * During release candidate cycle incremented by the release officer before publishing a subsequent RC version; * During final, stable release is set exactly to `developedVersion`. */ - val baseVersion = s"$developedVersion-RC3" + val baseVersion = developedVersion /** The version of TASTY that should be emitted, checked in runtime test * For defails on how TASTY version should be set see related discussions: