From 482959bacf03f14bfc08241ac779a341ea029a32 Mon Sep 17 00:00:00 2001 From: Alex1005a Date: Fri, 1 Aug 2025 13:03:02 +0300 Subject: [PATCH 001/128] Don't module class for unhabitated fields (#23634) Fix #23576 [Cherry-picked 17625b571f852462f1975af925e444a60f723e7e] --- compiler/src/dotty/tools/dotc/transform/patmat/Space.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 50363759c542..b7e1f349a377 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -685,7 +685,7 @@ object SpaceEngine { val refined = trace(i"refineUsingParent($tp, $sym1, $mixins)")(TypeOps.refineUsingParent(tp, sym1, mixins)) def containsUninhabitedField(tp: Type): Boolean = - tp.fields.exists { field => + !tp.typeSymbol.is(ModuleClass) && tp.fields.exists { field => !field.symbol.flags.is(Lazy) && field.info.dealias.isBottomType } From bd869f1f82706872ae67424367f1f937aa22d8ea Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 28 Jul 2025 18:41:38 +0000 Subject: [PATCH 002/128] Emit an error for quoted pattern type variable after `new` [Cherry-picked 05b26be733282aef65c179d9c6aee8ad19c2933d] --- .../tools/dotc/reporting/ErrorMessageID.scala | 1 + .../dotty/tools/dotc/reporting/messages.scala | 12 ++++++++++++ .../tools/dotc/typer/QuotesAndSplices.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 3 +++ tests/neg-macros/i22616.check | 16 ++++++++++++++++ tests/neg-macros/i22616.scala | 18 ++++++++++++++++++ 6 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/neg-macros/i22616.check create mode 100644 tests/neg-macros/i22616.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 2dedcd788ec4..24c3d2e412f7 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -232,6 +232,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case UnnecessaryNN // errorNumber: 216 case ErasedNotPureID // errorNumber: 217 case IllegalErasedDefID // errorNumber: 218 + case CannotInstantiateQuotedTypeVarID // errorNumber: 219 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index eb5e692b2915..f84fd3ba538e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3435,6 +3435,18 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q end QuotedTypeMissing +final class CannotInstantiateQuotedTypeVar(symbol: Symbol)(using patternCtx: Context) extends StagingMessage(CannotInstantiateQuotedTypeVarID): + override protected def msg(using Context): String = + i"""Quoted pattern type variable `${symbol.name}` cannot be instantiated. + |If you meant to refer to a class named `${symbol.name}`, wrap it in backticks. + |If you meant to introduce a binding, this is not allowed after `new`. You might + |want to use the lower-level `quotes.reflect` API instead. + |Read more about type variables in quoted pattern in the Scala documentation: + |https://docs.scala-lang.org/scala3/guides/macros/quotes.html#type-variables-in-quoted-patterns + """ + + override protected def explain(using Context): String = "" + final class DeprecatedAssignmentSyntax(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(DeprecatedAssignmentSyntaxID): override protected def msg(using Context): String = i"""Deprecated syntax: since 3.7 this is interpreted as a named tuple with one element, diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 4e7c4336b852..f979654e9811 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -222,7 +222,7 @@ trait QuotesAndSplices { if ctx.mode.is(Mode.InPatternAlternative) then report.error(IllegalVariableInPatternAlternative(tree.name), tree.srcPos) val typeSym = inContext(quotePatternOuterContext(ctx)) { - newSymbol(ctx.owner, tree.name.toTypeName, Case, typeSymInfo, NoSymbol, tree.span) + newSymbol(ctx.owner, tree.name.toTypeName, Synthetic | Case, typeSymInfo, NoSymbol, tree.span) } addQuotedPatternTypeVariable(typeSym) Bind(typeSym, untpd.Ident(nme.WILDCARD).withType(typeSymInfo)).withSpan(tree.span) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 10a061ab8fc4..c45c00845120 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1237,6 +1237,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => var tpt1 = typedType(tree.tpt) val tsym = tpt1.tpe.underlyingClassRef(refinementOK = false).typeSymbol + if ctx.mode.isQuotedPattern && tpt1.tpe.typeSymbol.isAllOf(Synthetic | Case) then + val errorTp = errorType(CannotInstantiateQuotedTypeVar(tpt1.tpe.typeSymbol), tpt1.srcPos) + return cpy.New(tree)(tpt1).withType(errorTp) if tsym.is(Package) then report error(em"$tsym cannot be instantiated", tpt1.srcPos) tpt1 = tpt1.withType(ensureAccessible(tpt1.tpe, superAccess = false, tpt1.srcPos)) diff --git a/tests/neg-macros/i22616.check b/tests/neg-macros/i22616.check new file mode 100644 index 000000000000..d830d0c3fe00 --- /dev/null +++ b/tests/neg-macros/i22616.check @@ -0,0 +1,16 @@ +-- [E219] Staging Issue Error: tests/neg-macros/i22616.scala:13:22 ----------------------------------------------------- +13 | case '{ new caseName(${ Expr(name) }) } => Some(caseName(name)) // error // error + | ^^^^^^^^ + | Quoted pattern type variable `caseName` cannot be instantiated. + | If you meant to refer to a class named `caseName`, wrap it in backticks. + | If you meant to introduce a binding, this is not allowed after `new`. You might + | want to use the lower-level `quotes.reflect` API instead. + | Read more about type variables in quoted pattern in the Scala documentation: + | https://docs.scala-lang.org/scala3/guides/macros/quotes.html#type-variables-in-quoted-patterns + | +-- [E006] Not Found Error: tests/neg-macros/i22616.scala:13:67 --------------------------------------------------------- +13 | case '{ new caseName(${ Expr(name) }) } => Some(caseName(name)) // error // error + | ^^^^ + | Not found: name + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-macros/i22616.scala b/tests/neg-macros/i22616.scala new file mode 100644 index 000000000000..bd86716bc30c --- /dev/null +++ b/tests/neg-macros/i22616.scala @@ -0,0 +1,18 @@ +import scala.quoted.* + +final case class caseName(name: String) extends scala.annotation.Annotation +object caseName { + + given FromExpr[caseName] = + new FromExpr[caseName] { + override def unapply(x: Expr[caseName])(using Quotes): Option[caseName] = + val y: Int = 42 + x match { + case '{ caseName(${ Expr(name) }) } => Some(caseName(name)) + // with/without the following line... + case '{ new caseName(${ Expr(name) }) } => Some(caseName(name)) // error // error + case _ => println(x.show); None + } + } + +} From 85fda46246c9f85bd7ab70a666d07e9d98e0b05b Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 28 Jul 2025 18:48:09 +0000 Subject: [PATCH 003/128] Add quoted pattern type variable expected type test [Cherry-picked e9808a9a379b8bd406dc18af1a84949f7f6ef300] --- tests/neg-macros/i22616b.check | 13 +++++++++++++ tests/neg-macros/i22616b.scala | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/neg-macros/i22616b.check create mode 100644 tests/neg-macros/i22616b.scala diff --git a/tests/neg-macros/i22616b.check b/tests/neg-macros/i22616b.check new file mode 100644 index 000000000000..3c6007276cef --- /dev/null +++ b/tests/neg-macros/i22616b.check @@ -0,0 +1,13 @@ +-- [E007] Type Mismatch Error: tests/neg-macros/i22616b.scala:17:18 ---------------------------------------------------- +17 | case '{ Foo($y: t) } => // error + | ^^^^^ + | Found: t + | Required: String + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg-macros/i22616b.scala:18:19 -------------------------------------------------------- +18 | '{type S = t; ()} // error + | ^ + | Not found: type t + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-macros/i22616b.scala b/tests/neg-macros/i22616b.scala new file mode 100644 index 000000000000..45926f4e0d37 --- /dev/null +++ b/tests/neg-macros/i22616b.scala @@ -0,0 +1,22 @@ +// This test illustrates a current limitation of quoted pattern type variables, +// which has been discussed in https://github.com/scala/scala3/issues/22616#issuecomment-3012534064: +// These type variables do not have bound in general (see `typedQuotedTypeVar`), +// so they might not conform to the expected type. Here, `t` does not conform +// to `String`. + +import scala.quoted.{FromExpr, Expr, Quotes} + +case class Foo(x: String) + +object Macro: + inline def myMacro(): Unit = + ${ myMacroImpl('{Foo("hello")}) } + + def myMacroImpl(x: Expr[Foo])(using Quotes): Expr[Unit] = + x match + case '{ Foo($y: t) } => // error + '{type S = t; ()} // error + case _ => + println("not a foo") + + '{()} From 02886acff49ebe11dd7b0f222fbc52ca221bfac0 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 28 Jul 2025 18:48:51 +0000 Subject: [PATCH 004/128] Add workaround for #22616 [Cherry-picked c5b83bd21886a20303297505f6fe9ba747e9fdbe] --- tests/run-macros/i22616c.check | 1 + tests/run-macros/i22616c/Macro_4.scala | 11 +++++++ tests/run-macros/i22616c/MyTypeRepr_3.scala | 33 +++++++++++++++++++ tests/run-macros/i22616c/SealedTrait3_2.scala | 8 +++++ tests/run-macros/i22616c/Test_5.scala | 2 ++ tests/run-macros/i22616c/caseName_1.scala | 14 ++++++++ 6 files changed, 69 insertions(+) create mode 100644 tests/run-macros/i22616c.check create mode 100644 tests/run-macros/i22616c/Macro_4.scala create mode 100644 tests/run-macros/i22616c/MyTypeRepr_3.scala create mode 100644 tests/run-macros/i22616c/SealedTrait3_2.scala create mode 100644 tests/run-macros/i22616c/Test_5.scala create mode 100644 tests/run-macros/i22616c/caseName_1.scala diff --git a/tests/run-macros/i22616c.check b/tests/run-macros/i22616c.check new file mode 100644 index 000000000000..d1918272a8f7 --- /dev/null +++ b/tests/run-macros/i22616c.check @@ -0,0 +1 @@ +_B_ diff --git a/tests/run-macros/i22616c/Macro_4.scala b/tests/run-macros/i22616c/Macro_4.scala new file mode 100644 index 000000000000..02e677db3428 --- /dev/null +++ b/tests/run-macros/i22616c/Macro_4.scala @@ -0,0 +1,11 @@ +import scala.quoted.* + +object Macro: + inline def myMacro[T](): String = + ${ myMacroImpl[T]() } + + def myMacroImpl[T: Type]()(using Quotes): Expr[String] = + import quotes.reflect.* + val myTypeRepr = MyTypeRepr(TypeRepr.of[T]) + val `caseName`(name) = myTypeRepr.requiredAnnotationValue[caseName] + Expr(name) diff --git a/tests/run-macros/i22616c/MyTypeRepr_3.scala b/tests/run-macros/i22616c/MyTypeRepr_3.scala new file mode 100644 index 000000000000..1a35889d403d --- /dev/null +++ b/tests/run-macros/i22616c/MyTypeRepr_3.scala @@ -0,0 +1,33 @@ +import scala.quoted.* + +final class MyTypeRepr(using val quotes: Quotes)(val unwrap: quotes.reflect.TypeRepr) { + import quotes.reflect.* + + def getAnnotation(annotTpe: quotes.reflect.Symbol): Option[quotes.reflect.Term] = + unwrap.typeSymbol.getAnnotation(annotTpe) + + def optionalAnnotation[Annot: Type]: Option[Expr[Annot]] = { + val annotTpe = TypeRepr.of[Annot] + val annotFlags = annotTpe.typeSymbol.flags + + if (annotFlags.is(Flags.Abstract) || annotFlags.is(Flags.Trait)) + report.errorAndAbort(s"Bad annotation type ${annotTpe.show} is abstract") + + this.getAnnotation(annotTpe.typeSymbol) match + case Some(tree) if tree.tpe <:< annotTpe => Some(tree.asExprOf[Annot]) + case _ => None + } + + def requiredAnnotation[Annot: Type]: Expr[Annot] = + optionalAnnotation[Annot].getOrElse(report.errorAndAbort(s"Missing required annotation `${TypeRepr.of[Annot].show}` for `$this`")) + + def optionalAnnotationValue[Annot: {Type, FromExpr}]: Option[Annot] = + optionalAnnotation[Annot].map { expr => + expr.value.getOrElse(report.errorAndAbort(s"Found annotation `${TypeRepr.of[Annot].show}` for `$this`, but are unable to extract Expr.value\n${expr.show}")) + } + + def requiredAnnotationValue[Annot: {Type, FromExpr}]: Annot = { + val expr = requiredAnnotation[Annot] + expr.value.getOrElse(report.errorAndAbort(s"Found annotation `${TypeRepr.of[Annot].show}` for `$this`, but are unable to extract Expr.value\n${expr.show}")) + } +} diff --git a/tests/run-macros/i22616c/SealedTrait3_2.scala b/tests/run-macros/i22616c/SealedTrait3_2.scala new file mode 100644 index 000000000000..9141a9ebd08b --- /dev/null +++ b/tests/run-macros/i22616c/SealedTrait3_2.scala @@ -0,0 +1,8 @@ +sealed trait SealedTrait3[+A, +B] +object SealedTrait3 { + final case class AB1[+B, +A](a: B, b: A) extends SealedTrait3[B, A] + final case class AB2[+C, +D](a: C, b: D) extends SealedTrait3[D, C] + final case class A[+T](a: T) extends SealedTrait3[T, Nothing] + @caseName("_B_") final case class B[+T](b: T) extends SealedTrait3[Nothing, T] + case object Neither extends SealedTrait3[Nothing, Nothing] +} diff --git a/tests/run-macros/i22616c/Test_5.scala b/tests/run-macros/i22616c/Test_5.scala new file mode 100644 index 000000000000..c8d177a5ae1b --- /dev/null +++ b/tests/run-macros/i22616c/Test_5.scala @@ -0,0 +1,2 @@ +@main def Test = + println(Macro.myMacro[SealedTrait3.B[Any]]()) diff --git a/tests/run-macros/i22616c/caseName_1.scala b/tests/run-macros/i22616c/caseName_1.scala new file mode 100644 index 000000000000..c8fee116e2e9 --- /dev/null +++ b/tests/run-macros/i22616c/caseName_1.scala @@ -0,0 +1,14 @@ +import scala.quoted.* + +final case class caseName(name: String) extends scala.annotation.Annotation +object caseName { + // This demonstrates a workaround for issue #22616. + given FromExpr[caseName] = + new FromExpr[caseName] { + override def unapply(x: Expr[caseName])(using Quotes): Option[caseName] = + x match { + case '{ new `caseName`(${ Expr(name) }) } => Some(caseName(name)) + case _ => println(x.show); None + } + } +} From a3b9238267acdd3586a362a521d27211a2ee724e Mon Sep 17 00:00:00 2001 From: aherlihy Date: Fri, 25 Jul 2025 20:24:54 +0200 Subject: [PATCH 005/128] Skip bypassing unapply for scala 2 case classes to allow for single-element named tuple in unapply [Cherry-picked 2183e537abb94f04636cf5c70b3d52af0a1b273c] --- .../src/dotty/tools/dotc/transform/PatternMatcher.scala | 2 +- tests/run/i23131.scala | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 tests/run/i23131.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index e2505144abda..6a13de14450e 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -343,7 +343,7 @@ object PatternMatcher { receiver.ensureConforms(defn.NonEmptyTupleTypeRef), // If scrutinee is a named tuple, cast to underlying tuple Literal(Constant(i))) - if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length) + if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length && args.length != 1) def tupleSel(sym: Symbol) = // If scrutinee is a named tuple, cast to underlying tuple, so that we can // continue to select with _1, _2, ... diff --git a/tests/run/i23131.scala b/tests/run/i23131.scala new file mode 100644 index 000000000000..8d22cd2a4b94 --- /dev/null +++ b/tests/run/i23131.scala @@ -0,0 +1,9 @@ +import scala.NamedTuple +@main +def Test = + Some((name = "Bob")) match { + case Some(name = a) => println(a) + } +// (name = "Bob") match { // works fine +// case (name = a) => println (a) +// } \ No newline at end of file From 4affff6aa9a000466c3b25801b54f7b29b2065ab Mon Sep 17 00:00:00 2001 From: aherlihy Date: Fri, 25 Jul 2025 20:53:04 +0200 Subject: [PATCH 006/128] more precise check [Cherry-picked 4e684dc176967e5fd2841637a62f2c167fceab25] --- compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 6a13de14450e..4cfcba44ee9c 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -343,7 +343,8 @@ object PatternMatcher { receiver.ensureConforms(defn.NonEmptyTupleTypeRef), // If scrutinee is a named tuple, cast to underlying tuple Literal(Constant(i))) - if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length && args.length != 1) + val wasNamedArg = args.length == 1 && args.head.removeAttachment(FirstTransform.WasNamedArg).isDefined + if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length && !wasNamedArg) def tupleSel(sym: Symbol) = // If scrutinee is a named tuple, cast to underlying tuple, so that we can // continue to select with _1, _2, ... @@ -388,7 +389,7 @@ object PatternMatcher { letAbstract(get) { getResult => def isUnaryNamedTupleSelectArg(arg: Tree) = get.tpe.widenDealias.isNamedTupleType - && arg.removeAttachment(FirstTransform.WasNamedArg).isDefined + && wasNamedArg // Special case: Normally, we pull out the argument wholesale if // there is only one. But if the argument is a named argument for // a single-element named tuple, we have to select the field instead. From 2ac1dcaa717eac28a2fc15c31e80ee3bedb84af6 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Tue, 29 Jul 2025 16:30:26 +0200 Subject: [PATCH 007/128] Make check more precise for single named tuple selector in pattern match [Cherry-picked 7f95284aa2d608054faae0751e75875e8abb022e] --- .../tools/dotc/transform/PatternMatcher.scala | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 4cfcba44ee9c..b9c93ce1b36f 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -343,8 +343,12 @@ object PatternMatcher { receiver.ensureConforms(defn.NonEmptyTupleTypeRef), // If scrutinee is a named tuple, cast to underlying tuple Literal(Constant(i))) - val wasNamedArg = args.length == 1 && args.head.removeAttachment(FirstTransform.WasNamedArg).isDefined - if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length && !wasNamedArg) + // Disable Scala2Unapply optimization if the argument is a named argument for a single-element named tuple to + // enable selecting the field. See i23131.scala for test cases. + val wasSingleNamedArgForNamedTuple = + args.length == 1 && args.head.removeAttachment(FirstTransform.WasNamedArg).isDefined && + isGetMatch(unappType) && unapp.select(nme.get, _.info.isParameterless).tpe.widenDealias.isNamedTupleType + if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length && !wasSingleNamedArgForNamedTuple) def tupleSel(sym: Symbol) = // If scrutinee is a named tuple, cast to underlying tuple, so that we can // continue to select with _1, _2, ... @@ -387,20 +391,16 @@ object PatternMatcher { } else letAbstract(get) { getResult => - def isUnaryNamedTupleSelectArg(arg: Tree) = - get.tpe.widenDealias.isNamedTupleType - && wasNamedArg // Special case: Normally, we pull out the argument wholesale if // there is only one. But if the argument is a named argument for // a single-element named tuple, we have to select the field instead. // NamedArg trees are eliminated in FirstTransform but for named arguments // of patterns we add a WasNamedArg attachment, which is used to guide the // logic here. See i22900.scala for test cases. - val selectors = args match - case arg :: Nil if !isUnaryNamedTupleSelectArg(arg) => - ref(getResult) :: Nil - case _ => - productSelectors(getResult.info).map(ref(getResult).select(_)) + val selectors = if args.length == 1 && !wasSingleNamedArgForNamedTuple then + ref(getResult) :: Nil + else + productSelectors(getResult.info).map(ref(getResult).select(_)) matchArgsPlan(selectors, args, onSuccess) } } From 5b1dccfe9605b0d49d1029fdc8ae5691fa544bc1 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Tue, 29 Jul 2025 16:49:52 +0200 Subject: [PATCH 008/128] minimize diff [Cherry-picked 3c5e4a086a6f061672e015136c8878067ae7cf70] --- .../dotty/tools/dotc/transform/PatternMatcher.scala | 13 +++++++------ tests/run/i23131.scala | 5 +---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index b9c93ce1b36f..e111cf845dfc 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -345,10 +345,10 @@ object PatternMatcher { // Disable Scala2Unapply optimization if the argument is a named argument for a single-element named tuple to // enable selecting the field. See i23131.scala for test cases. - val wasSingleNamedArgForNamedTuple = + val wasUnaryNamedTupleSelectArgForNamedTuple = args.length == 1 && args.head.removeAttachment(FirstTransform.WasNamedArg).isDefined && isGetMatch(unappType) && unapp.select(nme.get, _.info.isParameterless).tpe.widenDealias.isNamedTupleType - if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length && !wasSingleNamedArgForNamedTuple) + if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length && !wasUnaryNamedTupleSelectArgForNamedTuple) def tupleSel(sym: Symbol) = // If scrutinee is a named tuple, cast to underlying tuple, so that we can // continue to select with _1, _2, ... @@ -397,10 +397,11 @@ object PatternMatcher { // NamedArg trees are eliminated in FirstTransform but for named arguments // of patterns we add a WasNamedArg attachment, which is used to guide the // logic here. See i22900.scala for test cases. - val selectors = if args.length == 1 && !wasSingleNamedArgForNamedTuple then - ref(getResult) :: Nil - else - productSelectors(getResult.info).map(ref(getResult).select(_)) + val selectors = args match + case arg :: Nil if !wasUnaryNamedTupleSelectArgForNamedTuple => + ref(getResult) :: Nil + case _ => + productSelectors(getResult.info).map(ref(getResult).select(_)) matchArgsPlan(selectors, args, onSuccess) } } diff --git a/tests/run/i23131.scala b/tests/run/i23131.scala index 8d22cd2a4b94..2fdee0d9a618 100644 --- a/tests/run/i23131.scala +++ b/tests/run/i23131.scala @@ -3,7 +3,4 @@ import scala.NamedTuple def Test = Some((name = "Bob")) match { case Some(name = a) => println(a) - } -// (name = "Bob") match { // works fine -// case (name = a) => println (a) -// } \ No newline at end of file + } \ No newline at end of file From 0fa203fac557efff19245881b937f9d405733cdd Mon Sep 17 00:00:00 2001 From: aherlihy Date: Wed, 30 Jul 2025 10:46:32 +0200 Subject: [PATCH 009/128] Refactor out getOfGetMatch [Cherry-picked 73da315cb25cf1e39df5f76165752512bda48d41] --- compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index e111cf845dfc..778a97f6c38b 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -343,11 +343,12 @@ object PatternMatcher { receiver.ensureConforms(defn.NonEmptyTupleTypeRef), // If scrutinee is a named tuple, cast to underlying tuple Literal(Constant(i))) + def getOfGetMatch(gm: Tree) = gm.select(nme.get, _.info.isParameterless) // Disable Scala2Unapply optimization if the argument is a named argument for a single-element named tuple to // enable selecting the field. See i23131.scala for test cases. val wasUnaryNamedTupleSelectArgForNamedTuple = args.length == 1 && args.head.removeAttachment(FirstTransform.WasNamedArg).isDefined && - isGetMatch(unappType) && unapp.select(nme.get, _.info.isParameterless).tpe.widenDealias.isNamedTupleType + isGetMatch(unappType) && getOfGetMatch(unapp).tpe.widenDealias.isNamedTupleType if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length && !wasUnaryNamedTupleSelectArgForNamedTuple) def tupleSel(sym: Symbol) = // If scrutinee is a named tuple, cast to underlying tuple, so that we can @@ -381,7 +382,7 @@ object PatternMatcher { else { assert(isGetMatch(unappType)) val argsPlan = { - val get = ref(unappResult).select(nme.get, _.info.isParameterless) + val get = getOfGetMatch(ref(unappResult)) val arity = productArity(get.tpe.stripNamedTuple, unapp.srcPos) if (isUnapplySeq) letAbstract(get) { getResult => From e53f8615737a056cbab567188a2a1007f300a9e7 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Wed, 16 Jul 2025 18:46:42 +0800 Subject: [PATCH 010/128] Add setting for specifying magic line number comment [Cherry-picked b52e6b807ef5949765bff65eb1606a3bfeadb116] --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 5cdc742fa753..e8cc89e0dfef 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -341,6 +341,7 @@ private sealed trait XSettings: val Xdumpclasses: Setting[String] = StringSetting(AdvancedSetting, "Xdump-classes", "dir", "Dump the generated bytecode to .class files (useful for reflective compilation that utilizes in-memory classloaders).", "") val XjarCompressionLevel: Setting[Int] = IntChoiceSetting(AdvancedSetting, "Xjar-compression-level", "compression level to use when writing jar files", Deflater.DEFAULT_COMPRESSION to Deflater.BEST_COMPRESSION, Deflater.DEFAULT_COMPRESSION) val XkindProjector: Setting[String] = ChoiceSetting(AdvancedSetting, "Xkind-projector", "[underscores, enable, disable]", "Allow `*` as type lambda placeholder to be compatible with kind projector. When invoked as -Xkind-projector:underscores will repurpose `_` to be a type parameter placeholder, this will disable usage of underscore as a wildcard.", List("disable", "", "underscores"), "disable", legacyArgs = true) + val XmagicOffsetHeader: Setting[String] = StringSetting(AdvancedSetting, "Xmagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts.", "") /** Documentation related settings */ val XdropComments: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xdrop-docs", "Drop documentation when scanning source files.", aliases = List("-Xdrop-comments")) From cff7122d9645cd3cb9482cfc21b047cbc69af3a1 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 17 Jul 2025 00:09:31 +0800 Subject: [PATCH 011/128] Offset line numbers when reporting given a magic comment [Cherry-picked f5f299615013696e124417f8edb07335333a661d] --- .../tools/dotc/reporting/MessageRendering.scala | 6 ++++-- .../src/dotty/tools/dotc/rewrites/Rewrites.scala | 2 +- .../src/dotty/tools/dotc/util/SourceFile.scala | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index 7fddfc8d6ed0..8f9a00058ad5 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -11,7 +11,7 @@ import core.Decorators.* import printing.Highlighting.{Blue, Red, Yellow} import printing.SyntaxHighlighting import Diagnostic.* -import util.{ SourcePosition, NoSourcePosition } +import util.{ SourcePosition, NoSourcePosition, WrappedSourceFile } import util.Chars.{ LF, CR, FF, SU } import scala.annotation.switch @@ -44,7 +44,9 @@ trait MessageRendering { var maxLen = Int.MinValue def render(offsetAndLine: (Int, String)): String = { val (offset1, line) = offsetAndLine - val lineNbr = (pos.source.offsetToLine(offset1) + 1).toString + val magicOffset = WrappedSourceFile.locateMagicHeader(pos.source).getOrElse(0) + println(i"magicOffset: $magicOffset") + val lineNbr = (pos.source.offsetToLine(offset1) + 1 - magicOffset).toString val prefix = String.format(s"%${offset - 2}s |", lineNbr) maxLen = math.max(maxLen, prefix.length) val lnum = hl(" " * math.max(0, maxLen - prefix.length - 1) + prefix) diff --git a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala index 3c7216625a7c..272db26bdd3c 100644 --- a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala +++ b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala @@ -7,7 +7,7 @@ import core.Contexts.* import collection.mutable import scala.annotation.tailrec import dotty.tools.dotc.reporting.Reporter -import dotty.tools.dotc.util.SourcePosition; +import dotty.tools.dotc.util.SourcePosition import java.io.OutputStreamWriter import java.nio.charset.StandardCharsets.UTF_8 diff --git a/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index eb99fe99d926..971b614c085a 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -61,6 +61,21 @@ object ScriptSourceFile { } } +object WrappedSourceFile: + private val cache: mutable.HashMap[SourceFile, Int] = mutable.HashMap.empty + def locateMagicHeader(sourceFile: SourceFile)(using Context): Option[Int] = + def findOffset: Int = + val magicHeader = ctx.settings.XmagicOffsetHeader.value + if magicHeader.isEmpty then + -1 + else + val s = new String(sourceFile.content) + val regex = ("(?m)^" + java.util.regex.Pattern.quote(magicHeader) + "$").r + val pos = regex.findFirstMatchIn(s).map(_.start).map(sourceFile.offsetToLine(_)) + pos.getOrElse(-1) + val result = cache.getOrElseUpdate(sourceFile, findOffset) + if result >= 0 then Some(result + 1) else None + class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends interfaces.SourceFile { import SourceFile.* From fc0593cb2b6b042e9d9c9e779fbd0c9f4ef63850 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 17 Jul 2025 00:39:49 +0800 Subject: [PATCH 012/128] Add testcases [Cherry-picked f89d9cc998f5204d1114495a2268dd54c29c8206] --- .../dotty/tools/dotc/config/ScalaSettings.scala | 2 +- .../tools/dotc/reporting/MessageRendering.scala | 7 ++++--- tests/neg/magic-offset-header-a.check | 7 +++++++ tests/neg/magic-offset-header-a.scala | 6 ++++++ tests/neg/magic-offset-header-b.check | 14 ++++++++++++++ tests/neg/magic-offset-header-b.scala | 7 +++++++ tests/neg/magic-offset-header-c.check | 7 +++++++ tests/neg/magic-offset-header-c.scala | 8 ++++++++ 8 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 tests/neg/magic-offset-header-a.check create mode 100644 tests/neg/magic-offset-header-a.scala create mode 100644 tests/neg/magic-offset-header-b.check create mode 100644 tests/neg/magic-offset-header-b.scala create mode 100644 tests/neg/magic-offset-header-c.check create mode 100644 tests/neg/magic-offset-header-c.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index e8cc89e0dfef..1304f31ed9cd 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -341,7 +341,7 @@ private sealed trait XSettings: val Xdumpclasses: Setting[String] = StringSetting(AdvancedSetting, "Xdump-classes", "dir", "Dump the generated bytecode to .class files (useful for reflective compilation that utilizes in-memory classloaders).", "") val XjarCompressionLevel: Setting[Int] = IntChoiceSetting(AdvancedSetting, "Xjar-compression-level", "compression level to use when writing jar files", Deflater.DEFAULT_COMPRESSION to Deflater.BEST_COMPRESSION, Deflater.DEFAULT_COMPRESSION) val XkindProjector: Setting[String] = ChoiceSetting(AdvancedSetting, "Xkind-projector", "[underscores, enable, disable]", "Allow `*` as type lambda placeholder to be compatible with kind projector. When invoked as -Xkind-projector:underscores will repurpose `_` to be a type parameter placeholder, this will disable usage of underscore as a wildcard.", List("disable", "", "underscores"), "disable", legacyArgs = true) - val XmagicOffsetHeader: Setting[String] = StringSetting(AdvancedSetting, "Xmagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts.", "") + val XmagicOffsetHeader: Setting[String] = StringSetting(AdvancedSetting, "Xmagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts. Example: -Xmagic-offset-header:///SOURCE_CODE_START", "") /** Documentation related settings */ val XdropComments: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xdrop-docs", "Drop documentation when scanning source files.", aliases = List("-Xdrop-comments")) diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index 8f9a00058ad5..dcb9ddcb2d06 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -44,9 +44,10 @@ trait MessageRendering { var maxLen = Int.MinValue def render(offsetAndLine: (Int, String)): String = { val (offset1, line) = offsetAndLine - val magicOffset = WrappedSourceFile.locateMagicHeader(pos.source).getOrElse(0) - println(i"magicOffset: $magicOffset") - val lineNbr = (pos.source.offsetToLine(offset1) + 1 - magicOffset).toString + var magicOffset = WrappedSourceFile.locateMagicHeader(pos.source).getOrElse(0) + val lineId = pos.source.offsetToLine(offset1) + if lineId < magicOffset then magicOffset = 0 + val lineNbr = (lineId + 1 - magicOffset).toString val prefix = String.format(s"%${offset - 2}s |", lineNbr) maxLen = math.max(maxLen, prefix.length) val lnum = hl(" " * math.max(0, maxLen - prefix.length - 1) + prefix) diff --git a/tests/neg/magic-offset-header-a.check b/tests/neg/magic-offset-header-a.check new file mode 100644 index 000000000000..c4db6f3c5a12 --- /dev/null +++ b/tests/neg/magic-offset-header-a.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-a.scala:6:19 ---------------------------------------------- +1 |def test1(): Int = "无穷" // error + | ^^^^ + | Found: ("无穷" : String) + | Required: Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-a.scala b/tests/neg/magic-offset-header-a.scala new file mode 100644 index 000000000000..f125cbb56b70 --- /dev/null +++ b/tests/neg/magic-offset-header-a.scala @@ -0,0 +1,6 @@ +//> using options -Xmagic-offset-header:///TEST_MARKER +val t1 = 1 +val t2 = 2 +val t3 = 3 +///TEST_MARKER +def test1(): Int = "无穷" // error diff --git a/tests/neg/magic-offset-header-b.check b/tests/neg/magic-offset-header-b.check new file mode 100644 index 000000000000..aca2ee070145 --- /dev/null +++ b/tests/neg/magic-offset-header-b.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b.scala:3:13 ---------------------------------------------- +3 |def x: Int = true // error + | ^^^^ + | Found: (true : Boolean) + | Required: Int + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b.scala:7:13 ---------------------------------------------- +2 |def y: Int = false // error + | ^^^^^ + | Found: (false : Boolean) + | Required: Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-b.scala b/tests/neg/magic-offset-header-b.scala new file mode 100644 index 000000000000..a498335ecd31 --- /dev/null +++ b/tests/neg/magic-offset-header-b.scala @@ -0,0 +1,7 @@ +//> using options -Xmagic-offset-header:///TEST_MARKER + +def x: Int = true // error + +///TEST_MARKER + +def y: Int = false // error diff --git a/tests/neg/magic-offset-header-c.check b/tests/neg/magic-offset-header-c.check new file mode 100644 index 000000000000..504b65b7f95b --- /dev/null +++ b/tests/neg/magic-offset-header-c.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-c.scala:8:18 ---------------------------------------------- +3 | val x: String = 0 // error + | ^ + | Found: (0 : Int) + | Required: String + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-c.scala b/tests/neg/magic-offset-header-c.scala new file mode 100644 index 000000000000..0e1a1bfca9e3 --- /dev/null +++ b/tests/neg/magic-offset-header-c.scala @@ -0,0 +1,8 @@ +//> using options -Xmagic-offset-header:///SOURCE_CODE_START_MARKER + +val generatedCode = 123 + +///SOURCE_CODE_START_MARKER + +def userCode = + val x: String = 0 // error From eaebc99ca12f71bd763181e892ccd9c2d98a67cb Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Fri, 25 Jul 2025 15:50:06 +0800 Subject: [PATCH 013/128] Move magic header setting to -Y and add path setting [Cherry-picked d0ed7118baeecd0c2861ab302ccad592ac93f6e6] --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 1304f31ed9cd..bad59637254b 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -341,7 +341,6 @@ private sealed trait XSettings: val Xdumpclasses: Setting[String] = StringSetting(AdvancedSetting, "Xdump-classes", "dir", "Dump the generated bytecode to .class files (useful for reflective compilation that utilizes in-memory classloaders).", "") val XjarCompressionLevel: Setting[Int] = IntChoiceSetting(AdvancedSetting, "Xjar-compression-level", "compression level to use when writing jar files", Deflater.DEFAULT_COMPRESSION to Deflater.BEST_COMPRESSION, Deflater.DEFAULT_COMPRESSION) val XkindProjector: Setting[String] = ChoiceSetting(AdvancedSetting, "Xkind-projector", "[underscores, enable, disable]", "Allow `*` as type lambda placeholder to be compatible with kind projector. When invoked as -Xkind-projector:underscores will repurpose `_` to be a type parameter placeholder, this will disable usage of underscore as a wildcard.", List("disable", "", "underscores"), "disable", legacyArgs = true) - val XmagicOffsetHeader: Setting[String] = StringSetting(AdvancedSetting, "Xmagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts. Example: -Xmagic-offset-header:///SOURCE_CODE_START", "") /** Documentation related settings */ val XdropComments: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xdrop-docs", "Drop documentation when scanning source files.", aliases = List("-Xdrop-comments")) @@ -445,6 +444,9 @@ private sealed trait YSettings: val YbestEffort: Setting[Boolean] = BooleanSetting(ForkSetting, "Ybest-effort", "Enable best-effort compilation attempting to produce betasty to the META-INF/best-effort directory, regardless of errors, as part of the pickler phase.") val YwithBestEffortTasty: Setting[Boolean] = BooleanSetting(ForkSetting, "Ywith-best-effort-tasty", "Allow to compile using best-effort tasty files. If such file is used, the compiler will stop after the pickler phase.") + val YmagicOffsetHeader: Setting[String] = StringSetting(AdvancedSetting, "Ymagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts. Example: -Ymagic-offset-header:SOURCE_CODE_START", "") + val YoriginalSourceHeader: Setting[String] = StringSetting(AdvancedSetting, "Yoriginal-source-header", "header", "Specify the magic header comment that marks the path of the original code in generated wrapper scripts. Example: -Ymagic-offset-header:ORIGINAL_SOURCE_CODE_PATH", "") + // Experimental language features @deprecated(message = "This flag has no effect and will be removed in a future version.", since = "3.7.0") val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-kind-polymorphism", "Disable kind polymorphism. (This flag has no effect)", deprecation = Deprecation.removed()) From c945ae8001398d8fbf95b1f7a1980d4679aebf12 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 31 Jul 2025 16:00:01 +0800 Subject: [PATCH 014/128] Cleanup [Cherry-picked 36aac1e660ac4d96705683c7384b6bd1baa55f87] --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 3 +-- .../src/dotty/tools/dotc/reporting/MessageRendering.scala | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index bad59637254b..7e806fbd65f2 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -444,8 +444,7 @@ private sealed trait YSettings: val YbestEffort: Setting[Boolean] = BooleanSetting(ForkSetting, "Ybest-effort", "Enable best-effort compilation attempting to produce betasty to the META-INF/best-effort directory, regardless of errors, as part of the pickler phase.") val YwithBestEffortTasty: Setting[Boolean] = BooleanSetting(ForkSetting, "Ywith-best-effort-tasty", "Allow to compile using best-effort tasty files. If such file is used, the compiler will stop after the pickler phase.") - val YmagicOffsetHeader: Setting[String] = StringSetting(AdvancedSetting, "Ymagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts. Example: -Ymagic-offset-header:SOURCE_CODE_START", "") - val YoriginalSourceHeader: Setting[String] = StringSetting(AdvancedSetting, "Yoriginal-source-header", "header", "Specify the magic header comment that marks the path of the original code in generated wrapper scripts. Example: -Ymagic-offset-header:ORIGINAL_SOURCE_CODE_PATH", "") + val YmagicOffsetHeader: Setting[String] = StringSetting(AdvancedSetting, "Ymagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts. Example: -Ymagic-offset-header:SOURCE_CODE_START. Then, in the source, the magic comment `///SOURCE_CODE_START` marks the start of user code. The comment can be optionally suffixed by `:` to indicate the original file.", "") // Experimental language features @deprecated(message = "This flag has no effect and will be removed in a future version.", since = "3.7.0") diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index dcb9ddcb2d06..0414bdb3c58b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -11,7 +11,7 @@ import core.Decorators.* import printing.Highlighting.{Blue, Red, Yellow} import printing.SyntaxHighlighting import Diagnostic.* -import util.{ SourcePosition, NoSourcePosition, WrappedSourceFile } +import util.{SourcePosition, NoSourcePosition} import util.Chars.{ LF, CR, FF, SU } import scala.annotation.switch @@ -44,10 +44,7 @@ trait MessageRendering { var maxLen = Int.MinValue def render(offsetAndLine: (Int, String)): String = { val (offset1, line) = offsetAndLine - var magicOffset = WrappedSourceFile.locateMagicHeader(pos.source).getOrElse(0) - val lineId = pos.source.offsetToLine(offset1) - if lineId < magicOffset then magicOffset = 0 - val lineNbr = (lineId + 1 - magicOffset).toString + val lineNbr = (pos.source.offsetToLine(offset1) + 1).toString val prefix = String.format(s"%${offset - 2}s |", lineNbr) maxLen = math.max(maxLen, prefix.length) val lnum = hl(" " * math.max(0, maxLen - prefix.length - 1) + prefix) From 21d347a1c765115c4f2072c9fee9983429d444a0 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 31 Jul 2025 17:27:47 +0800 Subject: [PATCH 015/128] Reposition span to original file for wrapper scripts [Cherry-picked 48c432238b47d85f14b932c1f4eb85a6fc5cefed] --- .../src/dotty/tools/dotc/ast/Positioned.scala | 10 ++++- .../tools/dotc/config/ScalaSettings.scala | 2 +- .../dotty/tools/dotc/util/SourceFile.scala | 38 +++++++++++++------ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index d8017783f47f..0ab3e1ea6072 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -3,7 +3,8 @@ package dotc package ast import util.Spans.* -import util.{SourceFile, SourcePosition, SrcPos} +import util.{SourceFile, SourcePosition, SrcPos, WrappedSourceFile} +import WrappedSourceFile.MagicHeaderInfo, MagicHeaderInfo.* import core.Contexts.* import core.Decorators.* import core.NameOps.* @@ -51,7 +52,12 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src def source: SourceFile = mySource - def sourcePos(using Context): SourcePosition = source.atSpan(span) + def sourcePos(using Context): SourcePosition = + val info = WrappedSourceFile.locateMagicHeader(source) + info match + case HasHeader(offset, originalFile) => + originalFile.atSpan(span `shift` -offset) + case _ => source.atSpan(span) /** This positioned item, widened to `SrcPos`. Used to make clear we only need the * position, typically for error reporting. diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 7e806fbd65f2..a3fa5fd9bb06 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -444,7 +444,7 @@ private sealed trait YSettings: val YbestEffort: Setting[Boolean] = BooleanSetting(ForkSetting, "Ybest-effort", "Enable best-effort compilation attempting to produce betasty to the META-INF/best-effort directory, regardless of errors, as part of the pickler phase.") val YwithBestEffortTasty: Setting[Boolean] = BooleanSetting(ForkSetting, "Ywith-best-effort-tasty", "Allow to compile using best-effort tasty files. If such file is used, the compiler will stop after the pickler phase.") - val YmagicOffsetHeader: Setting[String] = StringSetting(AdvancedSetting, "Ymagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts. Example: -Ymagic-offset-header:SOURCE_CODE_START. Then, in the source, the magic comment `///SOURCE_CODE_START` marks the start of user code. The comment can be optionally suffixed by `:` to indicate the original file.", "") + val YmagicOffsetHeader: Setting[String] = StringSetting(ForkSetting, "Ymagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts. Example: -Ymagic-offset-header:SOURCE_CODE_START. Then, in the source, the magic comment `///SOURCE_CODE_START:` marks the start of user code. The comment should be suffixed by `:` to indicate the original file.", "") // Experimental language features @deprecated(message = "This flag has no effect and will be removed in a future version.", since = "3.7.0") diff --git a/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index 971b614c085a..9cc3dc5e731d 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -7,6 +7,7 @@ import scala.language.unsafeNulls import dotty.tools.io.* import Spans.* import core.Contexts.* +import core.Decorators.* import scala.io.Codec import Chars.* @@ -62,19 +63,34 @@ object ScriptSourceFile { } object WrappedSourceFile: - private val cache: mutable.HashMap[SourceFile, Int] = mutable.HashMap.empty - def locateMagicHeader(sourceFile: SourceFile)(using Context): Option[Int] = - def findOffset: Int = - val magicHeader = ctx.settings.XmagicOffsetHeader.value - if magicHeader.isEmpty then - -1 + enum MagicHeaderInfo: + case HasHeader(offset: Int, originalFile: SourceFile) + case NoHeader + import MagicHeaderInfo.* + + private val cache: mutable.HashMap[SourceFile, MagicHeaderInfo] = mutable.HashMap.empty + + def locateMagicHeader(sourceFile: SourceFile)(using Context): MagicHeaderInfo = + def findOffset: MagicHeaderInfo = + val magicHeader = ctx.settings.YmagicOffsetHeader.value + if magicHeader.isEmpty then NoHeader else - val s = new String(sourceFile.content) - val regex = ("(?m)^" + java.util.regex.Pattern.quote(magicHeader) + "$").r - val pos = regex.findFirstMatchIn(s).map(_.start).map(sourceFile.offsetToLine(_)) - pos.getOrElse(-1) + val text = new String(sourceFile.content) + val headerQuoted = java.util.regex.Pattern.quote("///" + magicHeader) + val regex = s"(?m)^$headerQuoted:(.+)$$".r + regex.findFirstMatchIn(text) match + case Some(m) => + val markerOffset = m.start + val sourceStartOffset = sourceFile.nextLine(markerOffset) + val file = ctx.getFile(m.group(1)) + if file.exists then + HasHeader(sourceStartOffset, ctx.getSource(file)) + else + report.warning(em"original source file not found: ${file.path}") + NoHeader + case None => NoHeader val result = cache.getOrElseUpdate(sourceFile, findOffset) - if result >= 0 then Some(result + 1) else None + result class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends interfaces.SourceFile { import SourceFile.* From 1258fc166b0cd8f9a7781b7bebe439411755fd57 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 31 Jul 2025 23:37:42 +0800 Subject: [PATCH 016/128] Adapt test cases [Cherry-picked 7eaf07acc07ae212472da1e3cc414435334b44b5] --- tests/neg/magic-offset-header-a.scala | 6 +----- ...header-a.check => magic-offset-header-a_wrapper.check} | 4 ++-- tests/neg/magic-offset-header-a_wrapper.scala | 7 +++++++ tests/neg/magic-offset-header-b.scala | 5 ----- ...header-b.check => magic-offset-header-b_wrapper.check} | 4 ++-- tests/neg/magic-offset-header-b_wrapper.scala | 7 +++++++ tests/neg/magic-offset-header-c.check | 7 ------- tests/neg/magic-offset-header-c.scala | 5 ----- tests/neg/magic-offset-header-c_wrapper.check | 8 ++++++++ tests/neg/magic-offset-header-c_wrapper.scala | 8 ++++++++ 10 files changed, 35 insertions(+), 26 deletions(-) rename tests/neg/{magic-offset-header-a.check => magic-offset-header-a_wrapper.check} (71%) create mode 100644 tests/neg/magic-offset-header-a_wrapper.scala rename tests/neg/{magic-offset-header-b.check => magic-offset-header-b_wrapper.check} (71%) create mode 100644 tests/neg/magic-offset-header-b_wrapper.scala delete mode 100644 tests/neg/magic-offset-header-c.check create mode 100644 tests/neg/magic-offset-header-c_wrapper.check create mode 100644 tests/neg/magic-offset-header-c_wrapper.scala diff --git a/tests/neg/magic-offset-header-a.scala b/tests/neg/magic-offset-header-a.scala index f125cbb56b70..48267a7853f0 100644 --- a/tests/neg/magic-offset-header-a.scala +++ b/tests/neg/magic-offset-header-a.scala @@ -1,6 +1,2 @@ -//> using options -Xmagic-offset-header:///TEST_MARKER -val t1 = 1 -val t2 = 2 -val t3 = 3 -///TEST_MARKER + def test1(): Int = "无穷" // error diff --git a/tests/neg/magic-offset-header-a.check b/tests/neg/magic-offset-header-a_wrapper.check similarity index 71% rename from tests/neg/magic-offset-header-a.check rename to tests/neg/magic-offset-header-a_wrapper.check index c4db6f3c5a12..0ab253c12804 100644 --- a/tests/neg/magic-offset-header-a.check +++ b/tests/neg/magic-offset-header-a_wrapper.check @@ -1,5 +1,5 @@ --- [E007] Type Mismatch Error: tests/neg/magic-offset-header-a.scala:6:19 ---------------------------------------------- -1 |def test1(): Int = "无穷" // error +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-a.scala:2:19 ---------------------------------------------- +2 |def test1(): Int = "无穷" // error | ^^^^ | Found: ("无穷" : String) | Required: Int diff --git a/tests/neg/magic-offset-header-a_wrapper.scala b/tests/neg/magic-offset-header-a_wrapper.scala new file mode 100644 index 000000000000..af4f2b8bf8dd --- /dev/null +++ b/tests/neg/magic-offset-header-a_wrapper.scala @@ -0,0 +1,7 @@ +//> using options -Ymagic-offset-header:TEST_MARKER +val t1 = 1 +val t2 = 2 +val t3 = 3 +///TEST_MARKER:tests/neg/magic-offset-header-a.scala + +def test1(): Int = "无穷" // anypos-error diff --git a/tests/neg/magic-offset-header-b.scala b/tests/neg/magic-offset-header-b.scala index a498335ecd31..aeb569272523 100644 --- a/tests/neg/magic-offset-header-b.scala +++ b/tests/neg/magic-offset-header-b.scala @@ -1,7 +1,2 @@ -//> using options -Xmagic-offset-header:///TEST_MARKER - -def x: Int = true // error - -///TEST_MARKER def y: Int = false // error diff --git a/tests/neg/magic-offset-header-b.check b/tests/neg/magic-offset-header-b_wrapper.check similarity index 71% rename from tests/neg/magic-offset-header-b.check rename to tests/neg/magic-offset-header-b_wrapper.check index aca2ee070145..5d862e5a6b10 100644 --- a/tests/neg/magic-offset-header-b.check +++ b/tests/neg/magic-offset-header-b_wrapper.check @@ -1,11 +1,11 @@ --- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b.scala:3:13 ---------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b_wrapper.scala:3:13 -------------------------------------- 3 |def x: Int = true // error | ^^^^ | Found: (true : Boolean) | Required: Int | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b.scala:7:13 ---------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b.scala:2:13 ---------------------------------------------- 2 |def y: Int = false // error | ^^^^^ | Found: (false : Boolean) diff --git a/tests/neg/magic-offset-header-b_wrapper.scala b/tests/neg/magic-offset-header-b_wrapper.scala new file mode 100644 index 000000000000..ef0e552948d3 --- /dev/null +++ b/tests/neg/magic-offset-header-b_wrapper.scala @@ -0,0 +1,7 @@ +//> using options -Ymagic-offset-header:TEST_MARKER + +def x: Int = true // error + +///TEST_MARKER:tests/neg/magic-offset-header-b.scala + +def y: Int = false // anypos-error diff --git a/tests/neg/magic-offset-header-c.check b/tests/neg/magic-offset-header-c.check deleted file mode 100644 index 504b65b7f95b..000000000000 --- a/tests/neg/magic-offset-header-c.check +++ /dev/null @@ -1,7 +0,0 @@ --- [E007] Type Mismatch Error: tests/neg/magic-offset-header-c.scala:8:18 ---------------------------------------------- -3 | val x: String = 0 // error - | ^ - | Found: (0 : Int) - | Required: String - | - | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-c.scala b/tests/neg/magic-offset-header-c.scala index 0e1a1bfca9e3..be3cb333abff 100644 --- a/tests/neg/magic-offset-header-c.scala +++ b/tests/neg/magic-offset-header-c.scala @@ -1,8 +1,3 @@ -//> using options -Xmagic-offset-header:///SOURCE_CODE_START_MARKER - -val generatedCode = 123 - -///SOURCE_CODE_START_MARKER def userCode = val x: String = 0 // error diff --git a/tests/neg/magic-offset-header-c_wrapper.check b/tests/neg/magic-offset-header-c_wrapper.check new file mode 100644 index 000000000000..ee2eb879cf34 --- /dev/null +++ b/tests/neg/magic-offset-header-c_wrapper.check @@ -0,0 +1,8 @@ +bad option '-Xmagic-offset-header:SOURCE_CODE_START_MARKER' was ignored +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-c_wrapper.scala:8:18 -------------------------------------- +8 | val x: String = 0 // anypos-error + | ^ + | Found: (0 : Int) + | Required: String + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-c_wrapper.scala b/tests/neg/magic-offset-header-c_wrapper.scala new file mode 100644 index 000000000000..99ddd5b86df2 --- /dev/null +++ b/tests/neg/magic-offset-header-c_wrapper.scala @@ -0,0 +1,8 @@ +//> using options -Xmagic-offset-header:SOURCE_CODE_START_MARKER + +val generatedCode = 123 + +///SOURCE_CODE_START_MARKER:tests/neg/magic-offset-header-c.scala + +def userCode = + val x: String = 0 // anypos-error From 31bf8c32f5b4e2e5d912b284d08be2a8feb34aaf Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 31 Jul 2025 23:39:52 +0800 Subject: [PATCH 017/128] Only shift spans in user code [Cherry-picked 438b4c159758206484c150fdddc27dea7374e2ed] --- compiler/src/dotty/tools/dotc/ast/Positioned.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index 0ab3e1ea6072..5b57733eaeb1 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -56,7 +56,10 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src val info = WrappedSourceFile.locateMagicHeader(source) info match case HasHeader(offset, originalFile) => - originalFile.atSpan(span `shift` -offset) + if span.start >= offset then // This span is in user code + originalFile.atSpan(span.shift(-offset)) + else // Otherwise, return the source position in the wrapper code + source.atSpan(span) case _ => source.atSpan(span) /** This positioned item, widened to `SrcPos`. Used to make clear we only need the From 10f09410e16f4d3620c9fd894240efe753b9c28b Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 31 Jul 2025 23:44:08 +0800 Subject: [PATCH 018/128] Add test on missing original file [Cherry-picked cc8b04a4976f66a1dffb2a2ba9d0d62698d87a23] --- tests/neg/magic-offset-header-c_wrapper.check | 5 ++--- tests/neg/magic-offset-header-c_wrapper.scala | 2 +- tests/neg/magic-offset-header-d_wrapper.check | 15 +++++++++++++++ tests/neg/magic-offset-header-d_wrapper.scala | 5 +++++ 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 tests/neg/magic-offset-header-d_wrapper.check create mode 100644 tests/neg/magic-offset-header-d_wrapper.scala diff --git a/tests/neg/magic-offset-header-c_wrapper.check b/tests/neg/magic-offset-header-c_wrapper.check index ee2eb879cf34..0c33f5ea0338 100644 --- a/tests/neg/magic-offset-header-c_wrapper.check +++ b/tests/neg/magic-offset-header-c_wrapper.check @@ -1,6 +1,5 @@ -bad option '-Xmagic-offset-header:SOURCE_CODE_START_MARKER' was ignored --- [E007] Type Mismatch Error: tests/neg/magic-offset-header-c_wrapper.scala:8:18 -------------------------------------- -8 | val x: String = 0 // anypos-error +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-c.scala:3:18 ---------------------------------------------- +3 | val x: String = 0 // error | ^ | Found: (0 : Int) | Required: String diff --git a/tests/neg/magic-offset-header-c_wrapper.scala b/tests/neg/magic-offset-header-c_wrapper.scala index 99ddd5b86df2..51804a647fbe 100644 --- a/tests/neg/magic-offset-header-c_wrapper.scala +++ b/tests/neg/magic-offset-header-c_wrapper.scala @@ -1,4 +1,4 @@ -//> using options -Xmagic-offset-header:SOURCE_CODE_START_MARKER +//> using options -Ymagic-offset-header:SOURCE_CODE_START_MARKER val generatedCode = 123 diff --git a/tests/neg/magic-offset-header-d_wrapper.check b/tests/neg/magic-offset-header-d_wrapper.check new file mode 100644 index 000000000000..36421e857a1c --- /dev/null +++ b/tests/neg/magic-offset-header-d_wrapper.check @@ -0,0 +1,15 @@ +original source file not found: something_nonexist.scala +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-d_wrapper.scala:3:20 -------------------------------------- +3 |def test1: String = 0 // error + | ^ + | Found: (0 : Int) + | Required: String + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-d_wrapper.scala:5:17 -------------------------------------- +5 |def test2: Int = "0" // error + | ^^^ + | Found: ("0" : String) + | Required: Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-d_wrapper.scala b/tests/neg/magic-offset-header-d_wrapper.scala new file mode 100644 index 000000000000..85840e84b702 --- /dev/null +++ b/tests/neg/magic-offset-header-d_wrapper.scala @@ -0,0 +1,5 @@ +//> using options -Ymagic-offset-header:SOURCE_CODE_START_MARKER + +def test1: String = 0 // error +///SOURCE_CODE_START_MARKER:something_nonexist.scala +def test2: Int = "0" // error From 254874d5e8ffe92f635a3359900169907507d92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zieli=C5=84ski=20Patryk?= <75637004+zielinsky@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:48:43 +0200 Subject: [PATCH 019/128] Handle default arguments in named parameters for inlay hints (#23641) Handle default arguments in named parameters for inlay hints [Cherry-picked a115c8d0ea4c4a5ab1cb775b2a15be2ef1e1cbfc] --- .../dotty/tools/pc/PcInlayHintsProvider.scala | 13 ++++- .../pc/tests/inlayHints/InlayHintsSuite.scala | 57 +++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala index b22b3ae8b7f4..29396a5c0d32 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala @@ -19,6 +19,7 @@ import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.NameOps.fieldName import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.core.NameKinds.DefaultGetterName import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.core.Types.* @@ -457,6 +458,13 @@ object Parameters: case TypeApply(fun, _) => getUnderlyingFun(fun) case t => t + @tailrec + def isDefaultArg(arg: Tree): Boolean = arg match + case Ident(name) => name.is(DefaultGetterName) + case Select(_, name) => name.is(DefaultGetterName) + case Apply(fun, _) => isDefaultArg(fun) + case _ => false + if (params.namedParameters() || params.byNameParameters()) then tree match case Apply(fun, args) if isRealApply(fun) => @@ -467,15 +475,16 @@ object Parameters: val funTp = fun.typeOpt.widenTermRefExpr val paramNames = funTp.paramNamess.flatten val paramInfos = funTp.paramInfoss.flatten + Some( - // Check if the function is an infix function or the underlying function is an infix function isInfixFun(fun, args) || underlyingFun.isInfix, ( args .zip(paramNames) .zip(paramInfos) .collect { - case ((arg, paramName), paramInfo) if !arg.span.isZeroExtent => (paramName.fieldName, arg.sourcePos, paramInfo.isByName) + case ((arg, paramName), paramInfo) if !arg.span.isZeroExtent && !isDefaultArg(arg) => + (paramName.fieldName, arg.sourcePos, paramInfo.isByName) } ) ) 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 bb79c19f5823..d9c10080581f 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala @@ -1277,4 +1277,61 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |} |""".stripMargin ) + + @Test def `default-parameter` = + check( + """|object Main { + | def foo(a: Int, b: Int = 2) = a + b + | val x = foo(1) + |} + |""".stripMargin, + """|object Main { + | def foo(a: Int, b: Int = 2)/*: Int<>*/ = a + b + | val x/*: Int<>*/ = foo(/*a = */1) + |} + |""".stripMargin + ) + + @Test def `default-parameter-2` = + check( + """|object Main { + | def foo(a: Int = 10, b: Int = 2) = a + b + | val x = foo(b = 1) + |} + |""".stripMargin, + """|object Main { + | def foo(a: Int = 10, b: Int = 2)/*: Int<>*/ = a + b + | val x/*: Int<>*/ = foo(b = 1) + |} + |""".stripMargin + ) + + @Test def `default-parameter-3` = + check( + """|object Main { + | def foo(a: Int, b: Int = 2, c: Int) = a + b + c + | val x = foo(a = 1, c = 2) + |} + |""".stripMargin, + """|object Main { + | def foo(a: Int, b: Int = 2, c: Int)/*: Int<>*/ = a + b + c + | val x/*: Int<>*/ = foo(a = 1, c = 2) + |} + |""".stripMargin + ) + + @Test def `default-parameter-4` = + check( + """|object Main { + | def foo(a: Int, b: Int = 2, c: Int) = a + b + c + | val x = foo(1, 2, 3) + |} + |""".stripMargin, + """|object Main { + | def foo(a: Int, b: Int = 2, c: Int)/*: Int<>*/ = a + b + c + | val x/*: Int<>*/ = foo(/*a = */1, /*b = */2, /*c = */3) + |} + |""".stripMargin + ) + } From ac9c7441a4e4164eb94b839fcc299898fd710b48 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 4 Aug 2025 15:48:55 +0200 Subject: [PATCH 020/128] Add release notes for 3.7.3-RC1 --- changelogs/3.7.3-RC1.md | 224 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 changelogs/3.7.3-RC1.md diff --git a/changelogs/3.7.3-RC1.md b/changelogs/3.7.3-RC1.md new file mode 100644 index 000000000000..25a2526d8e50 --- /dev/null +++ b/changelogs/3.7.3-RC1.md @@ -0,0 +1,224 @@ +# Release highlights + +- Standardize on `-Vprint:...` (still support `-Xprint:...` as alias) [#22828](https://github.com/scala/scala3/pull/22828) + +# Other changes and fixes + +## Desugaring + +- Fix #23224: Optimize simple tuple extraction [#23373](https://github.com/scala/scala3/pull/23373) + +## Enums + +- Make hashcode of enum items stable [#23218](https://github.com/scala/scala3/pull/23218) + +## Erasure + +- Replace erased class modifiers with Erased base traits [#23447](https://github.com/scala/scala3/pull/23447) +- Bring back part of PruneErasedDefs [#23466](https://github.com/scala/scala3/pull/23466) + +## Experimental: Capture Checking + +- Fix parsing crash for update in later phases [#23390](https://github.com/scala/scala3/pull/23390) +- Implement boxing for singleton type arguments [#23418](https://github.com/scala/scala3/pull/23418) +- Expand Capability types also in arguments of Capability classes [#23427](https://github.com/scala/scala3/pull/23427) +- Adjustments to the capability trilogy [#23428](https://github.com/scala/scala3/pull/23428) +- Set context owner to the method for `paramsToCap` [#23436](https://github.com/scala/scala3/pull/23436) +- Flatten nested capture sets in retainedElementsRaw [#23571](https://github.com/scala/scala3/pull/23571) +- Fix well-formed test for capabilities [#23393](https://github.com/scala/scala3/pull/23393) +- Add restricted capabilities `x.only[C]` [#23485](https://github.com/scala/scala3/pull/23485) +- Rely on hidden sets for use checking [#23580](https://github.com/scala/scala3/pull/23580) + +## Experimental: Seperation Checking + +- Make separation checking controlled by language import [#23560](https://github.com/scala/scala3/pull/23560) + +## Experimental: Erased Definitions + +- Refactorings and fixes to erased definition handling [#23404](https://github.com/scala/scala3/pull/23404) + +## Experimental: Explicit Nulls + +- Add quick fix to remove unnecessary .nn [#23461](https://github.com/scala/scala3/pull/23461) +- Add `stableNull` annotation to force tracking mutable fields [#23528](https://github.com/scala/scala3/pull/23528) + +## Experimental: Global Initialization + +- Rewrite resolveThis in global init checker [#23282](https://github.com/scala/scala3/pull/23282) +- Fix errors in the global initialization checker when compiling bootstrapped dotty [#23429](https://github.com/scala/scala3/pull/23429) +- Fix error in product-sequence match in global init checker [#23480](https://github.com/scala/scala3/pull/23480) + +## Experimental: Into + +- Fix isConversionTargetType test [#23401](https://github.com/scala/scala3/pull/23401) + +## Experimental: Modularity + +- Refinements to skolemizaton [#23513](https://github.com/scala/scala3/pull/23513) + +## Experimental: Unroll + +- Enable UnrollDefinitions phase in REPL frontend phases [#23433](https://github.com/scala/scala3/pull/23433) + +## Extension Methods + +- Avoid forcing extension on check of local select [#23439](https://github.com/scala/scala3/pull/23439) + +## Implicits + +- Refine implicit search fallbacks for better ClassTag handling [#23532](https://github.com/scala/scala3/pull/23532) + +## Inline + +- Fix Symbol.info remapping in TreeTypeMap [#23432](https://github.com/scala/scala3/pull/23432) +- Fail not inlined inline method calls early [#22925](https://github.com/scala/scala3/pull/22925) +- Fix inline export forwarder generation regression [#23126](https://github.com/scala/scala3/pull/23126) + +## Linting + +- Consider setter of effectively private var [#23211](https://github.com/scala/scala3/pull/23211) +- Add accessible check for import usage [#23348](https://github.com/scala/scala3/pull/23348) +- Check OrType in interpolated toString lint [#23365](https://github.com/scala/scala3/pull/23365) +- Use result of lambda type of implicit in CheckUnused [#23497](https://github.com/scala/scala3/pull/23497) + +## Match Types + +- Fix: #23261 Distinguish 0.0 and -0.0 in ConstantType match types [#23265](https://github.com/scala/scala3/pull/23265) + +## Named Tuples + +- Skip bypassing unapply for scala 2 case classes to allow for single-element named tuple in unapply [#23603](https://github.com/scala/scala3/pull/23603) + +## Parser + +- Enforce `-new-syntax` under `-language:future` [#23443](https://github.com/scala/scala3/pull/23443) +- Disallow Scala 2 implicits under `-source:future` [#23472](https://github.com/scala/scala3/pull/23472) + +## Pattern Matching + +- Fix problems in checking that a constructor is uninhabited for exhaustive match checking [#23403](https://github.com/scala/scala3/pull/23403) + +## Pickling + +- Don't force annotation unpickling when testing for SilentIntoAnnot [#23506](https://github.com/scala/scala3/pull/23506) +- Drop invalid assumption from TastyUnpickler [#23353](https://github.com/scala/scala3/pull/23353) + +## Printer + +- Print update modifier when printing update method definitions [#23392](https://github.com/scala/scala3/pull/23392) + +## Positions + +- Compare span points in pathTo to determine best span [#23581](https://github.com/scala/scala3/pull/23581) +- Add line number magic comment support [#23549](https://github.com/scala/scala3/pull/23549) + +## Presentation Compiler + +- Port Inlay hints for name parameters [#23375](https://github.com/scala/scala3/pull/23375) +- Fix: Simplify infer type for apply [#23434](https://github.com/scala/scala3/pull/23434) +- Fix: Inconsistent annotation tooltips [#23454](https://github.com/scala/scala3/pull/23454) +- Fix adjust type when already exists [#23455](https://github.com/scala/scala3/pull/23455) +- Exclude named parameters inlay hints for java defined [#23462](https://github.com/scala/scala3/pull/23462) +- Fix: StringIndexOutOfBoundsException in presentation compiler's hasColon method [#23498](https://github.com/scala/scala3/pull/23498) +- Add InferredMethodProvider for automatic method signature generation [#23563](https://github.com/scala/scala3/pull/23563) +- Fix completions for Quotes [#23619](https://github.com/scala/scala3/pull/23619) +- Handle default arguments in named parameters for inlay hints [#23641](https://github.com/scala/scala3/pull/23641) + +## Quotes + +- Skip splice level checking for `` symbols [#22782](https://github.com/scala/scala3/pull/22782) +- Fix stale top level synthetic package object being used in later runs [#23464](https://github.com/scala/scala3/pull/23464) +- Emit an error for quoted pattern type variable after `new` [#23618](https://github.com/scala/scala3/pull/23618) +- Fix issue with certain polyfunctions not properly matching in macros [#23614](https://github.com/scala/scala3/pull/23614) +- Check PCP of constructor calls on the type [#7531](https://github.com/scala/scala3/pull/7531) + +## Reflection + +- Quotes reflect: sort the typeMembers output list and filter out non-members [#22876](https://github.com/scala/scala3/pull/22876) + +## Reporting + +- Add an explainer to the DoubleDefinition error [#23470](https://github.com/scala/scala3/pull/23470) +- Suppress warnings in comprehensions with 22+ binds [#23590](https://github.com/scala/scala3/pull/23590) +- Unhelpful error message when trying to use named extraction, when not matching case class or named tuple [#23354](https://github.com/scala/scala3/pull/23354) +- Improve error message for conflicting definitions [#23453](https://github.com/scala/scala3/pull/23453) +- `-Yprofile-trace` properly report macro splicing source [#23488](https://github.com/scala/scala3/pull/23488) +- `-Yprofile-trace` profiles all inline calls [#23490](https://github.com/scala/scala3/pull/23490) + +## Rewrites + +- Patch empty implicit parens on error recovery [#22835](https://github.com/scala/scala3/pull/22835) +- Rewrite underscore with optional space [#23525](https://github.com/scala/scala3/pull/23525) + +## Scaladoc + +- Scaladoc: fixes and improvements to context bounds and extension methods [#22156](https://github.com/scala/scala3/pull/22156) +- Encode path of class [#23503](https://github.com/scala/scala3/pull/23503) + +## SemanticDB + +- Bugfix: Also save infos in semanticdb [#23587](https://github.com/scala/scala3/pull/23587) + +## Transform + +- Handle multiple type parameter lists in value class methods [#23516](https://github.com/scala/scala3/pull/23516) +- Check path of module prefix for tailrec [#23491](https://github.com/scala/scala3/pull/23491) + +## Tuples + +- Normalize tuple types in var args seq literals and classOf instances [#23465](https://github.com/scala/scala3/pull/23465) + +## Typer + +- Fix #22922: Add TypeParamRef handling in isSingletonBounded [#23501](https://github.com/scala/scala3/pull/23501) +- Fix this references everywhere in dependent function types [#23514](https://github.com/scala/scala3/pull/23514) +- Don't approximate a type using `Nothing` as prefix [#23531](https://github.com/scala/scala3/pull/23531) +- Support cleanup actions in class completers [#23515](https://github.com/scala/scala3/pull/23515) +- Fix regressions in asSeenFrom introduced in 3.7 [#23438](https://github.com/scala/scala3/pull/23438) +- Use correct owner in eta expansion [#7564](https://github.com/scala/scala3/pull/7564) +- Fix irrefutability checking in `for` with untupling [#23273](https://github.com/scala/scala3/pull/23273) +- Fix missing members reporting for var setters [#23476](https://github.com/scala/scala3/pull/23476) +- Guard against invalid prefixes in argForParam [#23508](https://github.com/scala/scala3/pull/23508) +- Add missing case to TypeComparer [#23550](https://github.com/scala/scala3/pull/23550) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.2..3.7.3-RC1` these are: + +``` + 80 Martin Odersky + 56 Hamza Remmal + 22 Wojciech Mazur + 20 noti0na1 + 18 Yichen Xu + 16 Som Snytt + 14 Jan Chyb + 9 Matt Bovel + 7 EnzeXing + 6 Guillaume Martres + 5 Sébastien Doeraene + 5 aherlihy + 4 Zieliński Patryk + 3 Oliver Bračevac + 3 Tomasz Godzik + 2 Alexander + 2 Mikołaj Fornal + 2 Piotr Chabelski + 2 Seyon Sivatharan + 1 Alex1005a + 1 HarrisL2 + 1 Jan + 1 Jentsch + 1 Jędrzej Rochala + 1 Katarzyna Marek + 1 Marc GRIS + 1 Martin Duhem + 1 Patryk Zieliński + 1 Przemysław Sajnóg + 1 Seth Tisue + 1 Wessel W. Bakker + 1 bingchen-li + 1 kijuky +``` From afc19b971774c36ed444b81ab71491900bbc523b Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 4 Aug 2025 15:53:03 +0200 Subject: [PATCH 021/128] Release Scala 3.7.3-RC1 --- project/Build.scala | 4 ++-- tasty/src/dotty/tools/tasty/TastyFormat.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 08433784c4ae..eed594bb3610 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-RC2" + val referenceVersion = "3.7.2" /** Version of the Scala compiler targeted in the current release cycle * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes @@ -90,7 +90,7 @@ object Build { * - in release candidate branch is experimental if {patch == 0} * - in stable release is always non-experimetnal */ - val expectedTastyVersion = "28.8-experimental-1" + val expectedTastyVersion = "28.7" checkReleasedTastyVersion() /** Final version of Scala compiler, controlled by environment variables. */ diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 37e3a3acfdab..4c1453243450 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -324,7 +324,7 @@ object TastyFormat { * compatibility, but remains backwards compatible, with all * preceding `MinorVersion`. */ - final val MinorVersion: Int = 8 + final val MinorVersion: Int = 7 /** Natural Number. The `ExperimentalVersion` allows for * experimentation with changes to TASTy without committing @@ -340,7 +340,7 @@ object TastyFormat { * is able to read final TASTy documents if the file's * `MinorVersion` is strictly less than the current value. */ - final val ExperimentalVersion: Int = 1 + final val ExperimentalVersion: Int = 0 /**This method implements a binary relation (`<:<`) between two TASTy versions. * From 144dbe6fc3f5ecc46092e5f47cc32fd2a6410e6d Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 7 Aug 2025 14:56:46 +0200 Subject: [PATCH 022/128] Backport "Generalize "Don't approximate a type using Nothing as prefix"" to 3.7.3 (#23673) Backports #23628 to 3.7.3-RC2 --- compiler/src/dotty/tools/dotc/core/Types.scala | 12 +++++++++--- tests/pos/i23627.scala | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 tests/pos/i23627.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index a6f59d8b2d33..7fac8c818a1a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -6507,7 +6507,7 @@ object Types extends TypeUtils { protected def range(lo: Type, hi: Type): Type = if variance > 0 then hi else if variance < 0 then - if (lo eq defn.NothingType) && hi.hasSimpleKind then + if (lo eq defn.NothingType) then // Approximate by Nothing & hi instead of just Nothing, in case the // approximated type is used as the prefix of another type (this would // lead to a type with a `NoDenotation` denot and a possible @@ -6518,8 +6518,14 @@ object Types extends TypeUtils { // example if Nothing is the type of a parameter being depended on in // a MethodType) // - // Test case in tests/pos/i23530.scala - AndType(lo, hi) + // Test case in tests/pos/i23530.scala (and tests/pos/i23627.scala for + // the higher-kinded case which requires eta-expansion) + hi.etaExpand match + case expandedHi: HKTypeLambda => + expandedHi.derivedLambdaType(resType = AndType(lo, expandedHi.resType)) + case _ => + // simple-kinded case + AndType(lo, hi) else lo else if lo `eq` hi then lo diff --git a/tests/pos/i23627.scala b/tests/pos/i23627.scala new file mode 100644 index 000000000000..1480a80c9c00 --- /dev/null +++ b/tests/pos/i23627.scala @@ -0,0 +1,18 @@ +trait TestContainer: + trait TestPath[T]: + type AbsMember + + extension (path: TestPath[?]) + infix def ext(color: path.AbsMember): Unit = ??? + infix def ext(other: Int): Unit = ??? + +object Repro: + val dc2: TestContainer = ??? + import dc2.TestPath + + def transition(path: TestPath[?])(using DummyImplicit): TestPath[?] = ??? + + def test: Unit = + val di: TestPath[?] = ??? + // error + val z1 = transition(di).ext(1) From 946ad71d10c2376e6be3304a5cd5cc51830e5fe0 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Sun, 10 Aug 2025 15:19:00 +0200 Subject: [PATCH 023/128] Bump Scala CLI to v1.8.5 (was v1.8.4) [Cherry-picked 679b5009e3599f8d9ce7ebad1d6f4c908c2fa66f] --- .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 376b8817b35e..95eabcebfe0c 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.8.4 + - uses: VirtusLab/scala-cli-setup@v1.8.5 - 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 eed594bb3610..b6acaa25fbae 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.8.4" + val scalaCliLauncherVersion = "1.8.5" /** Version of Coursier to download for initializing the local maven repo of Scala command */ val coursierJarVersion = "2.1.24" From 61d0073d9917b0ae8b864c25ab21905af8462e36 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Aug 2025 19:44:59 +0200 Subject: [PATCH 024/128] More careful ClassTag instantiation We now use a blend of the new scheme and backwards compatible special case if type variables as ClassTag arguments are constrained by further type variables. Fixes #23611 [Cherry-picked b677f97a1fa26e0c27a4f434e850b4c8214d606c] --- compiler/src/dotty/tools/dotc/core/Types.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 7fac8c818a1a..cd5e1c81f667 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5116,11 +5116,17 @@ object Types extends TypeUtils { */ private def currentEntry(using Context): Type = ctx.typerState.constraint.entry(origin) + /** For uninstantiated type variables: the lower bound */ + def lowerBound(using Context): Type = currentEntry.loBound + + /** For uninstantiated type variables: the upper bound */ + def upperBound(using Context): Type = currentEntry.hiBound + /** For uninstantiated type variables: Is the lower bound different from Nothing? */ - def hasLowerBound(using Context): Boolean = !currentEntry.loBound.isExactlyNothing + def hasLowerBound(using Context): Boolean = !lowerBound.isExactlyNothing /** For uninstantiated type variables: Is the upper bound different from Any? */ - def hasUpperBound(using Context): Boolean = !currentEntry.hiBound.isTopOfSomeKind + def hasUpperBound(using Context): Boolean = !upperBound.isTopOfSomeKind /** Unwrap to instance (if instantiated) or origin (if not), until result * is no longer a TypeVar From c31e1b760655ca2f4352d785f066e657a97cc0d2 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 6 Aug 2025 11:39:38 +0200 Subject: [PATCH 025/128] Refine criterion when to use fullyDefinedType in ClassTag search [Cherry-picked e4b8f3c7ad6cb727dafee0509db4fb7a44e3a9c2] --- compiler/src/dotty/tools/dotc/core/Types.scala | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index cd5e1c81f667..7fac8c818a1a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5116,17 +5116,11 @@ object Types extends TypeUtils { */ private def currentEntry(using Context): Type = ctx.typerState.constraint.entry(origin) - /** For uninstantiated type variables: the lower bound */ - def lowerBound(using Context): Type = currentEntry.loBound - - /** For uninstantiated type variables: the upper bound */ - def upperBound(using Context): Type = currentEntry.hiBound - /** For uninstantiated type variables: Is the lower bound different from Nothing? */ - def hasLowerBound(using Context): Boolean = !lowerBound.isExactlyNothing + def hasLowerBound(using Context): Boolean = !currentEntry.loBound.isExactlyNothing /** For uninstantiated type variables: Is the upper bound different from Any? */ - def hasUpperBound(using Context): Boolean = !upperBound.isTopOfSomeKind + def hasUpperBound(using Context): Boolean = !currentEntry.hiBound.isTopOfSomeKind /** Unwrap to instance (if instantiated) or origin (if not), until result * is no longer a TypeVar From 4dd0691be96304859ccc251a8a53c0d0c6d5f71d Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 5 Aug 2025 12:04:55 +0200 Subject: [PATCH 026/128] Use more context for implicit search only if no default argument After an "implicit not found", we type additional arguments to get more context which might give a larger implicit scope to search. With this commit we do that only if there is no default argument for the implicit. This might fix #23610 [Cherry-picked 975c988ed23818c033a433fd91e5c12ec909f359] --- .../src/dotty/tools/dotc/typer/Typer.scala | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c45c00845120..e9e3e22342bf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4362,11 +4362,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val arg = inferImplicitArg(formal, tree.span.endPos) + lazy val defaultArg = findDefaultArgument(argIndex) + .showing(i"default argument: for $formal, $tree, $argIndex = $result", typr) + def argHasDefault = hasDefaultParams && !defaultArg.isEmpty + def canProfitFromMoreConstraints = arg.tpe.isInstanceOf[AmbiguousImplicits] - // ambiguity could be decided by more constraints - || !isFullyDefined(formal, ForceDegree.none) - // more context might constrain type variables which could make implicit scope larger + // Ambiguity could be decided by more constraints + || !isFullyDefined(formal, ForceDegree.none) && !argHasDefault + // More context might constrain type variables which could make implicit scope larger. + // But in this case we should search with additional arguments typed only if there + // is no default argument. arg.tpe match case failed: SearchFailureType if canProfitFromMoreConstraints => @@ -4379,15 +4385,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case failed: AmbiguousImplicits => arg :: implicitArgs(formals1, argIndex + 1, pt) case failed: SearchFailureType => - lazy val defaultArg = findDefaultArgument(argIndex) - .showing(i"default argument: for $formal, $tree, $argIndex = $result", typr) - if !hasDefaultParams || defaultArg.isEmpty then - // no need to search further, the adapt fails in any case - // the reason why we continue inferring arguments in case of an AmbiguousImplicits - // is that we need to know whether there are further errors. - // If there are none, we have to propagate the ambiguity to the caller. - arg :: formals1.map(dummyArg) - else + if argHasDefault then // This is tricky. On the one hand, we need the defaultArg to // correctly type subsequent formal parameters in the same using // clause in case there are parameter dependencies. On the other hand, @@ -4398,6 +4396,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // `if propFail.exists` where we re-type the whole using clause with named // arguments for all implicits that were found. arg :: inferArgsAfter(defaultArg) + else + // no need to search further, the adapt fails in any case + // the reason why we continue inferring arguments in case of an AmbiguousImplicits + // is that we need to know whether there are further errors. + // If there are none, we have to propagate the ambiguity to the caller. + arg :: formals1.map(dummyArg) case _ => arg :: inferArgsAfter(arg) end implicitArgs From 6e675e09cdf46361bca0ab3468e3236f224b525c Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 6 Aug 2025 15:28:55 +0200 Subject: [PATCH 027/128] More careful ClassTag instantiation (#23659) We now use a blend of the new scheme and a backwards compatible special case if type variables as ClassTag arguments are constrained by further type variables. Fixes #23611 [Cherry-picked cfaa5d390dd225e54ea88383d7dd6e07f6f856de] --- .../dotty/tools/dotc/typer/Synthesizer.scala | 32 ++++++++++++++++--- tests/pos/i23611.scala | 26 +++++++++++++++ tests/pos/i23611a.scala | 30 +++++++++++++++++ 3 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 tests/pos/i23611.scala create mode 100644 tests/pos/i23611a.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 8df2fc1ed4b7..0c35d0377e51 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -20,6 +20,7 @@ import ast.tpd.* import Synthesizer.* import sbt.ExtractDependencies.* import xsbti.api.DependencyContext.* +import TypeComparer.{fullLowerBound, fullUpperBound} /** Synthesize terms for special classes */ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): @@ -38,10 +39,32 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): // bounds are usually widened during instantiation. instArg(tp.tp1) case tvar: TypeVar if ctx.typerState.constraint.contains(tvar) => + // If tvar has a lower or upper bound: + // 1. If the bound is not another type variable, use this as approximation. + // 2. Otherwise, if the type can be forced to be fully defined, use that type + // as approximation. + // 3. Otherwise leave argument uninstantiated. + // The reason for (2) is that we observed complicated constraints in i23611.scala + // that get better types if a fully defined type is computed than if several type + // variables are approximated incrementally. This is a minimization of some ZIO code. + // So in order to keep backwards compatibility (where before we _only_ did 2) we + // add that special case. + def isGroundConstr(tp: Type): Boolean = tp.dealias match + case tvar: TypeVar if ctx.typerState.constraint.contains(tvar) => false + case pref: TypeParamRef if ctx.typerState.constraint.contains(pref) => false + case tp: AndOrType => isGroundConstr(tp.tp1) && isGroundConstr(tp.tp2) + case _ => true instArg( - if tvar.hasLowerBound then tvar.instantiate(fromBelow = true) - else if tvar.hasUpperBound then tvar.instantiate(fromBelow = false) - else NoType) + if tvar.hasLowerBound then + if isGroundConstr(fullLowerBound(tvar.origin)) then tvar.instantiate(fromBelow = true) + else if isFullyDefined(tp, ForceDegree.all) then tp + else NoType + else if tvar.hasUpperBound then + if isGroundConstr(fullUpperBound(tvar.origin)) then tvar.instantiate(fromBelow = false) + else if isFullyDefined(tp, ForceDegree.all) then tp + else NoType + else + NoType) case _ => tp @@ -569,9 +592,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): resType <:< target val tparams = poly.paramRefs val variances = childClass.typeParams.map(_.paramVarianceSign) - val instanceTypes = tparams.lazyZip(variances).map((tparam, variance) => + val instanceTypes = tparams.lazyZip(variances).map: (tparam, variance) => TypeComparer.instanceType(tparam, fromBelow = variance < 0, Widen.Unions) - ) val instanceType = resType.substParams(poly, instanceTypes) // this is broken in tests/run/i13332intersection.scala, // because type parameters are not correctly inferred. diff --git a/tests/pos/i23611.scala b/tests/pos/i23611.scala new file mode 100644 index 000000000000..0fef178b9c32 --- /dev/null +++ b/tests/pos/i23611.scala @@ -0,0 +1,26 @@ +import java.io.{File, IOException} +import java.net.URI +import java.nio.file.{Path, Paths} +import scala.reflect.ClassTag + +trait FileConnectors { + def listPath(path: => Path): ZStream[Any, IOException, Path] + + final def listFile(file: => File): ZStream[Any, IOException, File] = + for { + path <- null.asInstanceOf[ZStream[Any, IOException, Path]] + r <- listPath(path).mapZIO(a => ZIO.attempt(a.toFile).refineToOrDie) + } yield r +} + +sealed trait ZIO[-R, +E, +A] +extension [R, E <: Throwable, A](self: ZIO[R, E, A]) + def refineToOrDie[E1 <: E: ClassTag]: ZIO[R, E1, A] = ??? + +object ZIO: + def attempt[A](code: => A): ZIO[Any, Throwable, A] = ??? + +sealed trait ZStream[-R, +E, +A]: + def map[B](f: A => B): ZStream[R, E, B] = ??? + def flatMap[R1 <: R, E1 >: E, B](f: A => ZStream[R1, E1, B]): ZStream[R1, E1, B] + def mapZIO[R1 <: R, E1 >: E, A1](f: A => ZIO[R1, E1, A1]): ZStream[R1, E1, A1] \ No newline at end of file diff --git a/tests/pos/i23611a.scala b/tests/pos/i23611a.scala new file mode 100644 index 000000000000..fbaf709e2f0e --- /dev/null +++ b/tests/pos/i23611a.scala @@ -0,0 +1,30 @@ +import java.io.{File, IOException} +import java.net.URI +import java.nio.file.{Path, Paths} +import scala.reflect.ClassTag + +trait FileConnectors { + def listPath(path: => Path): ZStream[Any, IOException, Path] + + final def listFile(file: => File): ZStream[Any, IOException, File] = + for { + path <- null.asInstanceOf[ZStream[Any, IOException, Path]] + r <- listPath(path).mapZIO(a => ZIO.attempt(a.toFile).refineToOrDie) + } yield r +} + +sealed abstract class CanFail[-E] +object CanFail: + given [E]: CanFail[E] = ??? + +sealed trait ZIO[-R, +E, +A] +extension [R, E <: Throwable, A](self: ZIO[R, E, A]) + def refineToOrDie[E1 <: E: ClassTag](using CanFail[E]): ZIO[R, E1, A] = ??? + +object ZIO: + def attempt[A](code: => A): ZIO[Any, Throwable, A] = ??? + +sealed trait ZStream[-R, +E, +A]: + def map[B](f: A => B): ZStream[R, E, B] = ??? + def flatMap[R1 <: R, E1 >: E, B](f: A => ZStream[R1, E1, B]): ZStream[R1, E1, B] + def mapZIO[R1 <: R, E1 >: E, A1](f: A => ZIO[R1, E1, A1]): ZStream[R1, E1, A1] \ No newline at end of file From 502eb0ba7d70bcfe8763b9b12a7fc77dc761e382 Mon Sep 17 00:00:00 2001 From: som-snytt Date: Wed, 6 Aug 2025 01:58:50 -0700 Subject: [PATCH 028/128] Add suppression if nowarn differs (#23652) Fixes #23651 The [previous fix](https://github.com/scala/scala3/pull/22383) for the same `@nowarn` attached to multiple elements should have compared the `annotPos` to identify duplicates (instead of the target range). This commit defers detecting "bad" or duplicate suppressions (which originate with the same annotation) to report time, after the suppression is "unused"; there are few nowarns per file and fewer that are unused. While checking for a suppression, mark matching unused suppressions as "superseded", so that if they remain unused, the warning can add an "audit" that the nowarn matched a diagnostic (but was superseded by some other nowarn). ~This commit goes further and checks for duplicates (including whether the filters look the same).~ ~If it finds a duplicate where the `annotPos` differs, warn about the user-written annotation.~ ~Filters match each other if they are the same type and, if they have a pattern, the string representations of the patterns are equal.~ [Cherry-picked 40843f748444886114bce7003ff2147d6f00500d] --- compiler/src/dotty/tools/dotc/Run.scala | 38 +++--- .../dotty/tools/dotc/reporting/WConf.scala | 16 ++- tests/neg/nowarn.check | 110 ------------------ tests/neg/nowarn.scala | 89 -------------- tests/warn/i23651.scala | 27 +++++ tests/warn/nowarn.check | 110 ++++++++++++++++++ tests/warn/nowarn.scala | 83 +++++++++++++ 7 files changed, 257 insertions(+), 216 deletions(-) delete mode 100644 tests/neg/nowarn.check delete mode 100644 tests/neg/nowarn.scala create mode 100644 tests/warn/i23651.scala create mode 100644 tests/warn/nowarn.check create mode 100644 tests/warn/nowarn.scala diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index a9f0c677e17f..58ea3e03edba 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -92,14 +92,21 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { mySuspendedMessages.getOrElseUpdate(warning.pos.source, mutable.LinkedHashSet.empty) += warning def nowarnAction(dia: Diagnostic): Action.Warning.type | Action.Verbose.type | Action.Silent.type = - mySuppressions.getOrElse(dia.pos.source, Nil).find(_.matches(dia)) match { - case Some(s) => + mySuppressions.get(dia.pos.source) match + case Some(suppressions) => + val matching = suppressions.iterator.filter(_.matches(dia)) + if matching.hasNext then + val s = matching.next() + for other <- matching do + if !other.used then + other.markSuperseded() // superseded unless marked used later s.markUsed() - if (s.verbose) Action.Verbose + if s.verbose then Action.Verbose else Action.Silent - case _ => + else Action.Warning - } + case none => + Action.Warning def registerNowarn(annotPos: SourcePosition, range: Span)(conf: String, pos: SrcPos)(using Context): Unit = var verbose = false @@ -118,12 +125,10 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { .merge addSuppression: Suppression(annotPos, filters, range.start, range.end, verbose) - .tap: sup => - if filters == List(MessageFilter.None) then sup.markUsed() // invalid suppressions, don't report as unused def addSuppression(sup: Suppression): Unit = val suppressions = mySuppressions.getOrElseUpdate(sup.annotPos.source, ListBuffer.empty) - if sup.start != sup.end && suppressions.forall(x => x.start != sup.start || x.end != sup.end) then + if sup.start != sup.end then suppressions += sup def reportSuspendedMessages(source: SourceFile)(using Context): Unit = { @@ -134,7 +139,8 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { mySuspendedMessages.remove(source).foreach(_.foreach(ctx.reporter.issueIfNotSuppressed)) } - def runFinished(hasErrors: Boolean): Unit = + def runFinished()(using Context): Unit = + val hasErrors = ctx.reporter.hasErrors // report suspended messages (in case the run finished before typer) mySuspendedMessages.keysIterator.toList.foreach(reportSuspendedMessages) // report unused nowarns only if all all phases are done @@ -142,10 +148,16 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { for source <- mySuppressions.keysIterator.toList sups <- mySuppressions.remove(source) - sup <- sups.reverse - if !sup.used do - report.warning("@nowarn annotation does not suppress any warnings", sup.annotPos) + val suppressions = sups.reverse.toList + for sup <- suppressions do + if !sup.used + && !suppressions.exists(s => s.ne(sup) && s.used && s.annotPos == sup.annotPos) // duplicate + && sup.filters != List(MessageFilter.None) // invalid suppression, don't report as unused + then + val more = if sup.superseded then " but matches a diagnostic" else "" + report.warning("@nowarn annotation does not suppress any warnings"+more, sup.annotPos) + end suppressions /** The compilation units currently being compiled, this may return different * results over time. @@ -411,7 +423,7 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { ctx.reporter.finalizeReporting() if (!ctx.reporter.hasErrors) Rewrites.writeBack() - suppressions.runFinished(hasErrors = ctx.reporter.hasErrors) + suppressions.runFinished() while (finalizeActions.nonEmpty && canProgress()) { val action = finalizeActions.remove(0) action() diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index ac25f2f6cd30..cff15aa6dc38 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -10,6 +10,7 @@ import dotty.tools.dotc.interfaces.SourceFile import dotty.tools.dotc.reporting.MessageFilter.SourcePattern import java.util.regex.PatternSyntaxException +import scala.PartialFunction.cond import scala.annotation.internal.sharable import scala.util.matching.Regex @@ -136,13 +137,20 @@ object WConf: if (parseErrorss.nonEmpty) Left(parseErrorss.flatten) else Right(WConf(configs)) -class Suppression(val annotPos: SourcePosition, filters: List[MessageFilter], val start: Int, val end: Int, val verbose: Boolean): - private var _used = false - def used: Boolean = _used +class Suppression(val annotPos: SourcePosition, val filters: List[MessageFilter], val start: Int, val end: Int, val verbose: Boolean): + inline def unusedState = 0 + inline def usedState = 1 + inline def supersededState = 2 + private var _used = unusedState + def used: Boolean = _used == usedState + def superseded: Boolean = _used == supersededState def markUsed(): Unit = - _used = true + _used = usedState + def markSuperseded(): Unit = + _used = supersededState def matches(dia: Diagnostic): Boolean = val pos = dia.pos pos.exists && start <= pos.start && pos.end <= end && filters.forall(_.matches(dia)) override def toString = s"Suppress in ${annotPos.source} $start..$end [${filters.mkString(", ")}]" +end Suppression diff --git a/tests/neg/nowarn.check b/tests/neg/nowarn.check deleted file mode 100644 index ff01de1788bd..000000000000 --- a/tests/neg/nowarn.check +++ /dev/null @@ -1,110 +0,0 @@ --- [E002] Syntax Warning: tests/neg/nowarn.scala:11:10 ----------------------------------------------------------------- -11 |def t1a = try 1 // warning (parser) - | ^^^^^ - | A try without catch or finally is equivalent to putting - | its body in a block; no exceptions are handled. - | - | longer explanation available when compiling with `-explain` --- [E002] Syntax Warning: tests/neg/nowarn.scala:25:25 ----------------------------------------------------------------- -25 |@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) - | ^^^^^ - | A try without catch or finally is equivalent to putting - | its body in a block; no exceptions are handled. - | - | longer explanation available when compiling with `-explain` --- [E002] Syntax Warning: tests/neg/nowarn.scala:33:26 ----------------------------------------------------------------- -33 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) - | ^^^^^ - | A try without catch or finally is equivalent to putting - | its body in a block; no exceptions are handled. - | - | longer explanation available when compiling with `-explain` --- [E002] Syntax Warning: tests/neg/nowarn.scala:35:28 ----------------------------------------------------------------- -35 |@nowarn("verbose") def t5 = try 1 // warning with details - | ^^^^^ - | A try without catch or finally is equivalent to putting - | its body in a block; no exceptions are handled. - |Matching filters for @nowarn or -Wconf: - | - id=E2 - | - name=EmptyCatchAndFinallyBlock - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/nowarn.scala:15:11 -------------------------------------------------------- -15 |def t2 = { 1; 2 } // warning (the invalid nowarn doesn't silence anything) - | ^ - | A pure expression does nothing in statement position - | - | longer explanation available when compiling with `-explain` --- Warning: tests/neg/nowarn.scala:14:8 -------------------------------------------------------------------------------- -14 |@nowarn("wat?") // warning (typer, invalid filter) - | ^^^^^^ - | Invalid message filter - | unknown filter: wat? --- [E129] Potential Issue Warning: tests/neg/nowarn.scala:18:12 -------------------------------------------------------- -18 |def t2a = { 1; 2 } // warning (invalid nowarn doesn't silence) - | ^ - | A pure expression does nothing in statement position - | - | longer explanation available when compiling with `-explain` --- Warning: tests/neg/nowarn.scala:17:8 -------------------------------------------------------------------------------- -17 |@nowarn(t1a.toString) // warning (typer, argument not a compile-time constant) - | ^^^^^^^^^^^^ - | filter needs to be a compile-time constant string --- Warning: tests/neg/nowarn.scala:25:10 ------------------------------------------------------------------------------- -25 |@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) - | ^^^^^ - | filter needs to be a compile-time constant string --- Deprecation Warning: tests/neg/nowarn.scala:39:10 ------------------------------------------------------------------- -39 |def t6a = f // warning (refchecks, deprecation) - | ^ - | method f is deprecated --- Deprecation Warning: tests/neg/nowarn.scala:42:30 ------------------------------------------------------------------- -42 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) - | ^ - | method f is deprecated --- Deprecation Warning: tests/neg/nowarn.scala:49:10 ------------------------------------------------------------------- -49 |def t7c = f // warning (deprecation) - | ^ - | method f is deprecated --- [E092] Pattern Match Unchecked Warning: tests/neg/nowarn.scala:55:7 ------------------------------------------------- -55 | case _: List[Int] => 0 // warning (patmat, unchecked) - | ^ - |the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from Any - | - | longer explanation available when compiling with `-explain` --- Error: tests/neg/nowarn.scala:33:1 ---------------------------------------------------------------------------------- -33 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) - |^^^^^^^^^^^^^^^ - |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:42:1 ---------------------------------------------------------------------------------- -42 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) - |^^^^^^^^^^^^^^^^^^^ - |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:50:5 ---------------------------------------------------------------------------------- -50 | : @nowarn("msg=fish") // error (unused nowarn) - | ^^^^^^^^^^^^^^^^^^^ - | @nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:62:0 ---------------------------------------------------------------------------------- -62 |@nowarn def t9a = { 1: @nowarn; 2 } // error (outer @nowarn is unused) - |^^^^^^^ - |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:63:27 --------------------------------------------------------------------------------- -63 |@nowarn def t9b = { 1: Int @nowarn; 2 } // error (inner @nowarn is unused, it covers the type, not the expression) - | ^^^^^^^ - | @nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:68:0 ---------------------------------------------------------------------------------- -68 |@nowarn @ann(f) def t10b = 0 // error (unused nowarn) - |^^^^^^^ - |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:69:8 ---------------------------------------------------------------------------------- -69 |@ann(f: @nowarn) def t10c = 0 // error (unused nowarn), should be silent - | ^^^^^^^ - | @nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:72:0 ---------------------------------------------------------------------------------- -72 |@nowarn class I1a { // error (unused nowarn) - |^^^^^^^ - |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:77:0 ---------------------------------------------------------------------------------- -77 |@nowarn class I1b { // error (unused nowarn) - |^^^^^^^ - |@nowarn annotation does not suppress any warnings diff --git a/tests/neg/nowarn.scala b/tests/neg/nowarn.scala deleted file mode 100644 index 5b18ab5ccc51..000000000000 --- a/tests/neg/nowarn.scala +++ /dev/null @@ -1,89 +0,0 @@ -//> using options -deprecation -Wunused:nowarn "-Wconf:msg=@nowarn annotation does not suppress any warnings:e" - -import scala.annotation.{ nowarn, Annotation } - -// This test doesn't run with `-Werror`, because once there's an error, later phases are skipped and we would not see -// their warnings. -// Instead, this test runs with `-Wunused:nowarn -Wconf:msg=@nowarn annotation does not suppress any warnings:e`. -// Only "unused nowarn" warnings are reported as errors. Since these warnings are reported at the very end, all other -// phases of the compiler run normally. - -def t1a = try 1 // warning (parser) -@nowarn("msg=try without catch") def t1b = try 1 - -@nowarn("wat?") // warning (typer, invalid filter) -def t2 = { 1; 2 } // warning (the invalid nowarn doesn't silence anything) - -@nowarn(t1a.toString) // warning (typer, argument not a compile-time constant) -def t2a = { 1; 2 } // warning (invalid nowarn doesn't silence) - -object o: - final val const = "msg=try" - inline def inl = "msg=try" - -@nowarn(o.const) def t2c = try 1 // no warning -@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) - -@nowarn("id=E129") def t3a = { 1; 2 } -@nowarn("name=PureExpressionInStatementPosition") def t3b = { 1; 2 } - -@nowarn("id=E002") def t4a = try 1 -@nowarn("id=E2") def t4b = try 1 -@nowarn("id=2") def t4c = try 1 -@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) - -@nowarn("verbose") def t5 = try 1 // warning with details - -@deprecated def f = 0 - -def t6a = f // warning (refchecks, deprecation) -@nowarn("cat=deprecation") def t6b = f -@nowarn("msg=deprecated") def t6c = f -@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) -@nowarn("") def t6e = f -@nowarn def t6f = f - -def t7a = f: @nowarn("cat=deprecation") -def t7b = f - : @nowarn("msg=deprecated") -def t7c = f // warning (deprecation) - : @nowarn("msg=fish") // error (unused nowarn) -def t7d = f: @nowarn("") -def t7e = f: @nowarn - -def t8a(x: Any) = x match - case _: List[Int] => 0 // warning (patmat, unchecked) - case _ => 1 - -@nowarn("cat=unchecked") def t8(x: Any) = x match - case _: List[Int] => 0 - case _ => 1 - -@nowarn def t9a = { 1: @nowarn; 2 } // error (outer @nowarn is unused) -@nowarn def t9b = { 1: Int @nowarn; 2 } // error (inner @nowarn is unused, it covers the type, not the expression) - -class ann(a: Any) extends Annotation - -@ann(f) def t10a = 0 // should be a deprecation warning, but currently isn't -@nowarn @ann(f) def t10b = 0 // error (unused nowarn) -@ann(f: @nowarn) def t10c = 0 // error (unused nowarn), should be silent - -def forceCompletionOfI1a = (new I1a).m -@nowarn class I1a { // error (unused nowarn) - @nowarn def m = { 1; 2 } -} - -// completion during type checking -@nowarn class I1b { // error (unused nowarn) - @nowarn def m = { 1; 2 } -} - -@nowarn class I1c { - def m = { 1; 2 } -} - -trait T { - @nowarn val t1 = { 0; 1 } -} - -class K extends T diff --git a/tests/warn/i23651.scala b/tests/warn/i23651.scala new file mode 100644 index 000000000000..c81ddb4249f8 --- /dev/null +++ b/tests/warn/i23651.scala @@ -0,0 +1,27 @@ +//> using options -deprecation -Wunused:nowarn + +import scala.annotation.nowarn + +@deprecated +class A + +@deprecated +class B + +@nowarn("msg=trait C is deprecated") // warn + // @nowarn annotation does not suppress any warnings +@nowarn("msg=class A is deprecated") +@nowarn("cat=deprecation&msg=class A is deprecated") // warn + // @nowarn annotation does not suppress any warnings but matches a diagnostic +@nowarn("cat=deprecation&msg=class B is deprecated") +trait C1: + def a: A + def b: B + +@nowarn("cat=deprecation&msg=class B is deprecated") +@nowarn("cat=deprecation&msg=class B is deprecated") // warn + // @nowarn annotation does not suppress any warnings but matches a diagnostic +@nowarn("cat=deprecation&msg=class A is deprecated") +trait C2: + def a: A + def b: B diff --git a/tests/warn/nowarn.check b/tests/warn/nowarn.check new file mode 100644 index 000000000000..89ff8d51161b --- /dev/null +++ b/tests/warn/nowarn.check @@ -0,0 +1,110 @@ +-- [E002] Syntax Warning: tests/warn/nowarn.scala:5:10 ----------------------------------------------------------------- +5 |def t1a = try 1 // warn (parser) + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. + | + | longer explanation available when compiling with `-explain` +-- [E002] Syntax Warning: tests/warn/nowarn.scala:19:25 ---------------------------------------------------------------- +19 |@nowarn(o.inl) def t2d = try 1 // warn // warn (`inl` is not a compile-time constant) + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. + | + | longer explanation available when compiling with `-explain` +-- [E002] Syntax Warning: tests/warn/nowarn.scala:27:26 ---------------------------------------------------------------- +27 |@nowarn("id=1") def t4d = try 1 // warn // warn (unused nowarn, wrong id) + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. + | + | longer explanation available when compiling with `-explain` +-- [E002] Syntax Warning: tests/warn/nowarn.scala:29:28 ---------------------------------------------------------------- +29 |@nowarn("verbose") def t5 = try 1 // warn with details + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. + |Matching filters for @nowarn or -Wconf: + | - id=E2 + | - name=EmptyCatchAndFinallyBlock + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/warn/nowarn.scala:9:11 -------------------------------------------------------- +9 |def t2 = { 1; 2 } // warn (the invalid nowarn doesn't silence anything) + | ^ + | A pure expression does nothing in statement position + | + | longer explanation available when compiling with `-explain` +-- Warning: tests/warn/nowarn.scala:8:8 -------------------------------------------------------------------------------- +8 |@nowarn("wat?") // warn (typer, invalid filter) + | ^^^^^^ + | Invalid message filter + | unknown filter: wat? +-- [E129] Potential Issue Warning: tests/warn/nowarn.scala:12:12 ------------------------------------------------------- +12 |def t2a = { 1; 2 } // warn (invalid nowarn doesn't silence) + | ^ + | A pure expression does nothing in statement position + | + | longer explanation available when compiling with `-explain` +-- Warning: tests/warn/nowarn.scala:11:8 ------------------------------------------------------------------------------- +11 |@nowarn(t1a.toString) // warn (typer, argument not a compile-time constant) + | ^^^^^^^^^^^^ + | filter needs to be a compile-time constant string +-- Warning: tests/warn/nowarn.scala:19:10 ------------------------------------------------------------------------------ +19 |@nowarn(o.inl) def t2d = try 1 // warn // warn (`inl` is not a compile-time constant) + | ^^^^^ + | filter needs to be a compile-time constant string +-- Deprecation Warning: tests/warn/nowarn.scala:33:10 ------------------------------------------------------------------ +33 |def t6a = f // warn (refchecks, deprecation) + | ^ + | method f is deprecated +-- Deprecation Warning: tests/warn/nowarn.scala:36:30 ------------------------------------------------------------------ +36 |@nowarn("msg=fish") def t6d = f // warn (unused nowarn) // warn (deprecation) + | ^ + | method f is deprecated +-- Deprecation Warning: tests/warn/nowarn.scala:43:10 ------------------------------------------------------------------ +43 |def t7c = f // warn (deprecation) + | ^ + | method f is deprecated +-- [E092] Pattern Match Unchecked Warning: tests/warn/nowarn.scala:49:7 ------------------------------------------------ +49 | case _: List[Int] => 0 // warn (patmat, unchecked) + | ^ + |the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from Any + | + | longer explanation available when compiling with `-explain` +-- Warning: tests/warn/nowarn.scala:27:1 ------------------------------------------------------------------------------- +27 |@nowarn("id=1") def t4d = try 1 // warn // warn (unused nowarn, wrong id) + |^^^^^^^^^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Warning: tests/warn/nowarn.scala:36:1 ------------------------------------------------------------------------------- +36 |@nowarn("msg=fish") def t6d = f // warn (unused nowarn) // warn (deprecation) + |^^^^^^^^^^^^^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Warning: tests/warn/nowarn.scala:44:5 ------------------------------------------------------------------------------- +44 | : @nowarn("msg=fish") // warn (unused nowarn) + | ^^^^^^^^^^^^^^^^^^^ + | @nowarn annotation does not suppress any warnings +-- Warning: tests/warn/nowarn.scala:56:0 ------------------------------------------------------------------------------- +56 |@nowarn def t9a = { 1: @nowarn; 2 } // warn (outer @nowarn is unused) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings but matches a diagnostic +-- Warning: tests/warn/nowarn.scala:57:27 ------------------------------------------------------------------------------ +57 |@nowarn def t9b = { 1: Int @nowarn; 2 } // warn (inner @nowarn is unused, it covers the type, not the expression) + | ^^^^^^^ + | @nowarn annotation does not suppress any warnings +-- Warning: tests/warn/nowarn.scala:62:0 ------------------------------------------------------------------------------- +62 |@nowarn @ann(f) def t10b = 0 // warn (unused nowarn) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Warning: tests/warn/nowarn.scala:63:8 ------------------------------------------------------------------------------- +63 |@ann(f: @nowarn) def t10c = 0 // warn (unused nowarn), should be silent + | ^^^^^^^ + | @nowarn annotation does not suppress any warnings +-- Warning: tests/warn/nowarn.scala:66:0 ------------------------------------------------------------------------------- +66 |@nowarn class I1a { // warn (unused nowarn) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings but matches a diagnostic +-- Warning: tests/warn/nowarn.scala:71:0 ------------------------------------------------------------------------------- +71 |@nowarn class I1b { // warn (unused nowarn) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings but matches a diagnostic diff --git a/tests/warn/nowarn.scala b/tests/warn/nowarn.scala new file mode 100644 index 000000000000..f6cacdd677d0 --- /dev/null +++ b/tests/warn/nowarn.scala @@ -0,0 +1,83 @@ +//> using options -deprecation -Wunused:nowarn + +import scala.annotation.{nowarn, Annotation} + +def t1a = try 1 // warn (parser) +@nowarn("msg=try without catch") def t1b = try 1 + +@nowarn("wat?") // warn (typer, invalid filter) +def t2 = { 1; 2 } // warn (the invalid nowarn doesn't silence anything) + +@nowarn(t1a.toString) // warn (typer, argument not a compile-time constant) +def t2a = { 1; 2 } // warn (invalid nowarn doesn't silence) + +object o: + final val const = "msg=try" + inline def inl = "msg=try" + +@nowarn(o.const) def t2c = try 1 // no warn +@nowarn(o.inl) def t2d = try 1 // warn // warn (`inl` is not a compile-time constant) + +@nowarn("id=E129") def t3a = { 1; 2 } +@nowarn("name=PureExpressionInStatementPosition") def t3b = { 1; 2 } + +@nowarn("id=E002") def t4a = try 1 +@nowarn("id=E2") def t4b = try 1 +@nowarn("id=2") def t4c = try 1 +@nowarn("id=1") def t4d = try 1 // warn // warn (unused nowarn, wrong id) + +@nowarn("verbose") def t5 = try 1 // warn with details + +@deprecated def f = 0 + +def t6a = f // warn (refchecks, deprecation) +@nowarn("cat=deprecation") def t6b = f +@nowarn("msg=deprecated") def t6c = f +@nowarn("msg=fish") def t6d = f // warn (unused nowarn) // warn (deprecation) +@nowarn("") def t6e = f +@nowarn def t6f = f + +def t7a = f: @nowarn("cat=deprecation") +def t7b = f + : @nowarn("msg=deprecated") +def t7c = f // warn (deprecation) + : @nowarn("msg=fish") // warn (unused nowarn) +def t7d = f: @nowarn("") +def t7e = f: @nowarn + +def t8a(x: Any) = x match + case _: List[Int] => 0 // warn (patmat, unchecked) + case _ => 1 + +@nowarn("cat=unchecked") def t8(x: Any) = x match + case _: List[Int] => 0 + case _ => 1 + +@nowarn def t9a = { 1: @nowarn; 2 } // warn (outer @nowarn is unused) +@nowarn def t9b = { 1: Int @nowarn; 2 } // warn (inner @nowarn is unused, it covers the type, not the expression) + +class ann(a: Any) extends Annotation + +@ann(f) def t10a = 0 // should be a deprecation warning, but currently isn't +@nowarn @ann(f) def t10b = 0 // warn (unused nowarn) +@ann(f: @nowarn) def t10c = 0 // warn (unused nowarn), should be silent + +def forceCompletionOfI1a = (new I1a).m +@nowarn class I1a { // warn (unused nowarn) + @nowarn def m = { 1; 2 } +} + +// completion during type checking +@nowarn class I1b { // warn (unused nowarn) + @nowarn def m = { 1; 2 } +} + +@nowarn class I1c { + def m = { 1; 2 } +} + +trait T { + @nowarn val t1 = { 0; 1 } +} + +class K extends T From 221d9db2d5006161333e6b05cc10baad0c288bfa Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Wed, 6 Aug 2025 10:17:02 +0100 Subject: [PATCH 029/128] fix: Fix extracting refinements from intersection types in dynamic select hovers (#23640) closes #22919 --------- Co-authored-by: Bulby <26726264+thedrawingcoder-gamer@users.noreply.github.com> [Cherry-picked b0627b3a9df3ba5e16f34b3b189c4df84fcf3647] --- .../main/dotty/tools/pc/HoverProvider.scala | 20 +++-- .../tools/pc/tests/hover/HoverTermSuite.scala | 75 +++++++++++++++++++ 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala index c55a8a0210be..375a75d0307f 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala @@ -13,6 +13,7 @@ import scala.meta.pc.SymbolSearch import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Constants.* import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Decorators.* import dotty.tools.dotc.core.Flags.* import dotty.tools.dotc.core.Names.* import dotty.tools.dotc.core.StdNames.* @@ -221,12 +222,21 @@ object HoverProvider: findRefinement(parent) case _ => None - val refTpe = sel.typeOpt.widen.deepDealiasAndSimplify match - case r: RefinedType => Some(r) - case t: (TermRef | TypeProxy) => Some(t.termSymbol.info.deepDealiasAndSimplify) - case _ => None + def extractRefinements(t: Type): List[Type] = t match + case r: RefinedType => List(r) + case t: (TypeRef | AppliedType) => + // deepDealiasAndSimplify can succeed with no progress, so we have to avoid infinite loops + val t1 = t.deepDealiasAndSimplify + if t1 == t then Nil + else extractRefinements(t1) + case t: TermRef => extractRefinements(t.widen) + case t: TypeProxy => List(t.termSymbol.info.deepDealiasAndSimplify) + case AndType(l , r) => List(extractRefinements(l), extractRefinements(r)).flatten + case _ => Nil - refTpe.flatMap(findRefinement).asJava + val refTpe: List[Type] = extractRefinements(sel.typeOpt) + + refTpe.flatMap(findRefinement).headOption.asJava case _ => ju.Optional.empty().nn 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 c483dc289b0e..60827f1e3590 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala @@ -851,3 +851,78 @@ class HoverTermSuite extends BaseHoverSuite: |""".stripMargin, "val thisIsAVeryLongName: Int".hover ) + + @Test def `intersection_of_selectable-1` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type A = Record { val aa: Int } + |type B = Record { val bb: String } + |type AB = A & B + | + |val ab: AB = Record().asInstanceOf[AB] + |val ab_a = ab.a@@a + |""".stripMargin, + "val aa: Int".hover + ) + + @Test def `intersection_of_selectable-2` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type A = Record { val aa: Int } + |type B = Record { val aa: String } + |type AB = A & B + | + |val ab: AB = Record().asInstanceOf[AB] + |val ab_a = ab.a@@a + |""".stripMargin, + "val aa: Int & String".hover + ) + + @Test def `intersection_of_selectable-3` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type A = Record { val aa: Int } + |type B = Record { val bb: String } + |type AB = A & B + | + |val ab: AB = Record().asInstanceOf[AB] + |val ab_a = ab.b@@b + |""".stripMargin, + "val bb: String".hover + ) + + @Test def `intersection_of_selectable-4` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type A = Record { val aa: Int } + |type B = Record { val bb: String } + |type C = Record { val cc: Float } + |type AB = A & B + |type ABC = AB & C + | + |val abc: ABC = Record().asInstanceOf[ABC] + |val abc_a = abc.a@@a + |""".stripMargin, + "val aa: Int".hover + ) + + @Test def `intersection_of_selectable-5` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type AL = List[Int] & Record { val aa: Int } + | + |val al: AL = ???.asInstanceOf[ABC] + |val al_a = al.a@@a + |""".stripMargin, + "val aa: Int".hover + ) From 2573ecf60cb0073f9e1a1469a4c019b63f864436 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Fri, 25 Jul 2025 19:27:20 +0200 Subject: [PATCH 030/128] Check for mismatched argument type length in PatternMatcher due to Named-Tuple :* syntax [Cherry-picked 180b4eba6b1bf67cc70b07bfc05a9610f7ac8a19] --- .../dotty/tools/dotc/transform/PatternMatcher.scala | 10 ++++++++-- tests/neg/i23155a.scala | 7 +++++++ tests/neg/i23155b.scala | 6 ++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 tests/neg/i23155a.scala create mode 100644 tests/neg/i23155b.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 778a97f6c38b..8bf88a0027c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -267,8 +267,14 @@ object PatternMatcher { def matchArgsPatternPlan(args: List[Tree], syms: List[Symbol]): Plan = args match { case arg :: args1 => - val sym :: syms1 = syms: @unchecked - patternPlan(sym, arg, matchArgsPatternPlan(args1, syms1)) + if (args.length != syms.length) + report.error(UnapplyInvalidNumberOfArguments(tree, tree.tpe :: Nil), arg.srcPos) + // Generate a throwaway but type-correct plan. + // This plan will never execute because it'll be guarded by a `NonNullTest`. + ResultPlan(tpd.Throw(tpd.nullLiteral)) + else + val sym :: syms1 = syms: @unchecked + patternPlan(sym, arg, matchArgsPatternPlan(args1, syms1)) case Nil => assert(syms.isEmpty) onSuccess diff --git a/tests/neg/i23155a.scala b/tests/neg/i23155a.scala new file mode 100644 index 000000000000..ac48bbf298e0 --- /dev/null +++ b/tests/neg/i23155a.scala @@ -0,0 +1,7 @@ +import scala.NamedTuple +object Unpack_NT { + (1, 2) match { + case Unpack_NT(first, _) => first // error + } + def unapply(e: (Int, Int)): Some[NamedTuple.NamedTuple["x" *: "y" *: EmptyTuple, Int *: Int *: EmptyTuple]] = ??? +} diff --git a/tests/neg/i23155b.scala b/tests/neg/i23155b.scala new file mode 100644 index 000000000000..ef35f4631983 --- /dev/null +++ b/tests/neg/i23155b.scala @@ -0,0 +1,6 @@ +object Unpack_T { + (1, 2) match { + case Unpack_T(first, _) => first // error + } + def unapply(e: (Int, Int)): Some[Int *: Int *: EmptyTuple] = ??? +} From b74b91e0764425a8da63804bc6571309ccaceb06 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 17 Jul 2025 14:39:25 -0700 Subject: [PATCH 031/128] Warn if implicit default shadows given -Wrecurse-with-default warns on self-recursion with default arg. [Cherry-picked 6dac167b2a182194a20b0060918a943ace5c89e6] --- .../tools/dotc/config/ScalaSettings.scala | 2 ++ .../tools/dotc/reporting/ErrorMessageID.scala | 2 ++ .../dotty/tools/dotc/reporting/messages.scala | 12 ++++++++ .../dotty/tools/dotc/transform/TailRec.scala | 10 +++++-- .../dotty/tools/dotc/typer/Applications.scala | 5 ++++ .../tools/scaladoc/tasty/TypesSupport.scala | 11 +++---- ...desugared.check => i19414-desugared.check} | 2 +- ...desugared.scala => i19414-desugared.scala} | 0 tests/neg/{19414.check => i19414.check} | 2 +- tests/neg/{19414.scala => i19414.scala} | 0 tests/warn/i23541.scala | 30 +++++++++++++++++++ 11 files changed, 66 insertions(+), 10 deletions(-) rename tests/neg/{19414-desugared.check => i19414-desugared.check} (91%) rename tests/neg/{19414-desugared.scala => i19414-desugared.scala} (100%) rename tests/neg/{19414.check => i19414.check} (91%) rename tests/neg/{19414.scala => i19414.scala} (100%) create mode 100644 tests/warn/i23541.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index a3fa5fd9bb06..a2c557ea2987 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -167,6 +167,7 @@ private sealed trait WarningSettings: private val WimplausiblePatterns = BooleanSetting(WarningSetting, "Wimplausible-patterns", "Warn if comparison with a pattern value looks like it might always fail.") 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 Wunused: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting( WarningSetting, name = "Wunused", @@ -309,6 +310,7 @@ private sealed trait WarningSettings: def implausiblePatterns(using Context): Boolean = allOr(WimplausiblePatterns) 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) /** -X "Extended" or "Advanced" settings */ diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 24c3d2e412f7..103687abdbff 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -233,6 +233,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case ErasedNotPureID // errorNumber: 217 case IllegalErasedDefID // errorNumber: 218 case CannotInstantiateQuotedTypeVarID // errorNumber: 219 + case DefaultShadowsGivenID // errorNumber: 220 + case RecurseWithDefaultID // errorNumber: 221 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index f84fd3ba538e..1e14e40f8dae 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3664,3 +3664,15 @@ final class IllegalErasedDef(sym: Symbol)(using Context) extends TypeMsg(Illegal override protected def explain(using Context): String = "Only non-lazy immutable values can be `erased`" end IllegalErasedDef + +final class DefaultShadowsGiven(name: Name)(using Context) extends TypeMsg(DefaultShadowsGivenID): + override protected def msg(using Context): String = + i"Argument for implicit parameter $name was supplied using a default argument." + override protected def explain(using Context): String = + "Usually the given in scope is intended, but you must specify it after explicit `using`." + +final class RecurseWithDefault(using Context) extends TypeMsg(RecurseWithDefaultID): + override protected def msg(using Context): String = + i"Recursive call used a default argument." + override protected def explain(using Context): String = + "It's more explicit to pass current or modified arguments in a recursion." diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index 08c2c6a015c0..ef2481d83067 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -4,9 +4,9 @@ package transform import ast.{TreeTypeMap, tpd} import config.Printers.tailrec import core.* -import Contexts.*, Flags.*, Symbols.*, Decorators.em +import Contexts.*, Flags.*, Symbols.*, Decorators.* import Constants.Constant -import NameKinds.{TailLabelName, TailLocalName, TailTempName} +import NameKinds.{DefaultGetterName, TailLabelName, TailLocalName, TailTempName} import StdNames.nme import reporting.* import transform.MegaPhase.MiniPhase @@ -325,7 +325,11 @@ class TailRec extends MiniPhase { method.matches(calledMethod) && enclosingClass.appliedRef.widen <:< prefix.tpe.widenDealias - if (isRecursiveCall) + if isRecursiveCall then + if ctx.settings.Whas.recurseWithDefault then + if tree.args.exists(_.symbol.name.is(DefaultGetterName)) then + report.warning(RecurseWithDefault(), tree.srcPos) + if (inTailPosition) { tailrec.println("Rewriting tail recursive call: " + tree.span) rewrote = true diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index da6b20b75c29..290e061772e4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -774,6 +774,11 @@ trait Applications extends Compatibility { methodType.isImplicitMethod && (applyKind == ApplyKind.Using || failEmptyArgs) if !defaultArg.isEmpty then + if methodType.isImplicitMethod && ctx.mode.is(Mode.ImplicitsEnabled) + && !inferImplicitArg(formal, appPos.span).tpe.isError + then + report.warning(DefaultShadowsGiven(methodType.paramNames(n)), appPos) + defaultArg.tpe.widen match case _: MethodOrPoly if testOnly => matchArgs(args1, formals1, n + 1) case _ => matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 30a5ac22be0d..60ec66407cf7 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -1,13 +1,13 @@ package dotty.tools.scaladoc package tasty -import scala.jdk.CollectionConverters._ - -import scala.quoted._ +import scala.annotation.* +import scala.jdk.CollectionConverters.* +import scala.quoted.* import scala.util.control.NonFatal -import NameNormalizer._ -import SyntheticsSupport._ +import NameNormalizer.* +import SyntheticsSupport.* trait TypesSupport: self: TastyParser => @@ -81,6 +81,7 @@ trait TypesSupport: case tpe => inner(tpe, skipThisTypePrefix) // TODO #23 add support for all types signatures that make sense + @nowarn("id=E219") private def inner( using Quotes, )( diff --git a/tests/neg/19414-desugared.check b/tests/neg/i19414-desugared.check similarity index 91% rename from tests/neg/19414-desugared.check rename to tests/neg/i19414-desugared.check index cc51ee471553..72a3a5eabd37 100644 --- a/tests/neg/19414-desugared.check +++ b/tests/neg/i19414-desugared.check @@ -1,4 +1,4 @@ --- [E172] Type Error: tests/neg/19414-desugared.scala:22:34 ------------------------------------------------------------ +-- [E172] Type Error: tests/neg/i19414-desugared.scala:22:34 ----------------------------------------------------------- 22 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances | ^ |No best given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef. diff --git a/tests/neg/19414-desugared.scala b/tests/neg/i19414-desugared.scala similarity index 100% rename from tests/neg/19414-desugared.scala rename to tests/neg/i19414-desugared.scala diff --git a/tests/neg/19414.check b/tests/neg/i19414.check similarity index 91% rename from tests/neg/19414.check rename to tests/neg/i19414.check index 016e3942c825..10bc939494c6 100644 --- a/tests/neg/19414.check +++ b/tests/neg/i19414.check @@ -1,4 +1,4 @@ --- [E172] Type Error: tests/neg/19414.scala:15:34 ---------------------------------------------------------------------- +-- [E172] Type Error: tests/neg/i19414.scala:15:34 --------------------------------------------------------------------- 15 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances | ^ |No best given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef. diff --git a/tests/neg/19414.scala b/tests/neg/i19414.scala similarity index 100% rename from tests/neg/19414.scala rename to tests/neg/i19414.scala diff --git a/tests/warn/i23541.scala b/tests/warn/i23541.scala new file mode 100644 index 000000000000..7f374b421ba3 --- /dev/null +++ b/tests/warn/i23541.scala @@ -0,0 +1,30 @@ +//> using options -Wrecurse-with-default + +def fun(x: Int)(using p: Int, q: Int = 0): Int = + if x <= 0 then p * q + else fun(x - 1)(using p = p + x) // warn recurse uses default (instead of given passed down the stack) + +def gun(x: Int)(p: Int, q: Int = 0): Int = + if x <= 0 then p * q + else gun(x - 1)(p = p + x) // warn recurse uses default (value not passed down the stack) + +def nested(using x: Int, y: Int = 42): Int = + def f: Int = nested(using x) // nowarn only self-recursive tailrec is eligible for warning + f + +def f(using s: String, i: Int = 1): String = s * i +def g(using s: String)(using i: Int = 1): String = s * i + +@main def Test = + println(fun(3)(using p = 0, q = 1)) + locally: + given String = "ab" + println(f) // prints "ab" + println(g) // prints "ab" + locally: + println(f(using s = "ab")) // prints "ab" + println(g(using s = "ab")) // prints "ab" + locally: + given Int = 2 + println(f(using s = "ab")) // warn uses default instead of given // prints "ab" + println(g(using s = "ab")) // prints "abab" From ad52b543114dc00ceca085cc8536a7196681c584 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 23 Jul 2025 14:10:51 -0700 Subject: [PATCH 032/128] Use explicit args in recursion [Cherry-picked 4cf87b07b8a11fdca834b2439e65d96f775666b1] --- .../tools/scaladoc/tasty/TypesSupport.scala | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 60ec66407cf7..24473c874c96 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -81,7 +81,6 @@ trait TypesSupport: case tpe => inner(tpe, skipThisTypePrefix) // TODO #23 add support for all types signatures that make sense - @nowarn("id=E219") private def inner( using Quotes, )( @@ -156,24 +155,25 @@ trait TypesSupport: .reduceLeftOption((acc: SSignature, elem: SSignature) => acc ++ plain(", ").l ++ elem).getOrElse(List()) ++ plain(")").l - def parseRefinedElem(name: String, info: TypeRepr, polyTyped: SSignature = Nil): SSignature = ( info match { + def parseRefinedElem(name: String, info: TypeRepr, polyTyped: SSignature = Nil): SSignature = + val ssig = info match case m: MethodType => { val paramList = getParamList(m) keyword("def ").l ++ plain(name).l ++ polyTyped ++ paramList ++ plain(": ").l ++ inner(m.resType, skipThisTypePrefix) } - case t: PolyType => { + case t: PolyType => val paramBounds = getParamBounds(t) - val parsedMethod = parseRefinedElem(name, t.resType) - if (!paramBounds.isEmpty){ + if !paramBounds.isEmpty then parseRefinedElem(name, t.resType, plain("[").l ++ paramBounds ++ plain("]").l) - } else parseRefinedElem(name, t.resType) - } + else + parseRefinedElem(name, t.resType, polyTyped = Nil) case ByNameType(tp) => keyword("def ").l ++ plain(s"$name: ").l ++ inner(tp, skipThisTypePrefix) case t: TypeBounds => keyword("type ").l ++ plain(name).l ++ inner(t, skipThisTypePrefix) case t: TypeRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t, skipThisTypePrefix) case t: TermRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t, skipThisTypePrefix) case other => noSupported(s"Not supported type in refinement $info") - } ) ++ plain("; ").l + + ssig ++ plain("; ").l def parsePolyFunction(info: TypeRepr): SSignature = info match { case t: PolyType => @@ -254,6 +254,7 @@ trait TypesSupport: }) ++ plain("]").l case tp @ TypeRef(qual, typeName) => + inline def wrapping = shouldWrapInParens(inner = qual, outer = tp, isLeft = true) qual match { case r: RecursiveThis => tpe(s"this.$typeName").l case ThisType(tr) => @@ -270,23 +271,28 @@ trait TypesSupport: if skipPrefix(qual, elideThis, originalOwner, skipThisTypePrefix) then tpe(tp.typeSymbol) else - val sig = inParens(inner(qual, skipThisTypePrefix)(using skipTypeSuffix = true), shouldWrapInParens(qual, tp, true)) - sig ++ plain(".").l ++ tpe(tp.typeSymbol) + val sig = inParens( + inner(qual, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true), wrapping) + sig + ++ plain(".").l + ++ tpe(tp.typeSymbol) case t if skipPrefix(t, elideThis, originalOwner, skipThisTypePrefix) => 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 skipTypeSuffix = true) ++ plain(".").l ++ suffix + inner(qual, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true) + ++ plain(".").l + ++ suffix case _ => - val sig = inParens(inner(qual, skipThisTypePrefix), shouldWrapInParens(qual, tp, true)) + val sig = inParens(inner(qual, skipThisTypePrefix), wrapping) sig ++ keyword("#").l ++ tpe(tp.typeSymbol) } case tr @ TermRef(qual, typeName) => val prefix = qual match case t if skipPrefix(t, elideThis, originalOwner, skipThisTypePrefix) => Nil - case tp => inner(tp, skipThisTypePrefix)(using skipTypeSuffix = true) ++ plain(".").l + case tp => inner(tp, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true) ++ 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) => @@ -305,9 +311,17 @@ trait TypesSupport: val spaces = " " * (indent) val casesTexts = cases.flatMap { case MatchCase(from, to) => - keyword(caseSpaces + "case ").l ++ inner(from, skipThisTypePrefix) ++ keyword(" => ").l ++ inner(to, skipThisTypePrefix)(using indent = indent + 2) ++ plain("\n").l + keyword(caseSpaces + "case ").l + ++ inner(from, skipThisTypePrefix) + ++ keyword(" => ").l + ++ inner(to, skipThisTypePrefix)(using indent = indent + 2, skipTypeSuffix = skipTypeSuffix) + ++ plain("\n").l case TypeLambda(_, _, MatchCase(from, to)) => - keyword(caseSpaces + "case ").l ++ inner(from, skipThisTypePrefix) ++ keyword(" => ").l ++ inner(to, skipThisTypePrefix)(using indent = indent + 2) ++ plain("\n").l + keyword(caseSpaces + "case ").l + ++ inner(from, skipThisTypePrefix) + ++ keyword(" => ").l + ++ inner(to, skipThisTypePrefix)(using indent = indent + 2, skipTypeSuffix = skipTypeSuffix) + ++ plain("\n").l } inner(sc, skipThisTypePrefix) ++ keyword(" match ").l ++ plain("{\n").l ++ casesTexts ++ plain(spaces + "}").l From 8749bba8ac829120ad625e338c9445c1749323a0 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 23 Jul 2025 15:05:13 -0700 Subject: [PATCH 033/128] Name the parameter using a default [Cherry-picked 8ba7cd38423b645ed6ef333f62371a34b9db8fde] --- .../dotty/tools/dotc/reporting/messages.scala | 4 ++-- .../dotty/tools/dotc/transform/TailRec.scala | 7 +++++-- tests/warn/i23541.check | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 tests/warn/i23541.check diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 1e14e40f8dae..210322841158 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3671,8 +3671,8 @@ final class DefaultShadowsGiven(name: Name)(using Context) extends TypeMsg(Defau override protected def explain(using Context): String = "Usually the given in scope is intended, but you must specify it after explicit `using`." -final class RecurseWithDefault(using Context) extends TypeMsg(RecurseWithDefaultID): +final class RecurseWithDefault(name: Name)(using Context) extends TypeMsg(RecurseWithDefaultID): override protected def msg(using Context): String = - i"Recursive call used a default argument." + i"Recursive call used a default argument for parameter $name." override protected def explain(using Context): String = "It's more explicit to pass current or modified arguments in a recursion." diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index ef2481d83067..690a180e52ca 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -327,8 +327,11 @@ class TailRec extends MiniPhase { if isRecursiveCall then if ctx.settings.Whas.recurseWithDefault then - if tree.args.exists(_.symbol.name.is(DefaultGetterName)) then - report.warning(RecurseWithDefault(), tree.srcPos) + tree.args.find(_.symbol.name.is(DefaultGetterName)) match + case Some(arg) => + val DefaultGetterName(_, index) = arg.symbol.name: @unchecked + report.warning(RecurseWithDefault(calledMethod.info.firstParamNames(index)), tree.srcPos) + case _ => if (inTailPosition) { tailrec.println("Rewriting tail recursive call: " + tree.span) diff --git a/tests/warn/i23541.check b/tests/warn/i23541.check new file mode 100644 index 000000000000..64ffbd9808d2 --- /dev/null +++ b/tests/warn/i23541.check @@ -0,0 +1,18 @@ +-- [E220] Type Warning: tests/warn/i23541.scala:29:13 ------------------------------------------------------------------ +29 | println(f(using s = "ab")) // warn uses default instead of given // prints "ab" + | ^^^^^^^^^^^^^^^^^ + | Argument for implicit parameter i was supplied using a default argument. + | + | longer explanation available when compiling with `-explain` +-- [E221] Type Warning: tests/warn/i23541.scala:5:17 ------------------------------------------------------------------- +5 | else fun(x - 1)(using p = p + x) // warn recurse uses default (instead of given passed down the stack) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Recursive call used a default argument for parameter q. + | + | longer explanation available when compiling with `-explain` +-- [E221] Type Warning: tests/warn/i23541.scala:9:17 ------------------------------------------------------------------- +9 | else gun(x - 1)(p = p + x) // warn recurse uses default (value not passed down the stack) + | ^^^^^^^^^^^^^^^^^^^^^ + | Recursive call used a default argument for parameter q. + | + | longer explanation available when compiling with `-explain` From 186b6d89df66943cec6674512c480078a5f8e186 Mon Sep 17 00:00:00 2001 From: Jan Chyb <48855024+jchyb@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:46:40 +0200 Subject: [PATCH 034/128] Fix issue with pc breaking in requiredMethod on newly overloaded valueOf (#23708) We recently started having issues when running completions in metals, with the presentation compiler crashing: ```scala dotty.tools.dotc.core.TypeError$.apply(TypeErrors.scala:54) dotty.tools.dotc.core.Denotations$MultiDenotation.suchThat(Denotations.scala:1280) dotty.tools.dotc.core.Denotations$Denotation.requiredSymbol(Denotations.scala:305) dotty.tools.dotc.core.Denotations$Denotation.requiredMethod(Denotations.scala:321) dotty.tools.pc.completions.Completions.isUninterestingSymbol$lzyINIT1(Completions.scala:744) dotty.tools.pc.completions.Completions.isUninterestingSymbol(Completions.scala:725) dotty.tools.pc.completions.Completions.includeSymbol(Completions.scala:90) dotty.tools.pc.completions.Completions.visit$3(Completions.scala:697) dotty.tools.pc.completions.Completions.filterInteresting$$anonfun$1(Completions.scala:715) scala.collection.immutable.List.foreach(List.scala:334) dotty.tools.pc.completions.Completions.filterInteresting(Completions.scala:715) dotty.tools.pc.completions.Completions.enrichedCompilerCompletions(Completions.scala:118) dotty.tools.pc.completions.Completions.completions(Completions.scala:136) dotty.tools.pc.completions.CompletionProvider.completions(CompletionProvider.scala:139) dotty.tools.pc.ScalaPresentationCompiler.complete$$anonfun$1(ScalaPresentationCompiler.scala:194) dotty.tools.dotc.core.TypeError$$anon$1: Failure to disambiguate overloaded reference with method valueOf in object Predef: [T]: T and method valueOf in object Predef: [T](implicit vt: ValueOf[T]): T ``` This happened after the recent changes to the stdlib, with the, I believe, newly added valueOf overload, and the previously used `requiredMethod` by design not handling these cases. I also noticed that in the same piece of code we had a bit of an empty Symbol factory situation going on, with MultiDenotation being changed into a Symbol (always resulting in an empty symbol), and only later flattened with the `alternatives`, so I changed that too. I can't really test this properly, as the pc tests seem to use an older stdlib, but at least the `wait` methods do resolve properly after these changes, so I have no reason to think the valueOf methods would be any different. [Cherry-picked 1b5e93b9128a4eec82c22fe350115e81add751ce] --- .../main/dotty/tools/pc/completions/Completions.scala | 11 +++++++---- .../tools/pc/tests/completion/CompletionSuite.scala | 3 --- 2 files changed, 7 insertions(+), 7 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 e7902ef8aa44..b396dd780cc0 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -734,15 +734,18 @@ class Completions( defn.Object_notifyAll, defn.Object_notify, defn.Predef_undefined, - defn.ObjectClass.info.member(nme.wait_).symbol, // NOTE(olafur) IntelliJ does not complete the root package and without this filter // then `_root_` would appear as a completion result in the code `foobar(_)` defn.RootPackage, // NOTE(gabro) valueOf was added as a Predef member in 2.13. We filter it out since is a niche // use case and it would appear upon typing 'val' - defn.ValueOfClass.info.member(nme.valueOf).symbol, - defn.ScalaPredefModule.requiredMethod(nme.valueOf) - ).flatMap(_.alternatives.map(_.symbol)).toSet + defn.ValueOfClass + ) ++ ( + Set( + defn.ObjectClass.info.member(nme.wait_), + defn.ScalaPredefModule.info.member(nme.valueOf) + ).flatMap(_.alternatives.map(_.symbol)).toSet + ) private def isNotLocalForwardReference(sym: Symbol)(using Context): Boolean = !sym.isLocalToBlock || 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 05037cffca94..277a579ba4ce 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -129,9 +129,6 @@ class CompletionSuite extends BaseCompletionSuite: |isInstanceOf[X0]: Boolean |synchronized[X0](x$0: X0): X0 |toString(): String - |wait(): Unit - |wait(x$0: Long): Unit - |wait(x$0: Long, x$1: Int): Unit |""".stripMargin ) From 045d362422280707f2943bc6acf3f2e6b2a1f38b Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 8 Aug 2025 15:06:56 +0200 Subject: [PATCH 035/128] Fix match type bounds checking problem [Cherry-picked a94a69c75f3855d0f78160200ae539fd8a193379] --- .../src/dotty/tools/dotc/typer/Checking.scala | 36 ++++++++++--------- .../pos-custom-args/captures/tuple-ops.scala | 13 +++++++ 2 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 tests/pos-custom-args/captures/tuple-ops.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index ceec2392af1a..ecbb34ea2949 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -145,22 +145,26 @@ object Checking { val checker = new TypeTraverser: def traverse(tp: Type) = tp match - case AppliedType(tycon, argTypes) - 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 || tycon.typeSymbol.is(CaptureChecked) - // Don't check bounds when capture checking type constructors that were not - // themselves capture checked. Since the type constructor could not foresee - // possible capture sets, it's better to be lenient for backwards compatibility. - => - checkAppliedType( - untpd.AppliedTypeTree(TypeTree(tycon), argTypes.map(TypeTree(_))) - .withType(tp).withSpan(tpt.span.toSynthetic), - tpt) + 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) case _ => traverseChildren(tp) checker.traverse(tpt.tpe) diff --git a/tests/pos-custom-args/captures/tuple-ops.scala b/tests/pos-custom-args/captures/tuple-ops.scala new file mode 100644 index 000000000000..6259328dd9ab --- /dev/null +++ b/tests/pos-custom-args/captures/tuple-ops.scala @@ -0,0 +1,13 @@ +sealed trait Tupp + +case object EmptyTupp extends Tupp +type EmptyTupp = EmptyTupp.type +infix case class `*::`[H, T <: Tupp](h: H, t: T) extends Tupp + +type Union[T <: Tupp] = T match + case EmptyTupp => Nothing + case h *:: t => h | Union[t] + +type Map[T <: Tupp, F[_ <: Union[T]]] <: Tupp = T match + case EmptyTupp => EmptyTupp + case h *:: t => F[h] *:: Map[t, F] \ No newline at end of file From fb1921f48b879cfe90e6f2c4cd2c83f4fef72286 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 13 Aug 2025 09:28:09 +0200 Subject: [PATCH 036/128] Add changelog for 3.7.3-RC2 --- changelogs/3.7.3-RC2.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 changelogs/3.7.3-RC2.md diff --git a/changelogs/3.7.3-RC2.md b/changelogs/3.7.3-RC2.md new file mode 100644 index 000000000000..243d06b5348d --- /dev/null +++ b/changelogs/3.7.3-RC2.md @@ -0,0 +1,29 @@ +# Backported chnages + +- Warn if implicit default shadows given [#23559](https://github.com/scala/scala3/pull/23559) +- Bump Scala CLI to v1.8.5 (was v1.8.4) [#23702](https://github.com/scala/scala3/pull/23702) +- Fix issue with pc breaking in requiredMethod on newly overloaded valueOf [#23708](https://github.com/scala/scala3/pull/23708) +- Handle default arguments in named parameters for inlay hints [#23641](https://github.com/scala/scala3/pull/23641) +- Add suppression if nowarn differs [#23652](https://github.com/scala/scala3/pull/23652) +- Fix match type bounds checking problem [#23695](https://github.com/scala/scala3/pull/23695) +- Generalize "Don't approximate a type using Nothing as prefix" [#23628](https://github.com/scala/scala3/pull/23628) +- More careful ClassTag instantiation [#23659](https://github.com/scala/scala3/pull/23659) +- Use more context for implicit search only if no default argument [#23664](https://github.com/scala/scala3/pull/23664) +- Fix extracting refinements from intersection types in dynamic select hovers [#23640](https://github.com/scala/scala3/pull/23640) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.3-RC1..3.7.3-RC2` these are: + +``` + 5 Martin Odersky + 4 Som Snytt + 2 Wojciech Mazur + 1 Guillaume Martres + 1 Jan Chyb + 1 Kacper Korban + 1 Piotr Chabelski + 1 aherlihy +``` From bb8910adce942a32015a158147ca9d7650778cd4 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 13 Aug 2025 09:29:14 +0200 Subject: [PATCH 037/128] Release 3.7.3-RC2 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index b6acaa25fbae..07f7164de438 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 15f010a52a54b96718fb1bedde70a2c52abf432c Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 3 Sep 2025 10:59:23 +0200 Subject: [PATCH 038/128] Backport "[chore] Update scala-cli to 1.9.0 (was 1.8.5)" to 3.7.3 (#23861) Backports #23856 to the 3.7.3-RC3. PR submitted by the release tooling. --- .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 95eabcebfe0c..6c8353435b50 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.8.5 + - uses: VirtusLab/scala-cli-setup@v1.9.0 - 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 07f7164de438..43ec7991899d 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.8.5" + val scalaCliLauncherVersion = "1.9.0" /** Version of Coursier to download for initializing the local maven repo of Scala command */ val coursierJarVersion = "2.1.24" From 9663e318b565cd8220602245ef853c6efdad6084 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 3 Sep 2025 11:33:56 +0200 Subject: [PATCH 039/128] Add release notes for 3.7.3-RC3 Signed-off-by: Wojciech Mazur --- changelogs/3.7.3-RC3.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 changelogs/3.7.3-RC3.md diff --git a/changelogs/3.7.3-RC3.md b/changelogs/3.7.3-RC3.md new file mode 100644 index 000000000000..59e385fa26a8 --- /dev/null +++ b/changelogs/3.7.3-RC3.md @@ -0,0 +1,14 @@ +# Backported chnages + +- Update scala-cli to 1.9.0 (was 1.8.5) [#23861](https://github.com/scala/scala3/pull/23861) + + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.3-RC2..3.7.3-RC3` these are: + +``` + 3 Wojciech Mazur +``` From 350126ffe581ec0da40a6fb468197e0203dc51a8 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 3 Sep 2025 11:35:50 +0200 Subject: [PATCH 040/128] Release Scala 3.7.3-RC3 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 43ec7991899d..c042a915e167 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 f8a1d038e44d4db4cc87fea62bb7f110dfac0a97 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 8 Sep 2025 12:24:28 +0200 Subject: [PATCH 041/128] Add changelog for 3.7.3 Signed-off-by: Wojciech Mazur --- changelogs/3.7.3.md | 238 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 changelogs/3.7.3.md diff --git a/changelogs/3.7.3.md b/changelogs/3.7.3.md new file mode 100644 index 000000000000..c8c31a4d5743 --- /dev/null +++ b/changelogs/3.7.3.md @@ -0,0 +1,238 @@ +# Release highlights + +- Warn if implicit default shadows given [#23559](https://github.com/scala/scala3/pull/23559) +- Standardize on `-Vprint:...` (still support `-Xprint:...` as alias) [#22828](https://github.com/scala/scala3/pull/22828) + +# Other changes and fixes + +## Desugaring + +- Optimize simple tuple extraction [#23373](https://github.com/scala/scala3/pull/23373) + +## Enums + +- Make hashcode of enum items stable [#23218](https://github.com/scala/scala3/pull/23218) + +## Erasure + +- Replace erased class modifiers with Erased base traits [#23447](https://github.com/scala/scala3/pull/23447) +- Bring back part of PruneErasedDefs [#23466](https://github.com/scala/scala3/pull/23466) + +## Experimental: Capture Checking + +- Fix parsing crash for update in later phases [#23390](https://github.com/scala/scala3/pull/23390) +- Implement boxing for singleton type arguments [#23418](https://github.com/scala/scala3/pull/23418) +- Expand Capability types also in arguments of Capability classes [#23427](https://github.com/scala/scala3/pull/23427) +- Adjustments to the capability trilogy [#23428](https://github.com/scala/scala3/pull/23428) +- Set context owner to the method for `paramsToCap` [#23436](https://github.com/scala/scala3/pull/23436) +- Flatten nested capture sets in retainedElementsRaw [#23571](https://github.com/scala/scala3/pull/23571) +- Fix well-formed test for capabilities [#23393](https://github.com/scala/scala3/pull/23393) +- Add restricted capabilities `x.only[C]` [#23485](https://github.com/scala/scala3/pull/23485) +- Rely on hidden sets for use checking [#23580](https://github.com/scala/scala3/pull/23580) + +## Experimental: Seperation Checking + +- Make separation checking controlled by language import [#23560](https://github.com/scala/scala3/pull/23560) + +## Experimental: Erased Definitions + +- Refactorings and fixes to erased definition handling [#23404](https://github.com/scala/scala3/pull/23404) + +## Experimental: Explicit Nulls + +- Add quick fix to remove unnecessary .nn [#23461](https://github.com/scala/scala3/pull/23461) +- Add `stableNull` annotation to force tracking mutable fields [#23528](https://github.com/scala/scala3/pull/23528) + +## Experimental: Global Initialization + +- Rewrite resolveThis in global init checker [#23282](https://github.com/scala/scala3/pull/23282) +- Fix errors in the global initialization checker when compiling bootstrapped dotty [#23429](https://github.com/scala/scala3/pull/23429) +- Fix error in product-sequence match in global init checker [#23480](https://github.com/scala/scala3/pull/23480) + +## Experimental: Into + +- Fix isConversionTargetType test [#23401](https://github.com/scala/scala3/pull/23401) + +## Experimental: Modularity + +- Refinements to skolemizaton [#23513](https://github.com/scala/scala3/pull/23513) + +## Experimental: Unroll + +- Enable UnrollDefinitions phase in REPL frontend phases [#23433](https://github.com/scala/scala3/pull/23433) + +## Extension Methods + +- Avoid forcing extension on check of local select [#23439](https://github.com/scala/scala3/pull/23439) + +## Implicits + +- Refine implicit search fallbacks for better ClassTag handling [#23532](https://github.com/scala/scala3/pull/23532) + +## Inline + +- Fix Symbol.info remapping in TreeTypeMap [#23432](https://github.com/scala/scala3/pull/23432) +- Fail not inlined inline method calls early [#22925](https://github.com/scala/scala3/pull/22925) +- Fix inline export forwarder generation regression [#23126](https://github.com/scala/scala3/pull/23126) + +## Linting + +- Consider setter of effectively private var [#23211](https://github.com/scala/scala3/pull/23211) +- Add accessible check for import usage [#23348](https://github.com/scala/scala3/pull/23348) +- Check OrType in interpolated toString lint [#23365](https://github.com/scala/scala3/pull/23365) +- Use result of lambda type of implicit in CheckUnused [#23497](https://github.com/scala/scala3/pull/23497) +- Add suppression if nowarn differs [#23652](https://github.com/scala/scala3/pull/23652) + +## Match Types + +- Fix: #23261 Distinguish 0.0 and -0.0 in ConstantType match types [#23265](https://github.com/scala/scala3/pull/23265) + +## Named Tuples + +- Skip bypassing unapply for scala 2 case classes to allow for single-element named tuple in unapply [#23603](https://github.com/scala/scala3/pull/23603) + +## Parser + +- Enforce `-new-syntax` under `-language:future` [#23443](https://github.com/scala/scala3/pull/23443) +- Disallow Scala 2 implicits under `-source:future` [#23472](https://github.com/scala/scala3/pull/23472) + +## Pattern Matching + +- Fix problems in checking that a constructor is uninhabited for exhaustive match checking [#23403](https://github.com/scala/scala3/pull/23403) + +## Pickling + +- Don't force annotation unpickling when testing for SilentIntoAnnot [#23506](https://github.com/scala/scala3/pull/23506) +- Drop invalid assumption from TastyUnpickler [#23353](https://github.com/scala/scala3/pull/23353) + +## Printer + +- Print update modifier when printing update method definitions [#23392](https://github.com/scala/scala3/pull/23392) + +## Positions + +- Compare span points in pathTo to determine best span [#23581](https://github.com/scala/scala3/pull/23581) +- Add line number magic comment support [#23549](https://github.com/scala/scala3/pull/23549) + +## Presentation Compiler + +- Port Inlay hints for name parameters [#23375](https://github.com/scala/scala3/pull/23375) +- Fix: Simplify infer type for apply [#23434](https://github.com/scala/scala3/pull/23434) +- Fix: Inconsistent annotation tooltips [#23454](https://github.com/scala/scala3/pull/23454) +- Fix adjust type when already exists [#23455](https://github.com/scala/scala3/pull/23455) +- Exclude named parameters inlay hints for java defined [#23462](https://github.com/scala/scala3/pull/23462) +- Fix: StringIndexOutOfBoundsException in presentation compiler's hasColon method [#23498](https://github.com/scala/scala3/pull/23498) +- Add InferredMethodProvider for automatic method signature generation [#23563](https://github.com/scala/scala3/pull/23563) +- Fix completions for Quotes [#23619](https://github.com/scala/scala3/pull/23619) +- Handle default arguments in named parameters for inlay hints [#23641](https://github.com/scala/scala3/pull/23641) +- Fix issue with pc breaking in requiredMethod on newly overloaded valueOf [#23708](https://github.com/scala/scala3/pull/23708) +- Handle default arguments in named parameters for inlay hints [#23641](https://github.com/scala/scala3/pull/23641) +- Fix extracting refinements from intersection types in dynamic select hovers [#23640](https://github.com/scala/scala3/pull/23640) + +## Quotes + +- Skip splice level checking for `` symbols [#22782](https://github.com/scala/scala3/pull/22782) +- Fix stale top level synthetic package object being used in later runs [#23464](https://github.com/scala/scala3/pull/23464) +- Emit an error for quoted pattern type variable after `new` [#23618](https://github.com/scala/scala3/pull/23618) +- Fix issue with certain polyfunctions not properly matching in macros [#23614](https://github.com/scala/scala3/pull/23614) +- Check PCP of constructor calls on the type [#7531](https://github.com/scala/scala3/pull/7531) + +## Reflection + +- Quotes reflect: sort the typeMembers output list and filter out non-members [#22876](https://github.com/scala/scala3/pull/22876) + +## Reporting + +- Add an explainer to the DoubleDefinition error [#23470](https://github.com/scala/scala3/pull/23470) +- Suppress warnings in comprehensions with 22+ binds [#23590](https://github.com/scala/scala3/pull/23590) +- Unhelpful error message when trying to use named extraction, when not matching case class or named tuple [#23354](https://github.com/scala/scala3/pull/23354) +- Improve error message for conflicting definitions [#23453](https://github.com/scala/scala3/pull/23453) +- `-Yprofile-trace` properly report macro splicing source [#23488](https://github.com/scala/scala3/pull/23488) +- `-Yprofile-trace` profiles all inline calls [#23490](https://github.com/scala/scala3/pull/23490) + +## Rewrites + +- Patch empty implicit parens on error recovery [#22835](https://github.com/scala/scala3/pull/22835) +- Rewrite underscore with optional space [#23525](https://github.com/scala/scala3/pull/23525) + +## Runner + +- Bump Scala CLI to v1.9.0 (was v1.8.4) [#23702](https://github.com/scala/scala3/pull/23856) + +## Scaladoc + +- Scaladoc: fixes and improvements to context bounds and extension methods [#22156](https://github.com/scala/scala3/pull/22156) +- Encode path of class [#23503](https://github.com/scala/scala3/pull/23503) + +## SemanticDB + +- Bugfix: Also save infos in semanticdb [#23587](https://github.com/scala/scala3/pull/23587) + +## Transform + +- Handle multiple type parameter lists in value class methods [#23516](https://github.com/scala/scala3/pull/23516) +- Check path of module prefix for tailrec [#23491](https://github.com/scala/scala3/pull/23491) + +## Tuples + +- Normalize tuple types in var args seq literals and classOf instances [#23465](https://github.com/scala/scala3/pull/23465) + +## Typer + +- Fix #22922: Add TypeParamRef handling in isSingletonBounded [#23501](https://github.com/scala/scala3/pull/23501) +- Fix this references everywhere in dependent function types [#23514](https://github.com/scala/scala3/pull/23514) +- Don't approximate a type using `Nothing` as prefix [#23531](https://github.com/scala/scala3/pull/23531) +- Support cleanup actions in class completers [#23515](https://github.com/scala/scala3/pull/23515) +- Fix regressions in asSeenFrom introduced in 3.7 [#23438](https://github.com/scala/scala3/pull/23438) +- Use correct owner in eta expansion [#7564](https://github.com/scala/scala3/pull/7564) +- Fix irrefutability checking in `for` with untupling [#23273](https://github.com/scala/scala3/pull/23273) +- Fix missing members reporting for var setters [#23476](https://github.com/scala/scala3/pull/23476) +- Guard against invalid prefixes in argForParam [#23508](https://github.com/scala/scala3/pull/23508) +- Add missing case to TypeComparer [#23550](https://github.com/scala/scala3/pull/23550) +- Fix match type bounds checking problem [#23695](https://github.com/scala/scala3/pull/23695) +- Generalize "Don't approximate a type using Nothing as prefix" [#23628](https://github.com/scala/scala3/pull/23628) +- More careful ClassTag instantiation [#23659](https://github.com/scala/scala3/pull/23659) +- Use more context for implicit search only if no default argument [#23664](https://github.com/scala/scala3/pull/23664) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.2..3.7.3` these are: + +``` + 85 Martin Odersky + 56 Hamza Remmal + 29 Wojciech Mazur + 20 Som Snytt + 20 noti0na1 + 18 Yichen Xu + 15 Jan Chyb + 9 Matt Bovel + 7 EnzeXing + 7 Guillaume Martres + 6 aherlihy + 5 Sébastien Doeraene + 4 Zieliński Patryk + 3 Oliver Bračevac + 3 Piotr Chabelski + 3 Tomasz Godzik + 2 Alexander + 2 Mikołaj Fornal + 2 Seyon Sivatharan + 1 Alex1005a + 1 HarrisL2 + 1 Jan + 1 Jentsch + 1 Jędrzej Rochala + 1 Kacper Korban + 1 Katarzyna Marek + 1 Marc GRIS + 1 Martin Duhem + 1 Patryk Zieliński + 1 Przemysław Sajnóg + 1 Seth Tisue + 1 Wessel W. Bakker + 1 bingchen-li + 1 kijuky +``` From eb198cfad4876e812699821583548e0730eaf051 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 8 Sep 2025 12:25:30 +0200 Subject: [PATCH 042/128] Release 3.7.3 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 c042a915e167..cd45bdf4ceda 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: From e963a757bbaa59ae9a88dec42353c33d241364df Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 6 Aug 2025 16:11:14 +0200 Subject: [PATCH 043/128] 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 044/128] 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 045/128] 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 046/128] 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 047/128] 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 048/128] 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 049/128] 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 050/128] 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 051/128] 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 052/128] 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 053/128] 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 054/128] 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 055/128] 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 056/128] 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 057/128] 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 058/128] 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 059/128] 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 060/128] 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 061/128] 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 062/128] 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 063/128] 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 064/128] 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 065/128] 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 066/128] 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 067/128] 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 068/128] 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 069/128] 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 070/128] 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 071/128] 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 072/128] 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 073/128] 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 074/128] 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 075/128] 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 076/128] 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 077/128] 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 078/128] 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 079/128] 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 080/128] 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 081/128] 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 082/128] 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 083/128] 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 084/128] 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 085/128] 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 086/128] 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 087/128] 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 088/128] 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 089/128] 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 090/128] 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 091/128] 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 092/128] 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 093/128] 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 094/128] 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 095/128] 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 096/128] 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 097/128] 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 098/128] 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 099/128] 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 100/128] 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 101/128] 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 102/128] 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 103/128] 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 104/128] 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 105/128] [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 106/128] 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 107/128] 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 108/128] 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 109/128] 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 110/128] 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 111/128] 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 112/128] 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 113/128] 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 114/128] 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 115/128] 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 116/128] 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 117/128] 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 118/128] 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 119/128] 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 120/128] 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 121/128] 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 122/128] 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 123/128] 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 124/128] 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 125/128] 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 126/128] 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 127/128] 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 128/128] Release 3.7.4-RC1 Signed-off-by: Wojciech Mazur