From d4d6cec68e0ccab24525a868f9fc9497ec43473c Mon Sep 17 00:00:00 2001 From: kasiaMarek Date: Fri, 23 Aug 2024 11:20:58 +0200 Subject: [PATCH 001/202] use new infer expected type for singleton complations --- .../dotty/tools/pc/InferExpectedType.scala | 3 +- .../pc/completions/SingletonCompletions.scala | 81 ------------------- 2 files changed, 2 insertions(+), 82 deletions(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala index 260a28392093..3d65f69621e1 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala @@ -7,6 +7,7 @@ import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.StdNames import dotty.tools.dotc.core.Symbols +import dotty.tools.dotc.core.Symbols.defn import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.core.Types.Type import dotty.tools.dotc.interactive.Interactive @@ -61,7 +62,7 @@ class InferExpectedType( object InterCompletionType: def inferType(path: List[Tree])(using Context): Option[Type] = path match - case (lit: Literal) :: Select(Literal(_), _) :: Apply(Select(Literal(_), _), List(Literal(Constant(null)))) :: rest => inferType(rest, lit.span) + case (lit: Literal) :: Select(Literal(_), _) :: Apply(Select(Literal(_), _), List(s: Select)) :: rest if s.symbol == defn.Predef_undefined => inferType(rest, lit.span) case ident :: rest => inferType(rest, ident.span) case _ => None diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala index 6e59c9afca3a..53c4e01980bc 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala @@ -7,18 +7,13 @@ import dotty.tools.pc.completions.CompletionValue.SingletonValue import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.core.Flags -import dotty.tools.dotc.core.StdNames import dotty.tools.dotc.core.Symbols import dotty.tools.dotc.core.Types.AndType import dotty.tools.dotc.core.Types.AppliedType import dotty.tools.dotc.core.Types.ConstantType import dotty.tools.dotc.core.Types.OrType -import dotty.tools.dotc.core.Types.TermRef import dotty.tools.dotc.core.Types.Type import dotty.tools.dotc.core.Types.TypeRef -import dotty.tools.dotc.util.Spans.Span -import dotty.tools.dotc.core.Symbols.defn object SingletonCompletions: def contribute( @@ -55,79 +50,3 @@ object SingletonCompletions: collectSingletons(tpe1).intersect(collectSingletons(tpe2)) case _ => Nil -object InterCompletionType: - def inferType(path: List[Tree])(using Context): Option[Type] = - path match - case (lit: Literal) :: Select(Literal(_), _) :: Apply(Select(Literal(_), _), List(s: Select)) :: rest if s.symbol == defn.Predef_undefined => - inferType(rest, lit.span) - case ident :: rest => inferType(rest, ident.span) - case _ => None - - def inferType(path: List[Tree], span: Span)(using Context): Option[Type] = - path match - case Apply(head, List(p : Select)) :: rest if p.name == StdNames.nme.??? && p.qualifier.symbol.name == StdNames.nme.Predef && p.span.isSynthetic => - inferType(rest, span) - case Block(_, expr) :: rest if expr.span.contains(span) => - inferType(rest, span) - case If(cond, _, _) :: rest if !cond.span.contains(span) => - inferType(rest, span) - case Typed(expr, tpt) :: _ if expr.span.contains(span) && !tpt.tpe.isErroneous => Some(tpt.tpe) - case Block(_, expr) :: rest if expr.span.contains(span) => - inferType(rest, span) - case Bind(_, body) :: rest if body.span.contains(span) => inferType(rest, span) - case Alternative(_) :: rest => inferType(rest, span) - case Try(block, _, _) :: rest if block.span.contains(span) => inferType(rest, span) - case CaseDef(_, _, body) :: Try(_, cases, _) :: rest if body.span.contains(span) && cases.exists(_.span.contains(span)) => inferType(rest, span) - case If(cond, _, _) :: rest if !cond.span.contains(span) => inferType(rest, span) - case CaseDef(_, _, body) :: Match(_, cases) :: rest if body.span.contains(span) && cases.exists(_.span.contains(span)) => - inferType(rest, span) - case NamedArg(_, arg) :: rest if arg.span.contains(span) => inferType(rest, span) - // x match - // case @@ - case CaseDef(pat, _, _) :: Match(sel, cases) :: rest if pat.span.contains(span) && cases.exists(_.span.contains(span)) && !sel.tpe.isErroneous => - sel.tpe match - case tpe: TermRef => Some(tpe.symbol.info).filterNot(_.isErroneous) - case tpe => Some(tpe) - // List(@@) - case SeqLiteral(_, tpe) :: _ if !tpe.tpe.isErroneous => - Some(tpe.tpe) - // val _: T = @@ - // def _: T = @@ - case (defn: ValOrDefDef) :: rest if !defn.tpt.tpe.isErroneous => Some(defn.tpt.tpe) - // f(@@) - case (app: Apply) :: rest => - val param = - for { - ind <- app.args.zipWithIndex.collectFirst { - case (arg, id) if arg.span.contains(span) => id - } - params <- app.symbol.paramSymss.find(!_.exists(_.isTypeParam)) - param <- params.get(ind) - } yield param.info - param match - // def f[T](a: T): T = ??? - // f[Int](@@) - // val _: Int = f(@@) - case Some(t : TypeRef) if t.symbol.is(Flags.TypeParam) => - for { - (typeParams, args) <- - app match - case Apply(TypeApply(fun, args), _) => - val typeParams = fun.symbol.paramSymss.headOption.filter(_.forall(_.isTypeParam)) - typeParams.map((_, args.map(_.tpe))) - // val f: (j: "a") => Int - // f(@@) - case Apply(Select(v, StdNames.nme.apply), _) => - v.symbol.info match - case AppliedType(des, args) => - Some((des.typeSymbol.typeParams, args)) - case _ => None - case _ => None - ind = typeParams.indexOf(t.symbol) - tpe <- args.get(ind) - if !tpe.isErroneous - } yield tpe - case Some(tpe) => Some(tpe) - case _ => None - case _ => None - From d0fdbfb4cace1bba61fcffc95fba4de1c236e3eb Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 22 Oct 2024 03:00:58 -0700 Subject: [PATCH 002/202] Nowarn extension matching nonpublic member --- .../dotty/tools/dotc/typer/RefChecks.scala | 23 +++++++++++-------- tests/warn/i21816.scala | 17 ++++++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 tests/warn/i21816.scala diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 0a0356707048..6945dffbe2f2 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -1169,16 +1169,19 @@ object RefChecks { target.nonPrivateMember(sym.name) .filterWithPredicate: member => - val memberIsImplicit = member.info.hasImplicitParams - val paramTps = - if memberIsImplicit then methTp.stripPoly.firstParamTypes - else methTp.firstExplicitParamTypes - - paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || { - val memberParamTps = member.info.stripPoly.firstParamTypes - !memberParamTps.isEmpty - && memberParamTps.lengthCompare(paramTps) == 0 - && memberParamTps.lazyZip(paramTps).forall((m, x) => x frozen_<:< m) + val memberIsPublic = (member.symbol.flags & AccessFlags).isEmpty && !member.symbol.privateWithin.exists + memberIsPublic && { + val memberIsImplicit = member.info.hasImplicitParams + val paramTps = + if memberIsImplicit then methTp.stripPoly.firstParamTypes + else methTp.firstExplicitParamTypes + + paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || { + val memberParamTps = member.info.stripPoly.firstParamTypes + !memberParamTps.isEmpty + && memberParamTps.lengthCompare(paramTps) == 0 + && memberParamTps.lazyZip(paramTps).forall((m, x) => x frozen_<:< m) + } } .exists if !target.typeSymbol.denot.isAliasType && !target.typeSymbol.denot.isOpaqueAlias && hidden diff --git a/tests/warn/i21816.scala b/tests/warn/i21816.scala new file mode 100644 index 000000000000..9153b8b0ee2f --- /dev/null +++ b/tests/warn/i21816.scala @@ -0,0 +1,17 @@ + +case class CC(a: String, b: String) extends Iterable[String] { + override def iterator: Iterator[String] = Iterator(a, b) +} + +trait T { + extension (cc: CC) def className: String = "foo" +} + +object O extends T { + def foo = { + val cc = CC("a", "b") + println(cc.className) + } +} + +@main def main() = O.foo From ae1b583325a160ed980808be7915c1adb66ac22a Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 22 Oct 2024 08:04:10 -0700 Subject: [PATCH 003/202] Prefer isPublic in RefChecks --- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 6945dffbe2f2..0ec9458cac5c 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -525,7 +525,6 @@ object RefChecks { // todo: align accessibility implication checking with isAccessible in Contexts def isOverrideAccessOK = - val memberIsPublic = (member.flags & AccessFlags).isEmpty && !member.privateWithin.exists def protectedOK = !other.is(Protected) || member.is(Protected) // if o is protected, so is m def accessBoundaryOK = val ob = other.accessBoundary(member.owner) @@ -534,7 +533,7 @@ object RefChecks { def companionBoundaryOK = ob.isClass && !ob.isLocalToBlock && mb.is(Module) && (ob.companionModule eq mb.companionModule) ob.isContainedIn(mb) || companionBoundaryOK // m relaxes o's access boundary, def otherIsJavaProtected = other.isAllOf(JavaProtected) // or o is Java defined and protected (see #3946) - memberIsPublic || protectedOK && (accessBoundaryOK || otherIsJavaProtected) + member.isPublic || protectedOK && (accessBoundaryOK || otherIsJavaProtected) end isOverrideAccessOK if !member.hasTargetName(other.targetName) then @@ -1169,8 +1168,7 @@ object RefChecks { target.nonPrivateMember(sym.name) .filterWithPredicate: member => - val memberIsPublic = (member.symbol.flags & AccessFlags).isEmpty && !member.symbol.privateWithin.exists - memberIsPublic && { + member.symbol.isPublic && { val memberIsImplicit = member.info.hasImplicitParams val paramTps = if memberIsImplicit then methTp.stripPoly.firstParamTypes From 144fecbef774ea15f5ed950017906035e90c5097 Mon Sep 17 00:00:00 2001 From: Seth Tisue Date: Wed, 10 Apr 2024 10:01:26 -0700 Subject: [PATCH 004/202] sbt 1.10.5 (was 1.9.9) and compiler-interface to 1.10.4, there is no 1.10.5 for that fastparse was on Scala 2.13.0, needed updating to 2.13.15 --- .github/Dockerfile | 2 +- community-build/community-projects/fastparse | 2 +- community-build/src/scala/dotty/communitybuild/projects.scala | 2 +- project/Dependencies.scala | 2 +- project/build.properties | 2 +- .../sourcepath-with-inline-api-hash/project/build.properties | 2 +- .../sourcepath-with-inline/project/build.properties | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/Dockerfile b/.github/Dockerfile index 59d46fd169d9..3ba5466c30b8 100644 --- a/.github/Dockerfile +++ b/.github/Dockerfile @@ -22,5 +22,5 @@ RUN apt-get update && \ # Install sbt ENV SBT_HOME /usr/local/sbt ENV PATH ${SBT_HOME}/bin:${PATH} -ENV SBT_VERSION 1.9.0 +ENV SBT_VERSION 1.10.5 RUN curl -sL "https://github.com/sbt/sbt/releases/download/v$SBT_VERSION/sbt-$SBT_VERSION.tgz" | gunzip | tar -x -C /usr/local \ No newline at end of file diff --git a/community-build/community-projects/fastparse b/community-build/community-projects/fastparse index 8b93438064ae..4c416b222ad3 160000 --- a/community-build/community-projects/fastparse +++ b/community-build/community-projects/fastparse @@ -1 +1 @@ -Subproject commit 8b93438064aecc7b94f4647af47c0fa237bf89a9 +Subproject commit 4c416b222ad3dcf0bc71656e22407fa49aec193a diff --git a/community-build/src/scala/dotty/communitybuild/projects.scala b/community-build/src/scala/dotty/communitybuild/projects.scala index 31c1bb95743c..2575bb2037c0 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.9.7", + "-sbt-version", "1.10.5", "-Dsbt.supershell=false", s"-Ddotty.communitybuild.dir=$communitybuildDir", s"--addPluginSbtFile=$sbtPluginFilePath" diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 88a97721ee3c..427b53e45221 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -28,5 +28,5 @@ object Dependencies { "com.vladsch.flexmark" % "flexmark-ext-yaml-front-matter" % flexmarkVersion, ) - val compilerInterface = "org.scala-sbt" % "compiler-interface" % "1.9.6" + val compilerInterface = "org.scala-sbt" % "compiler-interface" % "1.10.4" } diff --git a/project/build.properties b/project/build.properties index 04267b14af69..db1723b08622 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.9 +sbt.version=1.10.5 diff --git a/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties index e8a1e246e8ad..db1723b08622 100644 --- a/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties +++ b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.7 +sbt.version=1.10.5 diff --git a/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/build.properties b/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/build.properties index e8a1e246e8ad..db1723b08622 100644 --- a/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/build.properties +++ b/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.7 +sbt.version=1.10.5 From 46cd2565e1bc9d5b660bfc0218cdfc5834fa934e Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 11 Nov 2024 22:10:30 +0100 Subject: [PATCH 005/202] Allow discarding "Discarded non-Unit" warnings with `: Unit` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: nmc.borst <1698211+nmcb@users.noreply.github.com> Co-Authored-By: Jędrzej Rochala <48657087+rochala@users.noreply.github.com> --- .../dotty/tools/dotc/reporting/messages.scala | 4 ++-- .../src/dotty/tools/dotc/typer/Typer.scala | 22 +++++++++++++++---- tests/neg-custom-args/captures/real-try.check | 2 +- tests/neg/i13091.check | 22 +++++++------------ tests/neg/i13091.scala | 2 +- tests/neg/i18408a.check | 2 +- tests/neg/i18408b.check | 2 +- tests/neg/i18408c.check | 2 +- tests/neg/spaces-vs-tabs.check | 6 ----- tests/neg/spaces-vs-tabs.scala | 2 +- tests/warn/21557.check | 18 +++++++++++++++ tests/warn/21557.scala | 19 ++++++++++++++++ tests/warn/i18722.check | 8 +++---- tests/warn/nonunit-statement.check | 20 ++++++++--------- tests/warn/warn-value-discard.check | 10 ++++----- 15 files changed, 90 insertions(+), 51 deletions(-) create mode 100644 tests/warn/21557.check create mode 100644 tests/warn/21557.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 3b7fba1cb52d..1126e78ea62a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2469,7 +2469,7 @@ class PureExpressionInStatementPosition(stat: untpd.Tree, val exprOwner: Symbol) class PureUnitExpression(stat: untpd.Tree, tpe: Type)(using Context) extends Message(PureUnitExpressionID) { def kind = MessageKind.PotentialIssue - def msg(using Context) = i"Discarded non-Unit value of type ${tpe.widen}. You may want to use `()`." + def msg(using Context) = i"Discarded non-Unit value of type ${tpe.widen}. Add `: Unit` to discard silently." def explain(using Context) = i"""As this expression is not of type Unit, it is desugared into `{ $stat; () }`. |Here the `$stat` expression is a pure statement that can be discarded. @@ -3173,7 +3173,7 @@ class InlinedAnonClassWarning()(using Context) class ValueDiscarding(tp: Type)(using Context) extends Message(ValueDiscardingID): def kind = MessageKind.PotentialIssue - def msg(using Context) = i"discarded non-Unit value of type $tp" + def msg(using Context) = i"discarded non-Unit value of type ${tp.widen}. Add `: Unit` to discard silently." def explain(using Context) = "" class UnusedNonUnitValue(tp: Type)(using Context) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bbd78b5dcc2e..3c32e9aea765 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -81,6 +81,9 @@ object Typer { /** Indicates that a definition was copied over from the parent refinements */ val RefinementFromParent = new Property.StickyKey[Unit] + /** Indicates that an expression is explicitly ascribed to [[Unit]] type. */ + val AscribedToUnit = new Property.StickyKey[Unit] + /** An attachment on a Select node with an `apply` field indicating that the `apply` * was inserted by the Typer. */ @@ -1193,7 +1196,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else tpt val expr1 = if isWildcard then tree.expr.withType(underlyingTreeTpe.tpe) - else typed(tree.expr, underlyingTreeTpe.tpe.widenSkolem) + else + if underlyingTreeTpe.tpe.isRef(defn.UnitClass) then + untpd.unsplice(tree.expr).putAttachment(AscribedToUnit, ()) + typed(tree.expr, underlyingTreeTpe.tpe.widenSkolem) assignType(cpy.Typed(tree)(expr1, tpt), underlyingTreeTpe) .withNotNullInfo(expr1.notNullInfo) } @@ -3377,7 +3383,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else if (ctx.mode.is(Mode.Pattern)) typedUnApply(cpy.Apply(tree)(op, l :: r :: Nil), pt) else { - val app = typedApply(desugar.binop(l, op, r), pt) + val app = typedApply(desugar.binop(l, op, r).withAttachmentsFrom(tree), pt) if op.name.isRightAssocOperatorName && !ctx.mode.is(Mode.QuotedExprPattern) then val defs = new mutable.ListBuffer[Tree] def lift(app: Tree): Tree = (app: @unchecked) match @@ -4581,9 +4587,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // so will take the code path that decides on inlining val tree1 = adapt(tree, WildcardType, locked) checkStatementPurity(tree1)(tree, ctx.owner, isUnitExpr = true) - if (!ctx.isAfterTyper && !tree.isInstanceOf[Inlined] && ctx.settings.Whas.valueDiscard && !isThisTypeResult(tree)) { + + if ctx.settings.Whas.valueDiscard + && !ctx.isAfterTyper + && !tree.isInstanceOf[Inlined] + && !isThisTypeResult(tree) + && !tree.hasAttachment(AscribedToUnit) then report.warning(ValueDiscarding(tree.tpe), tree.srcPos) - } + return tpd.Block(tree1 :: Nil, unitLiteral) } @@ -4839,6 +4850,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // sometimes we do not have the original anymore and use the transformed tree instead. // But taken together, the two criteria are quite accurate. missingArgs(tree, tree.tpe.widen) + case _ if tree.hasAttachment(AscribedToUnit) => + // The tree was ascribed to `Unit` explicitly to silence the warning. + () case _ if isUnitExpr => report.warning(PureUnitExpression(original, tree.tpe), original.srcPos) case _ => diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index 7f8ab50bc222..6df092885384 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -1,7 +1,7 @@ -- [E190] Potential Issue Warning: tests/neg-custom-args/captures/real-try.scala:38:4 ---------------------------------- 38 | b.x | ^^^ - | Discarded non-Unit value of type () -> Unit. You may want to use `()`. + | Discarded non-Unit value of type () -> Unit. Add `: Unit` to discard silently. | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/real-try.scala:14:2 ----------------------------------------------------------- diff --git a/tests/neg/i13091.check b/tests/neg/i13091.check index 5cd793a9cfcb..bf8207d85e51 100644 --- a/tests/neg/i13091.check +++ b/tests/neg/i13091.check @@ -1,15 +1,9 @@ --- [E190] Potential Issue Warning: tests/neg/i13091.scala:7:17 --------------------------------------------------------- -7 |def test: Unit = new Foo // error: class Foo is marked @experimental ... - | ^^^^^^^ - | Discarded non-Unit value of type Foo. You may want to use `()`. +-- Error: tests/neg/i13091.scala:7:16 ---------------------------------------------------------------------------------- +7 |def test = (new Foo): Unit // error: class Foo is marked @experimental ... + | ^^^ + | class Foo is marked @experimental | - | longer explanation available when compiling with `-explain` --- Error: tests/neg/i13091.scala:7:21 ---------------------------------------------------------------------------------- -7 |def test: Unit = new Foo // error: class Foo is marked @experimental ... - | ^^^ - | class Foo is marked @experimental - | - | Experimental definition may only be used under experimental mode: - | 1. in a definition marked as @experimental, or - | 2. an experimental feature is imported at the package level, or - | 3. compiling with the -experimental compiler flag. + | Experimental definition may only be used under experimental mode: + | 1. in a definition marked as @experimental, or + | 2. an experimental feature is imported at the package level, or + | 3. compiling with the -experimental compiler flag. diff --git a/tests/neg/i13091.scala b/tests/neg/i13091.scala index 549fdf6d0fae..8ee77efa37c1 100644 --- a/tests/neg/i13091.scala +++ b/tests/neg/i13091.scala @@ -4,4 +4,4 @@ import annotation.experimental @experimental class Foo -def test: Unit = new Foo // error: class Foo is marked @experimental ... +def test = (new Foo): Unit // error: class Foo is marked @experimental ... diff --git a/tests/neg/i18408a.check b/tests/neg/i18408a.check index ff278e6fe5cb..2fb701f90712 100644 --- a/tests/neg/i18408a.check +++ b/tests/neg/i18408a.check @@ -7,7 +7,7 @@ -- [E190] Potential Issue Warning: tests/neg/i18408a.scala:3:15 -------------------------------------------------------- 3 |def test1 = fa(42) | ^^ - | Discarded non-Unit value of type Int. You may want to use `()`. + | Discarded non-Unit value of type Int. Add `: Unit` to discard silently. | | longer explanation available when compiling with `-explain` -- [E129] Potential Issue Warning: tests/neg/i18408a.scala:4:16 -------------------------------------------------------- diff --git a/tests/neg/i18408b.check b/tests/neg/i18408b.check index 7c72833fe5ad..069c8345742b 100644 --- a/tests/neg/i18408b.check +++ b/tests/neg/i18408b.check @@ -7,7 +7,7 @@ -- [E190] Potential Issue Warning: tests/neg/i18408b.scala:3:15 -------------------------------------------------------- 3 |def test1 = fa(42) | ^^ - | Discarded non-Unit value of type Int. You may want to use `()`. + | Discarded non-Unit value of type Int. Add `: Unit` to discard silently. | | longer explanation available when compiling with `-explain` -- [E129] Potential Issue Warning: tests/neg/i18408b.scala:4:16 -------------------------------------------------------- diff --git a/tests/neg/i18408c.check b/tests/neg/i18408c.check index 078f42bb0006..994e6b499371 100644 --- a/tests/neg/i18408c.check +++ b/tests/neg/i18408c.check @@ -7,7 +7,7 @@ -- [E190] Potential Issue Warning: tests/neg/i18408c.scala:3:15 -------------------------------------------------------- 3 |def test1 = fa(42) | ^^ - | Discarded non-Unit value of type Int. You may want to use `()`. + | Discarded non-Unit value of type Int. Add `: Unit` to discard silently. | | longer explanation available when compiling with `-explain` -- [E129] Potential Issue Warning: tests/neg/i18408c.scala:4:16 -------------------------------------------------------- diff --git a/tests/neg/spaces-vs-tabs.check b/tests/neg/spaces-vs-tabs.check index f8374618f0fd..ce55267b0cce 100644 --- a/tests/neg/spaces-vs-tabs.check +++ b/tests/neg/spaces-vs-tabs.check @@ -28,9 +28,3 @@ | The start of this line does not match any of the previous indentation widths. | Indentation width of current line : 1 tab, 2 spaces | This falls between previous widths: 1 tab and 1 tab, 4 spaces --- [E190] Potential Issue Warning: tests/neg/spaces-vs-tabs.scala:13:7 ------------------------------------------------- -13 | 1 - | ^ - | Discarded non-Unit value of type Int. You may want to use `()`. - | - | longer explanation available when compiling with `-explain` diff --git a/tests/neg/spaces-vs-tabs.scala b/tests/neg/spaces-vs-tabs.scala index 4f48d784eb7d..eafc5209ca47 100644 --- a/tests/neg/spaces-vs-tabs.scala +++ b/tests/neg/spaces-vs-tabs.scala @@ -10,6 +10,6 @@ object Test: object Test2: if true then - 1 + () else 2 // error diff --git a/tests/warn/21557.check b/tests/warn/21557.check new file mode 100644 index 000000000000..1753960b852a --- /dev/null +++ b/tests/warn/21557.check @@ -0,0 +1,18 @@ +-- [E190] Potential Issue Warning: tests/warn/21557.scala:9:16 --------------------------------------------------------- +9 | val x: Unit = 1 + 1 // warn + | ^^^^^ + | Discarded non-Unit value of type Int. Add `: Unit` to discard silently. + | + | longer explanation available when compiling with `-explain` +-- [E176] Potential Issue Warning: tests/warn/21557.scala:10:2 --------------------------------------------------------- +10 | 1 + 1 // warn + | ^^^^^ + | unused value of type (2 : Int) +-- [E175] Potential Issue Warning: tests/warn/21557.scala:15:52 -------------------------------------------------------- +15 | val x1: Unit = new Assertion("another").shouldPass() // warn (enabled by -Wvalue-discard) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | discarded non-Unit value of type Assertion. Add `: Unit` to discard silently. +-- [E176] Potential Issue Warning: tests/warn/21557.scala:16:41 -------------------------------------------------------- +16 | new Assertion("yet another").shouldPass() // warn (enabled by -Wnonunit-statement) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | unused value of type Assertion diff --git a/tests/warn/21557.scala b/tests/warn/21557.scala new file mode 100644 index 000000000000..ed7028b6a48a --- /dev/null +++ b/tests/warn/21557.scala @@ -0,0 +1,19 @@ +//> using options -Wvalue-discard -Wnonunit-statement + +class Assertion(assert: => Any): + def shouldPass(): Assertion = ??? + +def test: Unit = + 1 + 1: Unit + (1 + 1): Unit + val x: Unit = 1 + 1 // warn + 1 + 1 // warn + val y: Int = 1 + 1 + + new Assertion("").shouldPass(): Unit + (new Assertion("").shouldPass()): Unit + val x1: Unit = new Assertion("another").shouldPass() // warn (enabled by -Wvalue-discard) + new Assertion("yet another").shouldPass() // warn (enabled by -Wnonunit-statement) + val y1: Assertion = new Assertion("another other").shouldPass() + + () diff --git a/tests/warn/i18722.check b/tests/warn/i18722.check index 4fae07e15204..4b32cb418167 100644 --- a/tests/warn/i18722.check +++ b/tests/warn/i18722.check @@ -1,7 +1,7 @@ -- [E190] Potential Issue Warning: tests/warn/i18722.scala:3:15 -------------------------------------------------------- 3 |def f1: Unit = null // warn | ^^^^ - | Discarded non-Unit value of type Null. You may want to use `()`. + | Discarded non-Unit value of type Null. Add `: Unit` to discard silently. |--------------------------------------------------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -12,7 +12,7 @@ -- [E190] Potential Issue Warning: tests/warn/i18722.scala:4:15 -------------------------------------------------------- 4 |def f2: Unit = 1 // warn | ^ - | Discarded non-Unit value of type Int. You may want to use `()`. + | Discarded non-Unit value of type Int. Add `: Unit` to discard silently. |--------------------------------------------------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -23,7 +23,7 @@ -- [E190] Potential Issue Warning: tests/warn/i18722.scala:5:15 -------------------------------------------------------- 5 |def f3: Unit = "a" // warn | ^^^ - | Discarded non-Unit value of type String. You may want to use `()`. + | Discarded non-Unit value of type String. Add `: Unit` to discard silently. |--------------------------------------------------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -34,7 +34,7 @@ -- [E190] Potential Issue Warning: tests/warn/i18722.scala:7:15 -------------------------------------------------------- 7 |def f4: Unit = i // warn | ^ - | Discarded non-Unit value of type Int. You may want to use `()`. + | Discarded non-Unit value of type Int. Add `: Unit` to discard silently. |--------------------------------------------------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/warn/nonunit-statement.check b/tests/warn/nonunit-statement.check index 742a9fe911e8..7e7939e1c163 100644 --- a/tests/warn/nonunit-statement.check +++ b/tests/warn/nonunit-statement.check @@ -27,15 +27,15 @@ -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:58:19 -------------------------------------------- 58 | if (!isEmpty) f(a) // warn (if) | ^^^^ - | discarded non-Unit value of type U + | discarded non-Unit value of type U. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:62:7 --------------------------------------------- 62 | f(a) // warn (if) | ^^^^ - | discarded non-Unit value of type Boolean + | discarded non-Unit value of type Boolean. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:73:25 -------------------------------------------- 73 | if (!fellback) action(z) // warn (if) | ^^^^^^^^^ - | discarded non-Unit value of type U + | discarded non-Unit value of type U. Add `: Unit` to discard silently. -- [E176] Potential Issue Warning: tests/warn/nonunit-statement.scala:79:6 --------------------------------------------- 79 | g // warn block statement | ^ @@ -43,7 +43,7 @@ -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:81:6 --------------------------------------------- 81 | g // warn (if) | ^ - | discarded non-Unit value of type (g : => Int) + | discarded non-Unit value of type Int. Add `: Unit` to discard silently. -- [E176] Potential Issue Warning: tests/warn/nonunit-statement.scala:84:6 --------------------------------------------- 84 | g // warn | ^ @@ -51,7 +51,7 @@ -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:86:6 --------------------------------------------- 86 | g // warn | ^ - | discarded non-Unit value of type (g : => Int) + | discarded non-Unit value of type Int. Add `: Unit` to discard silently. -- [E176] Potential Issue Warning: tests/warn/nonunit-statement.scala:96:4 --------------------------------------------- 96 | if (b) { // warn, at least one branch looks interesting | ^ @@ -70,20 +70,20 @@ -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:126:37 ------------------------------------------- 126 | if (start.length != 0) jsb.append(start) // warn (value-discard) | ^^^^^^^^^^^^^^^^^ - | discarded non-Unit value of type StringBuilder + | discarded non-Unit value of type StringBuilder. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:132:18 ------------------------------------------- 132 | jsb.append(it.next()) // warn (value-discard) | ^^^^^^^^^^^^^^^^^^^^^ - | discarded non-Unit value of type StringBuilder + | discarded non-Unit value of type StringBuilder. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:135:35 ------------------------------------------- 135 | if (end.length != 0) jsb.append(end) // warn (value-discard) | ^^^^^^^^^^^^^^^ - | discarded non-Unit value of type StringBuilder + | discarded non-Unit value of type StringBuilder. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:141:14 ------------------------------------------- 141 | b.append(it.next()) // warn (value-discard) | ^^^^^^^^^^^^^^^^^^^ - | discarded non-Unit value of type StringBuilder + | discarded non-Unit value of type StringBuilder. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:146:30 ------------------------------------------- 146 | while (it.hasNext) it.next() // warn | ^^^^^^^^^ - | discarded non-Unit value of type String + | discarded non-Unit value of type String. Add `: Unit` to discard silently. diff --git a/tests/warn/warn-value-discard.check b/tests/warn/warn-value-discard.check index ca6fedb29053..dcd6d62c00e0 100644 --- a/tests/warn/warn-value-discard.check +++ b/tests/warn/warn-value-discard.check @@ -1,20 +1,20 @@ -- [E175] Potential Issue Warning: tests/warn/warn-value-discard.scala:27:36 ------------------------------------------- 27 | mutable.Set.empty[String].remove("") // warn | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | discarded non-Unit value of type Boolean + | discarded non-Unit value of type Boolean. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/warn-value-discard.scala:39:41 ------------------------------------------- 39 | mutable.Set.empty[String].subtractOne("") // warn | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | discarded non-Unit value of type scala.collection.mutable.Set[String] + | discarded non-Unit value of type scala.collection.mutable.Set[String]. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/warn-value-discard.scala:59:4 -------------------------------------------- 59 | mutable.Set.empty[String] += "" // warn | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | discarded non-Unit value of type scala.collection.mutable.Set[String] + | discarded non-Unit value of type scala.collection.mutable.Set[String]. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/warn-value-discard.scala:15:35 ------------------------------------------- 15 | firstThing().map(_ => secondThing()) // warn | ^^^^^^^^^^^^^ - | discarded non-Unit value of type Either[Failed, Unit] + | discarded non-Unit value of type Either[Failed, Unit]. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/warn-value-discard.scala:18:35 ------------------------------------------- 18 | firstThing().map(_ => secondThing()) // warn | ^^^^^^^^^^^^^ - | discarded non-Unit value of type Either[Failed, Unit] + | discarded non-Unit value of type Either[Failed, Unit]. Add `: Unit` to discard silently. From 26ecda540b93fbe1fc7be030559a78dd2db364f2 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 14 Nov 2024 22:11:20 +0000 Subject: [PATCH 006/202] Drop phase.isTyper use in isLegalPrefix/asf Note that "asSeenFrom" (aka asf) is used by SymDenotation#findMember, which is used by TypeComparer's "hasMatchingMember", as a part of "compareRefinedSlow". Previously (using the minimisation in i17222.8.scala) `summonOne` is inlined during the "inlining" phase, while "summonInline" is inlined during typing. The result is that during inlining we fail to find `Reader[BC, Int]` because we incorrectly consider `A{type F = Int}` as stable. --- .../dotty/tools/dotc/core/TypeComparer.scala | 5 +- .../src/dotty/tools/dotc/core/TypeOps.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 29 ++++---- .../dotty/tools/dotc/transform/Recheck.scala | 4 +- .../test/dotc/pos-test-pickling.blacklist | 1 + tests/neg/6314-6.check | 4 +- tests/neg/i6225.scala | 2 +- tests/pos/i17222.2.scala | 30 ++++++++ tests/pos/i17222.3.scala | 39 ++++++++++ tests/pos/i17222.4.scala | 71 +++++++++++++++++++ tests/pos/i17222.5.scala | 26 +++++++ tests/pos/i17222.8.scala | 18 +++++ tests/pos/i17222.scala | 33 +++++++++ 13 files changed, 240 insertions(+), 24 deletions(-) create mode 100644 tests/pos/i17222.2.scala create mode 100644 tests/pos/i17222.3.scala create mode 100644 tests/pos/i17222.4.scala create mode 100644 tests/pos/i17222.5.scala create mode 100644 tests/pos/i17222.8.scala create mode 100644 tests/pos/i17222.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 17d427513e58..9cef4148747e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -369,7 +369,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling } compareWild case tp2: LazyRef => - isBottom(tp1) || !tp2.evaluating && recur(tp1, tp2.ref) + isBottom(tp1) + || !tp2.evaluating && recur(tp1, tp2.ref) case CapturingType(_, _) => secondTry case tp2: AnnotatedType if !tp2.isRefining => @@ -489,7 +490,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // If `tp1` is in train of being evaluated, don't force it // because that would cause an assertionError. Return false instead. // See i859.scala for an example where we hit this case. - tp2.isRef(AnyClass, skipRefined = false) + tp2.isAny || !tp1.evaluating && recur(tp1.ref, tp2) case AndType(tp11, tp12) => if tp11.stripTypeVar eq tp12.stripTypeVar then recur(tp11, tp2) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 2403a6e22bc6..c27b3c3978d7 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -124,7 +124,7 @@ object TypeOps: } def isLegalPrefix(pre: Type)(using Context): Boolean = - pre.isStable || !ctx.phase.isTyper + pre.isStable /** Implementation of Types#simplified */ def simplify(tp: Type, theMap: SimplifyMap | Null)(using Context): Type = { diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d8a4453325f2..2d53150c72cc 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -99,12 +99,8 @@ object Types extends TypeUtils { // ----- Tests ----------------------------------------------------- // // debug only: a unique identifier for a type -// val uniqId = { -// nextId = nextId + 1 -// if (nextId == 19555) -// println("foo") -// nextId -// } +// val uniqId = { nextId = nextId + 1; nextId } +// if uniqId == 19555 then trace.dumpStack() /** A cache indicating whether the type was still provisional, last time we checked */ @sharable private var mightBeProvisional = true @@ -5577,24 +5573,25 @@ object Types extends TypeUtils { } def & (that: TypeBounds)(using Context): TypeBounds = + val lo1 = this.lo.stripLazyRef + val lo2 = that.lo.stripLazyRef + val hi1 = this.hi.stripLazyRef + val hi2 = that.hi.stripLazyRef + // This will try to preserve the FromJavaObjects type in upper bounds. // For example, (? <: FromJavaObjects | Null) & (? <: Any), // we want to get (? <: FromJavaObjects | Null) intead of (? <: Any), // because we may check the result <:< (? <: Object | Null) later. - if this.hi.containsFromJavaObject - && (this.hi frozen_<:< that.hi) - && (that.lo frozen_<:< this.lo) then + if hi1.containsFromJavaObject && (hi1 frozen_<:< hi2) && (lo2 frozen_<:< lo1) then // FromJavaObject in tp1.hi guarantees tp2.hi <:< tp1.hi // prefer tp1 if FromJavaObject is in its hi this - else if that.hi.containsFromJavaObject - && (that.hi frozen_<:< this.hi) - && (this.lo frozen_<:< that.lo) then + else if hi2.containsFromJavaObject && (hi2 frozen_<:< hi1) && (lo1 frozen_<:< lo2) then // Similarly, prefer tp2 if FromJavaObject is in its hi that - else if (this.lo frozen_<:< that.lo) && (that.hi frozen_<:< this.hi) then that - else if (that.lo frozen_<:< this.lo) && (this.hi frozen_<:< that.hi) then this - else TypeBounds(this.lo | that.lo, this.hi & that.hi) + else if (lo1 frozen_<:< lo2) && (hi2 frozen_<:< hi1) then that + else if (lo2 frozen_<:< lo1) && (hi1 frozen_<:< hi2) then this + else TypeBounds(lo1 | lo2, hi1 & hi2) def | (that: TypeBounds)(using Context): TypeBounds = if ((this.lo frozen_<:< that.lo) && (that.hi frozen_<:< this.hi)) this @@ -5603,7 +5600,7 @@ object Types extends TypeUtils { override def & (that: Type)(using Context): Type = that match { case that: TypeBounds => this & that - case _ => super.& (that) + case _ => super.&(that) } override def | (that: Type)(using Context): Type = that match { diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 8df9e5966920..be1d9d8bee54 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -217,10 +217,10 @@ abstract class Recheck extends Phase, SymTransformer: sharpen: Denotation => Denotation)(using Context): Type = if name.is(OuterSelectName) then tree.tpe else - //val pre = ta.maybeSkolemizePrefix(qualType, name) + val pre = ta.maybeSkolemizePrefix(qualType, name) val mbr = sharpen( - qualType.findMember(name, qualType, + qualType.findMember(name, pre, excluded = if tree.symbol.is(Private) then EmptyFlags else Private )).suchThat(tree.symbol == _) val newType = tree.tpe match diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 032b53150e49..ebdd414ea7f2 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -23,6 +23,7 @@ t5031_2.scala i16997.scala i7414.scala i17588.scala +i8300.scala i9804.scala i13433.scala i16649-irrefutable.scala diff --git a/tests/neg/6314-6.check b/tests/neg/6314-6.check index 7d6bd182173d..df988f1db9dd 100644 --- a/tests/neg/6314-6.check +++ b/tests/neg/6314-6.check @@ -4,7 +4,7 @@ |object creation impossible, since def apply(fa: String): Int in trait XX in object Test3 is not defined |(Note that | parameter String in def apply(fa: String): Int in trait XX in object Test3 does not match - | parameter Test3.Bar[X & Object with Test3.YY {...}#Foo] in def apply(fa: Test3.Bar[X & YY.this.Foo]): Test3.Bar[Y & YY.this.Foo] in trait YY in object Test3 + | parameter Test3.Bar[X & (X & Y)] in def apply(fa: Test3.Bar[X & YY.this.Foo]): Test3.Bar[Y & YY.this.Foo] in trait YY in object Test3 | ) -- Error: tests/neg/6314-6.scala:52:3 ---------------------------------------------------------------------------------- 52 | (new YY {}).boom // error: object creation impossible @@ -12,5 +12,5 @@ |object creation impossible, since def apply(fa: String): Int in trait XX in object Test4 is not defined |(Note that | parameter String in def apply(fa: String): Int in trait XX in object Test4 does not match - | parameter Test4.Bar[X & Object with Test4.YY {...}#FooAlias] in def apply(fa: Test4.Bar[X & YY.this.FooAlias]): Test4.Bar[Y & YY.this.FooAlias] in trait YY in object Test4 + | parameter Test4.Bar[X & (X & Y)] in def apply(fa: Test4.Bar[X & YY.this.FooAlias]): Test4.Bar[Y & YY.this.FooAlias] in trait YY in object Test4 | ) diff --git a/tests/neg/i6225.scala b/tests/neg/i6225.scala index 148a484fd0f1..bb936c9a79b1 100644 --- a/tests/neg/i6225.scala +++ b/tests/neg/i6225.scala @@ -1,4 +1,4 @@ -object O1 { +object O1 { // error: cannot be instantiated type A[X] = X opaque type T = A // error: opaque type alias must be fully applied } diff --git a/tests/pos/i17222.2.scala b/tests/pos/i17222.2.scala new file mode 100644 index 000000000000..34db494750c4 --- /dev/null +++ b/tests/pos/i17222.2.scala @@ -0,0 +1,30 @@ +import scala.compiletime.* + +trait Reader[-In, Out] + +trait A: + type T + type F[X] + type Q = F[T] + +object Reader: + + given [X]: Reader[A { type Q = X }, X] with {} + +object Test: + + trait B[X] extends A: + type T = X + + trait C extends A: + type F[X] = X + + trait D[X] extends B[X] with C + + val d = new D[Int] {} + val bc = new B[Int] with C + + summonAll[(Reader[d.type, Int], Reader[d.type, Int])] // works + summonAll[(Reader[bc.type, Int], Reader[bc.type, Int])] // error + summonInline[Reader[d.type, Int]] // works + summonInline[Reader[bc.type, Int]] // works?? diff --git a/tests/pos/i17222.3.scala b/tests/pos/i17222.3.scala new file mode 100644 index 000000000000..7ca85f65278f --- /dev/null +++ b/tests/pos/i17222.3.scala @@ -0,0 +1,39 @@ +import scala.compiletime.* + +trait Reader[-In, Out] + +trait A: + type T + type F[X] + type Q = F[T] + +object Reader: + + given [X]: Reader[A { type Q = X }, X] with {} + +object Test: + + trait B[X] extends A: + type T = X + + trait C extends A: + type F[X] = X + + trait D[X] extends B[X] with C + + val d = new D[Int] {} + val bc = new B[Int] with C + + case class Box[T](value: T) + + /** compiletime.summonAll, but with one case */ + inline def summonOne[T <: Box[?]]: T = + val res = + inline erasedValue[T] match + case _: Box[t] => summonInline[t] + end match + Box(res).asInstanceOf[T] + end summonOne + + summonOne[Box[Reader[d.type, Int]]] // works + summonOne[Box[Reader[bc.type, Int]]] // errors diff --git a/tests/pos/i17222.4.scala b/tests/pos/i17222.4.scala new file mode 100644 index 000000000000..209425d47915 --- /dev/null +++ b/tests/pos/i17222.4.scala @@ -0,0 +1,71 @@ +import scala.compiletime.* + +trait Reader[-In, Out] + +trait A: + type T + type F[X] + type Q = F[T] + +given [X]: Reader[A { type Q = X }, X] with {} + +case class Box[T](x: T) + +/** compiletime.summonAll, but with one case */ +inline def summonOne[T]: T = + val res = + inline erasedValue[T] match + case _: Box[t] => summonInline[t] + end match + Box(res).asInstanceOf[T] +end summonOne + + +@main def main = + + + trait B[X] extends A: + type T = X + + trait C extends A: + type F[X] = X + + + val bc = new B[Int] with C + + summonOne[Box[Reader[bc.type, Int]]] // errors + + + val bc2: A { type Q = Int } = new B[Int] with C + + summonOne[Box[Reader[bc2.type, Int]]] // works + + + object BC extends B[Int] with C + + summonOne[Box[Reader[BC.type, Int]]] // works + + + val a = new A: + type T = Int + type F[X] = X + + summonOne[Box[Reader[a.type, Int]]] // works + + + val b = new B[Int]: + type F[X] = X + + summonOne[Box[Reader[b.type, Int]]] // works + + + val ac = new A with C: + type T = Int + + summonOne[Box[Reader[ac.type, Int]]] // works + + + trait D[X] extends B[X] with C + val d = new D[Int] {} + + summonOne[Box[Reader[d.type, Int]]] // works diff --git a/tests/pos/i17222.5.scala b/tests/pos/i17222.5.scala new file mode 100644 index 000000000000..dc608e94235c --- /dev/null +++ b/tests/pos/i17222.5.scala @@ -0,0 +1,26 @@ +import scala.compiletime.* + +trait Reader[-In, Out] + +trait A: + type T + type F[X] + type Q = F[T] + +given [X]: Reader[A { type Q = X }, X] with {} + +case class Box[T](x: T) + +inline def summonOne[T]: T = + summonInline[T] +end summonOne + +@main def main = + trait B[X] extends A: + type T = X + trait C extends A: + type F[X] = X + + val bc = new B[Int] with C + summonInline[Reader[bc.type, Int]] // (I) Works + summonOne[Reader[bc.type, Int]] // (II) Errors diff --git a/tests/pos/i17222.8.scala b/tests/pos/i17222.8.scala new file mode 100644 index 000000000000..a415a78e0703 --- /dev/null +++ b/tests/pos/i17222.8.scala @@ -0,0 +1,18 @@ +import scala.compiletime.* + +trait A: + type F + type Q = F + +trait Reader[-In, Out] +object Reader: + given [X]: Reader[A { type Q = X }, X] with {} + +class Test: + //type BC = A { type F = Int } & A // ok + type BC = A & A { type F = Int } // fail, also ok when manually de-aliased + + inline def summonOne: Unit = summonInline[Reader[BC, Int]] + + def t1(): Unit = summonInline[Reader[BC, Int]] // ok + def t2(): Unit = summonOne // error diff --git a/tests/pos/i17222.scala b/tests/pos/i17222.scala new file mode 100644 index 000000000000..2af9fc2861a8 --- /dev/null +++ b/tests/pos/i17222.scala @@ -0,0 +1,33 @@ +import scala.deriving.Mirror +import scala.compiletime.* + +trait Reader[-In, Out] + +trait A: + type T + type F[X] + type Q = F[T] + +object Reader: + + given [X]: Reader[A { type Q = X }, X] with {} + + type Map2[Tup1 <: Tuple, Tup2 <: Tuple, F[_, _]] <: Tuple = (Tup1, Tup2) match + case (h1 *: t1, h2 *: t2) => F[h1, h2] *: Map2[t1, t2, F] + case (EmptyTuple, EmptyTuple) => EmptyTuple + + inline given productReader[In <: Product, Out <: Product](using mi: Mirror.ProductOf[In])(using mo: Mirror.ProductOf[Out]): Reader[In, Out] = + summonAll[Map2[mi.MirroredElemTypes, mo.MirroredElemTypes, Reader]] + ??? + +object Test: + + trait B[X] extends A: + type T = X + + trait C extends A: + type F[X] = X + + val bc = new B[Int] with C + + summon[Reader[(bc.type, bc.type), (Int, Int)]] // fails From 0293318877b981985d3d61f11dd5bef34fff753f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 16 Nov 2024 13:47:41 +0000 Subject: [PATCH 007/202] Fix use of memberType in scaladoc --- scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index d3c93aaba8c7..ee12755c7f98 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -577,7 +577,8 @@ trait ClassLikeSupport: def unwrapMemberInfo(c: ClassDef, symbol: Symbol): MemberInfo = - val baseTypeRepr = typeForClass(c).memberType(symbol) + val qualTypeRepr = if c.symbol.isClassDef then This(c.symbol).tpe else typeForClass(c) + val baseTypeRepr = qualTypeRepr.memberType(symbol) def isSyntheticEvidence(name: String) = if !name.startsWith(NameKinds.ContextBoundParamName.separator) then false else From d64766fce91cca6a30a4949f5a7ca33dd40e19b3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 18 Nov 2024 13:12:49 +0000 Subject: [PATCH 008/202] Relax method signature matching under Mode.CheckBoundsOrSelfType --- .../tools/dotc/core/CheckRealizable.scala | 4 +- .../dotty/tools/dotc/core/TypeComparer.scala | 7 +- tests/pos/i17222.izumi.min.scala | 7 + .../pos/i17222.izumi.rep/InspectorBase.scala | 59 ++++ .../pos/i17222.izumi.rep/ReflectionUtil.scala | 331 ++++++++++++++++++ 5 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 tests/pos/i17222.izumi.min.scala create mode 100644 tests/pos/i17222.izumi.rep/InspectorBase.scala create mode 100644 tests/pos/i17222.izumi.rep/ReflectionUtil.scala diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index d8241f3ff304..81b03d765676 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -131,8 +131,8 @@ class CheckRealizable(using Context) { /** `Realizable` if `tp` has good bounds, a `HasProblem...` instance * pointing to a bad bounds member otherwise. "Has good bounds" means: * - * - all type members have good bounds (except for opaque helpers) - * - all refinements of the underlying type have good bounds (except for opaque companions) + * - all type members have good bounds + * - all refinements of the underlying type have good bounds * - all base types are class types, and if their arguments are wildcards * they have good bounds. * - base types do not appear in multiple instances with different arguments. diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 9cef4148747e..8414c3795f49 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2134,11 +2134,16 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // resort to reflection to invoke the member. And Java reflection needs to know exact // erased parameter types. See neg/i12211.scala. Other reflection algorithms could // conceivably dispatch without knowing precise parameter signatures. One can signal - // this by inheriting from the `scala.reflect.SignatureCanBeImprecise` marker trait, + // this by inheriting from the `scala.Selectable.WithoutPreciseParameterTypes` marker trait, // in which case the signature test is elided. + // We also relax signature checking when checking bounds, + // for instance in tests/pos/i17222.izumi.min.scala + // the `go` method info as seen from `Foo` is `>: (in: Any): Unit <: (Nothing): Unit` + // So the parameter types conform but their signatures don't match. def sigsOK(symInfo: Type, info2: Type) = tp2.underlyingClassRef(refinementOK = true).member(name).exists || tp2.derivesFrom(defn.WithoutPreciseParameterTypesClass) + || ctx.mode.is(Mode.CheckBoundsOrSelfType) || symInfo.isInstanceOf[MethodType] && symInfo.signature.consistentParams(info2.signature) diff --git a/tests/pos/i17222.izumi.min.scala b/tests/pos/i17222.izumi.min.scala new file mode 100644 index 000000000000..06eadca73130 --- /dev/null +++ b/tests/pos/i17222.izumi.min.scala @@ -0,0 +1,7 @@ +class Foo: + type In + type Bar = { def go(in: In): Unit } + type False = false + +class Test: + def t1: Unit = valueOf[Foo#False] diff --git a/tests/pos/i17222.izumi.rep/InspectorBase.scala b/tests/pos/i17222.izumi.rep/InspectorBase.scala new file mode 100644 index 000000000000..56075831898d --- /dev/null +++ b/tests/pos/i17222.izumi.rep/InspectorBase.scala @@ -0,0 +1,59 @@ +package izumi.reflect.dottyreflection + +import scala.quoted.Quotes + +trait InspectorBase extends ReflectionUtil { + + val qctx: Quotes + import qctx.reflect._ + + protected def shift: Int + + // FIXME reimplement TrivialMacroLogger on Scala 3 + inline def debug: debug = valueOf[debug] + final type debug = false + + // println instead of report.info because report.info eats all the subsequent report.info's after first. + inline final protected def logStart(inline s: String): Unit = { + inline if (debug) println(" " * shift + currentPositionStr + s) + } + + inline final protected def log(inline s: String): Unit = { + inline if (debug) println(" " * shift + currentPositionStr + " -> " + s) + } + + inline final protected def logTpeAttrs[T](inline typeRepr: TypeRepr): Unit = { + inline if (debug) { + val tree = TypeTree.of(using typeRepr.asType) + val symbol = tree.symbol + System + .err.println( + currentPositionStr + ": " + + s"Attrs[${tree.show}]: type=${symbol.isType}, term=${symbol.isTerm}, packageDef=${symbol.isPackageDef}, classDef=${symbol.isClassDef}, typeDef=${symbol.isValDef}, defdef=${symbol.isDefDef}, bind=${symbol.isBind}, nosymbol=${symbol.isNoSymbol}" + ) + } + } + + private def currentPositionStr: String = { + val pos = qctx.reflect.Position.ofMacroExpansion + s"${pos.sourceFile.name}:${pos.endLine}" + } + +} + +object InspectorBase { + + private[reflect] inline def ifDebug[A](inline f: => Unit): Unit = { + inline if (valueOf[InspectorBase#debug]) { +//[error] ^^^^^^^^^^^^^ +//[error] izumi.reflect.dottyreflection.InspectorBase is not a legal path +//[error] since it has a member InternalTypeRefOrParamRef with possibly conflicting bounds Object{def underlying(ctx: Any): Nothing} <: ... <: Object{def underlying(ctx: Nothing): Matchable} + f + } + } + + private[reflect] inline def log(inline shift: Int, s: String): Unit = { + inline if (valueOf[InspectorBase#debug]) println(" " * shift + " -> " + s) + } + +} diff --git a/tests/pos/i17222.izumi.rep/ReflectionUtil.scala b/tests/pos/i17222.izumi.rep/ReflectionUtil.scala new file mode 100644 index 000000000000..c8ff667191af --- /dev/null +++ b/tests/pos/i17222.izumi.rep/ReflectionUtil.scala @@ -0,0 +1,331 @@ +package izumi.reflect.dottyreflection + +import scala.annotation.{tailrec, unused} +import scala.collection.immutable.Queue +import scala.quoted.Quotes + +private[dottyreflection] trait ReflectionUtil { this: InspectorBase => + + import qctx.reflect.* + + private final lazy val ignoredInIntersections0: Set[TypeRepr] = { + Set( + defn.AnyClass.typeRef, + defn.MatchableClass.typeRef, + defn.AnyRefClass.typeRef, + defn.ObjectClass.typeRef + ) + } + def ignoredInIntersections(repr: qctx.reflect.TypeRepr): Boolean = { + ignoredInIntersections0.exists(_ =:= repr) + } + def ignoredInUnions(repr: qctx.reflect.TypeRepr): Boolean = { + repr =:= defn.NothingClass.typeRef + } + + protected final def flattenAnd(tpe: TypeRepr): List[TypeRepr] = + tpe.dealias match { + case AndType(lhs, rhs) => flattenAnd(lhs) ++ flattenAnd(rhs) + case _ => List(tpe) + } + + protected final def flattenOr(tpe: TypeRepr): List[TypeRepr] = + tpe.dealias match { + case OrType(lhs, rhs) => flattenOr(lhs) ++ flattenOr(rhs) + case _ => List(tpe) + } + + protected final def intersectionUnionRefinementClassPartsOf(tpe: TypeRepr): List[TypeRepr] = { + tpe.dealias match { + case AndType(lhs, rhs) => + intersectionUnionRefinementClassPartsOf(lhs) ++ intersectionUnionRefinementClassPartsOf(rhs) + case OrType(lhs, rhs) => + intersectionUnionRefinementClassPartsOf(lhs) ++ intersectionUnionRefinementClassPartsOf(rhs) + case refinement: Refinement => + intersectionUnionRefinementClassPartsOf(refinement.parent) + case _ => + List(tpe) + } + } + + protected final def refinementInfoToParts(tpe0: TypeRepr): List[TypeRepr] = { + tpe0 match { + case ByNameType(tpe) => + refinementInfoToParts(tpe) + case MethodType(_, args, res) => + args.flatMap(refinementInfoToParts) ++ refinementInfoToParts(res) + case PolyType(_, tbounds, res) => + // FIXME we need to do FullDbInspector.inspectTypeReprToFullBases.lambdify/LightTypeTagImpl.makeLambdaOnlyBases.makeLambdaParents + // to wrap the unresolved type params in `res` into a lambda. + // As is, if type parameters are used in `res`, we'll add lots of trash types into db + tbounds.flatMap { case TypeBounds(lo, hi) => List(lo, hi) } ++ refinementInfoToParts(res) + case tpe => + List(tpe) + } + } + + protected final def flattenRefinements(ref: Refinement): (Queue[(Symbol, String, TypeRepr)], TypeRepr) = { + val refinementDecl = (ref.typeSymbol, ref.name, ref.info) + ref.parent match { + case innerRefinement: Refinement => + val (innerRefs, nonRefinementParent) = flattenRefinements(innerRefinement) + (innerRefs :+ refinementDecl, nonRefinementParent) + case nonRefinementParent => + (Queue(refinementDecl), nonRefinementParent) + } + } + + protected final def allPartsStrong(outerOwnerClassDefs: Set[Symbol], typeRepr: TypeRepr): Boolean = { + ReflectionUtil.allPartsStrong(using qctx)(shift, outerOwnerClassDefs, Set.empty, typeRepr) + } + + protected final def getClassDefOwners(symbol: Symbol): Set[Symbol] = { + ReflectionUtil.getClassDefOwners(using qctx)(symbol) + } + + import ReflectionUtil.reflectiveUncheckedNonOverloadedSelectable + import InternalContext.InternalContext + + extension (typeRef: TypeRef | ParamRef) { + protected final def _underlying: TypeRepr = { + // This works as a substitution for `TypeRef#underlying` call, + // but I'm not sure if it's a reliable substitution. + +// typeRef.typeSymbol.owner._typeRef.memberType(typeRef.typeSymbol) + + // No, It's not a reliable substitution. When used on a TypeParamRef it returns Any instead of the underlying TypeBounds + // https://github.com/lampepfl/dotty/issues/15799 + +// val underlying = typeRef +// .getClass.getMethods.collect { case m if m.getName == "underlying" => m }.head.invoke( +// typeRef, +// qctx.getClass.getMethods.collect { case m if m.getName == "ctx" => m }.head.invoke(qctx) +// ) +// underlying.asInstanceOf[TypeRepr] + + typeRef.asInstanceOf[InternalTypeRefOrParamRef].underlying(qctx._ctx) + } + } + + extension (typeRepr: TypeRepr) { + protected final def _paramVariancesIfHKTypeLambda: Option[List[Flags]] = { + try { + val params = typeRepr.asInstanceOf[InternalHKTypeLambda].typeParams + val flags = params.map(_.paramVariance(qctx._ctx)) + Some(flags) + } catch { + case _: NoSuchMethodException => None + } + } + + @tailrec + protected final def _dealiasSimplifiedFull: TypeRepr = { +// val res = typeRepr.dealias.simplified + // simplified does everything below functions do, with exception of `_removeTautologicalUnions` for some reason + // All of these would be more useful, if not for forced type simplification on implicit macro - https://github.com/lampepfl/dotty/issues/17544 + val res = typeRepr.dealias._removeTautologicalIntersections._removeTautologicalUnions._simplifyMatchCase + if (res.asInstanceOf[AnyRef] eq typeRepr.asInstanceOf[AnyRef]) { + res + } else { + res._dealiasSimplifiedFull + } + } + + // Calling .simplified will remove too many intersections - we only want to remove those with Any/AnyRef/Object/Matchable + @tailrec private def _removeTautologicalIntersections: TypeRepr = { + typeRepr match { + case AndType(a, b) => + if (ignoredInIntersections(a)) { + b._removeTautologicalIntersections + } else if (ignoredInIntersections(b)) { + a._removeTautologicalIntersections + } else { + removeTautologicalIntersectionsNonTailRec(a, b) + } + case _ => + typeRepr + } + } + + private def removeTautologicalIntersectionsNonTailRec(a: TypeRepr, b: TypeRepr): TypeRepr = { + val a0 = a._removeTautologicalIntersections + val b0 = b._removeTautologicalIntersections + if ((a.asInstanceOf[AnyRef] ne a0.asInstanceOf[AnyRef]) || (b.asInstanceOf[AnyRef] ne b0.asInstanceOf[AnyRef])) { + AndType(a0, b0) + } else { + typeRepr + } + } + + @tailrec private def _removeTautologicalUnions: TypeRepr = { + typeRepr match { + case OrType(a, b) => + if (ignoredInUnions(a)) { + b._removeTautologicalUnions + } else if (ignoredInUnions(b)) { + a._removeTautologicalUnions + } else { + removeTautologicaUnionsNonTailRec(a, b) + } + case _ => + typeRepr + } + } + + private def removeTautologicaUnionsNonTailRec(a: TypeRepr, b: TypeRepr): TypeRepr = { + val superA = ignoredInIntersections(a) + val superB = ignoredInIntersections(b) + if (superA && superB) { + (if (a <:< b) b else a)._removeTautologicalUnions + } else if (superA) { + a + } else if (superB) { + b + } else { + val a0 = a._removeTautologicalUnions + val b0 = b._removeTautologicalUnions + if ((a.asInstanceOf[AnyRef] ne a0.asInstanceOf[AnyRef]) || (b.asInstanceOf[AnyRef] ne b0.asInstanceOf[AnyRef])) { + AndType(a0, b0) + } else { + typeRepr + } + } + } + + inline private def _simplifyMatchCase: TypeRepr = { + typeRepr match { + case _: MatchCase | _: MatchType => + // no other way to evaluate a match type other than calling simplified, + // even though that'll also cause a collapse of tautological intersections + // other than with Any/AnyRef/Object/Matchable + typeRepr.simplified + case _ => + typeRepr + } + } + + } + + extension (qctx: Quotes) { + final def _ctx: InternalContext = qctx.asInstanceOf[{ def ctx: InternalContext }].ctx + } + + type InternalTypeRefOrParamRef = { + def underlying(ctx: InternalContext): TypeRepr + } + + type InternalHKTypeLambda = { + val typeParams: List[InternalLambdaParam] + } + + type InternalLambdaParam = { + def paramVariance(ctx: InternalContext): Flags + } + + object InternalContext { + opaque type InternalContext = Any + } + +} + +private[reflect] object ReflectionUtil { + + private[reflect] inline implicit def reflectiveUncheckedNonOverloadedSelectable(x: Any): UncheckedNonOverloadedSelectable = new UncheckedNonOverloadedSelectable(x) + + /** + * Returns true if the given type contains no type parameters + * (this means the type is not "weak" https://stackoverflow.com/questions/29435985/weaktypetag-v-typetag) + */ + private[reflect] def allPartsStrong( + using qctx: Quotes + )(shift: Int, + outerOwnerClassDefs: Set[qctx.reflect.Symbol], + outerLambdas: Set[qctx.reflect.TypeRepr], + typeRepr: qctx.reflect.TypeRepr + ): Boolean = { + import qctx.reflect.* + typeRepr.dealias match { + case x if topLevelWeakType(outerOwnerClassDefs, outerLambdas, x) => false + case AppliedType(tpe, args) => + allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) && args.forall(allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, _)) + case AndType(lhs, rhs) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, lhs) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, rhs) + case OrType(lhs, rhs) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, lhs) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, rhs) + case TypeRef(tpe, _) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) + case TermRef(tpe, _) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) + case ThisType(tpe) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) + case NoPrefix() => true + case TypeBounds(lo, hi) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, lo) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, hi) + case lam @ TypeLambda(_, _, body) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas + lam, body) + case Refinement(parent, _, tpe) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, parent) + case ByNameType(tpe) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) + case strange => + InspectorBase.log(shift, s"Got unknown type component when checking strength: $strange") + true + } + } + + private[reflect] def topLevelWeakType( + using qctx: Quotes + )(outerOwnerClassDefs: Set[qctx.reflect.Symbol], + outerLambdas: Set[qctx.reflect.TypeRepr], + typeRepr: qctx.reflect.TypeRepr + ): Boolean = { + import qctx.reflect.* + typeRepr match { + case x if x.typeSymbol.isTypeParam => + x match { + case t: ParamRef if outerLambdas.contains(t.binder) => false + case _ => true + } + // we regard abstract types like T in trait X { type T; Tag[this.T] } - when we are _inside_ the definition template + // as 'type parameters' too. So that you could define `implicit def tagForT: Tag[this.T]` and the tag would be resolved + // to this implicit correctly, instead of generating a useless `X::this.type::T` tag. + // TODO: Due to https://github.com/lampepfl/dotty/issues/16107 not being fixed we have to make sure we're actually + // inside the definition of the this-type prefix to count it as 'weak' - unlike Scala 2 we're not protected + // from this-types leaking in and have to carry the owner chain here - until that issue is fixed. + case x @ TypeRef(ThisType(prefix), _) if x.typeSymbol.isAbstractType && !x.typeSymbol.isClassDef && outerOwnerClassDefs.contains(prefix.typeSymbol) => + true + case _ => false + } + } + + private[reflect] def getClassDefOwners(using qctx: Quotes)(symbol: qctx.reflect.Symbol): Set[qctx.reflect.Symbol] = { + Iterator + .iterate(symbol) { + s => + val owner = s.owner + if (owner == null || owner.isNoSymbol || owner == qctx.reflect.defn.RootClass) { + null.asInstanceOf[qctx.reflect.Symbol] + } else { + owner + } + } + .takeWhile(_ ne null) + .filter(s => s.isClassDef && !s.isAbstractType) + .toSet + } + + private[reflect] final class UncheckedNonOverloadedSelectable(private val selectable: Any) extends AnyVal with Selectable { + + inline def selectDynamic(name: String): Any = { + applyDynamic(name)() + } + + def applyDynamic(name: String, @unused paramTypes: Class[_]*)(args: Any*): Any = { + val cls = selectable.getClass + val method = { + if (args.isEmpty) { + cls.getMethod(name) + } else { + cls.getMethods.collectFirst { case m if m.getName == name => m } match { + case Some(m) => m + case None => throw new NoSuchMethodException(s"No method named `$name` found in class `$cls`") + } + } + } + method.invoke(selectable, args*) + } + + } + +} From 30ecf0d52fd531b707f112641938e7892c7f49b5 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Tue, 19 Nov 2024 14:36:31 +0100 Subject: [PATCH 009/202] Only retry tests on CI --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index d742ab9ad3f4..148810d4af99 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -327,7 +327,7 @@ object Build { .withTestRetryConfiguration( config.testRetryConfiguration .withFlakyTestPolicy(FlakyTestPolicy.Fail) - .withMaxRetries(1) + .withMaxRetries(if (isInsideCI) 1 else 0) .withMaxFailures(10) .withClassesFilter((className, _) => !noRetryTestClasses.contains(className)) ) From bdbb0ba8b08c3e68cbc9139acc5226bbbdc354b3 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 1 Nov 2024 13:22:47 +0100 Subject: [PATCH 010/202] Handle CapsOf in more places --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 2 +- .../src/dotty/tools/dotc/cc/CaptureRef.scala | 2 ++ .../dotty/tools/dotc/cc/CheckCaptures.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 4 ++- .../captures/capture-poly.scala | 25 +++++++++++++++++++ 5 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 tests/neg-custom-args/captures/capture-poly.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index aad6ca8ddeac..a9272b73e605 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -198,7 +198,7 @@ extension (tp: Type) || tp.isRootCapability ) && !tp.symbol.isOneOf(UnstableValueFlags) case tp: TypeRef => - tp.symbol.isAbstractOrParamType && tp.derivesFrom(defn.Caps_CapSet) + tp.symbol.isType && tp.derivesFrom(defn.Caps_CapSet) case tp: TypeParamRef => tp.derivesFrom(defn.Caps_CapSet) case AnnotatedType(parent, annot) => diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala index 590beda42903..199114880c2b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -140,6 +140,8 @@ trait CaptureRef extends TypeProxy, ValueType: case ReachCapability(x1) => x1.subsumes(y.stripReach) case x: TermRef => viaInfo(x.info)(subsumingRefs(_, y)) case x: TermParamRef => subsumesExistentially(x, y) + case x: TypeRef if x.symbol.info.derivesFrom(defn.Caps_CapSet) => + x.captureSetOfInfo.elems.exists(_.subsumes(y)) case x: TypeRef => assumedContainsOf(x).contains(y) case _ => false end subsumes diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 77d893ad49b9..2a3cd50d0e0c 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -135,7 +135,7 @@ object CheckCaptures: elem match case CapsOfApply(arg) => def isLegalCapsOfArg = - arg.symbol.isAbstractOrParamType && arg.symbol.info.derivesFrom(defn.Caps_CapSet) + arg.symbol.isType && arg.symbol.info.derivesFrom(defn.Caps_CapSet) if !isLegalCapsOfArg then report.error( em"""$arg is not a legal prefix for `^` here, diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 31e11487ae38..821363c74a25 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4007,7 +4007,7 @@ object Types extends TypeUtils { (compute(status, parent, theAcc) /: refs.elems) { (s, ref) => ref.stripReach match case tp: TermParamRef if tp.binder eq thisLambdaType => combine(s, CaptureDeps) - case _ => s + case tp => combine(s, compute(status, tp, theAcc)) } case _ => if tp.annot.refersToParamOf(thisLambdaType) then TrueDeps @@ -6078,6 +6078,8 @@ object Types extends TypeUtils { case tp: CaptureRef => if tp.isTrackableRef then tp else ensureTrackable(tp.underlying) + case tp: TypeAlias => + ensureTrackable(tp.alias) case _ => assert(false, i"not a trackable captureRef ref: $result, ${result.underlyingIterator.toList}") ensureTrackable(result) diff --git a/tests/neg-custom-args/captures/capture-poly.scala b/tests/neg-custom-args/captures/capture-poly.scala new file mode 100644 index 000000000000..a3a7a4c2a3d7 --- /dev/null +++ b/tests/neg-custom-args/captures/capture-poly.scala @@ -0,0 +1,25 @@ +import caps.* + +trait Foo extends Capability + +trait CaptureSet: + type C <: CapSet^ + +def capturePoly[C^](a: Foo^{C^}): Foo^{C^} = a +def capturePoly2(c: CaptureSet)(a: Foo^{c.C^}): Foo^{c.C^} = a + +def test = + val x: Foo^ = ??? + val y: Foo^ = ??? + + object X extends CaptureSet: + type C = CapSet^{x} + + val z1: Foo^{X.C^} = x + val z2: Foo^{X.C^} = y // error + + val z3: Foo^{x} = capturePoly(x) + val z4: Foo^{x} = capturePoly(y) // error + + val z5: Foo^{x} = capturePoly2(X)(x) + val z6: Foo^{x} = capturePoly2(X)(y) // error \ No newline at end of file From 13e443954a871da85f043b5c9649a31a892a1ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Mon, 18 Nov 2024 17:49:03 +0100 Subject: [PATCH 011/202] Report error in Setup for illegal capture refs --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 3147a0f7bd47..cc456567ce8a 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -18,6 +18,7 @@ import reporting.Message import printing.{Printer, Texts}, Texts.{Text, Str} import collection.mutable import CCState.* +import dotty.tools.dotc.util.NoSourcePosition /** Operations accessed from CheckCaptures */ trait SetupAPI: @@ -323,7 +324,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val parent2 = stripImpliedCaptureSet(parent1) for tpt <- tptToCheck do checkWellformedLater(parent2, ann.tree, tpt) - CapturingType(parent2, ann.tree.toCaptureSet) + try + CapturingType(parent2, ann.tree.toCaptureSet) + catch case ex: IllegalCaptureRef => + report.error(em"Illegal capture reference: ${ex.getMessage.nn}", tptToCheck.fold(NoSourcePosition)(_.srcPos)) + t else t.derivedAnnotatedType(parent1, ann) case throwsAlias(res, exc) => From 20917c7a95f004ac5caad5097d5d06eb5120da77 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 19 Nov 2024 18:04:51 +0100 Subject: [PATCH 012/202] Add some tests --- .../captures/capset-bound.scala | 18 ++++++++++++++++++ tests/neg-custom-args/captures/i21868.scala | 14 ++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/neg-custom-args/captures/capset-bound.scala create mode 100644 tests/neg-custom-args/captures/i21868.scala diff --git a/tests/neg-custom-args/captures/capset-bound.scala b/tests/neg-custom-args/captures/capset-bound.scala new file mode 100644 index 000000000000..c00f61240dea --- /dev/null +++ b/tests/neg-custom-args/captures/capset-bound.scala @@ -0,0 +1,18 @@ +import caps.* + +class IO + +case class File(io: IO^) + +def test(io1: IO^, io2: IO^) = + def f[C >: CapSet^{io1} <: CapSet^](file: File^{C^}) = ??? + val f1: File^{io1} = ??? + val f2: File^{io2} = ??? + val f3: File^{io1, io2} = ??? + f[CapSet^{io1}](f1) + f[CapSet^{io1}](f2) // error + f[CapSet^{io1}](f3) // error + f[CapSet^{io2}](f2) // error + f[CapSet^{io1, io2}](f1) + f[CapSet^{io1, io2}](f2) + f[CapSet^{io1, io2}](f3) \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i21868.scala b/tests/neg-custom-args/captures/i21868.scala new file mode 100644 index 000000000000..349750a2ee4b --- /dev/null +++ b/tests/neg-custom-args/captures/i21868.scala @@ -0,0 +1,14 @@ +import caps. + +trait AbstractWrong: + type C <: CapSet + def boom(): Unit^{C^} // error + +trait Abstract: + type C <: CapSet^ + def boom(): Unit^{C^} + +class Concrete extends Abstract: + type C = Nothing + def boom() = () // error + From 6b9266e903bb3bfd12e61d2a117c3ad4d347a327 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 19 Nov 2024 22:50:33 +0100 Subject: [PATCH 013/202] Set developedVersion to 3.6.4 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 148810d4af99..f5593144a631 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -106,7 +106,7 @@ object Build { * Should only be referred from `dottyVersion` or settings/tasks requiring simplified version string, * eg. `compatMode` or Windows native distribution version. */ - val developedVersion = "3.6.3" + val developedVersion = "3.6.4" /** The version of the compiler including the RC prefix. * Defined as common base before calculating environment specific suffixes in `dottyVersion` From 31c9fe192611a1e4664ca6c8b41cbd04e7857cf7 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 19 Nov 2024 22:51:28 +0100 Subject: [PATCH 014/202] Set referenceVersion to 3.6.2-RC1 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index f5593144a631..b4673405bec9 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -97,7 +97,7 @@ object Build { * - In release branch it should be the last stable release * 3.6.0-RC1 was released as 3.6.0 - it's having and experimental TASTy version */ - val referenceVersion = "3.6.0" + val referenceVersion = "3.6.2-RC1" /** Version of the Scala compiler targeted in the current release cycle * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes From 2bf5df7e1a0bf3c0d8b15bb33ddb1df8f05cb24c Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 20 Nov 2024 12:55:37 +0100 Subject: [PATCH 015/202] Revert "Set referenceVersion to 3.6.2-RC1" This reverts commit 8a0824550b5c39ca2cf8c06ef518f3f12e81ec97. --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index b4673405bec9..f5593144a631 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -97,7 +97,7 @@ object Build { * - In release branch it should be the last stable release * 3.6.0-RC1 was released as 3.6.0 - it's having and experimental TASTy version */ - val referenceVersion = "3.6.2-RC1" + val referenceVersion = "3.6.0" /** Version of the Scala compiler targeted in the current release cycle * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes From cf66d1840e1fcbc9a59e627ac970d866df29036e Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 20 Nov 2024 13:20:22 +0100 Subject: [PATCH 016/202] Fix the test --- tests/neg-custom-args/captures/i21868.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg-custom-args/captures/i21868.scala b/tests/neg-custom-args/captures/i21868.scala index 349750a2ee4b..929e770a21c6 100644 --- a/tests/neg-custom-args/captures/i21868.scala +++ b/tests/neg-custom-args/captures/i21868.scala @@ -1,4 +1,4 @@ -import caps. +import caps.* trait AbstractWrong: type C <: CapSet From 2be2a60e88fdf4d2ef579a3e624b0e3e04ef77b8 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 21 Nov 2024 13:08:55 +0100 Subject: [PATCH 017/202] Cancel stale CI executions when CI is re-triggered in the same branch/PR (#21974) Cancels any in-progress runs within the same group identified by workflow name and GH reference (branch or tag) For example it would: - terminate previous PR CI execution after pushing more changes to the same PR branch Co-authored-by: Tomasz Godzik --- .github/workflows/ci.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 303922719b5b..a2006e16c7e8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,6 +26,14 @@ on: - cron: '0 3 * * *' # Every day at 3 AM workflow_dispatch: +# Cancels any in-progress runs within the same group identified by workflow name and GH reference (branch or tag) +# For example it would: +# - terminate previous PR CI execution after pushing more changes to the same PR branch +# - terminate previous on-push CI run after merging new PR to main +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + env: DOTTY_CI_RUN: true DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} From a163f0c9257e0165f723b016f20e0b30c7b0a342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Thu, 21 Nov 2024 15:17:45 +0100 Subject: [PATCH 018/202] Report illegal capture reference only once If we do not strip the capture set at this point, then the type will be transformed twice and we'll get the same error message twice. --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index cc456567ce8a..7cd12b999618 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -328,7 +328,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: CapturingType(parent2, ann.tree.toCaptureSet) catch case ex: IllegalCaptureRef => report.error(em"Illegal capture reference: ${ex.getMessage.nn}", tptToCheck.fold(NoSourcePosition)(_.srcPos)) - t + parent2 else t.derivedAnnotatedType(parent1, ann) case throwsAlias(res, exc) => From 63dc305126bf57af6328afeb5f43e596decb6fcb Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Thu, 21 Nov 2024 21:10:31 +0100 Subject: [PATCH 019/202] Consider all arguments in Annotations.refersToParamOf --- compiler/src/dotty/tools/dotc/core/Annotations.scala | 2 +- tests/pos/dependent-annot-type-param.scala | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/pos/dependent-annot-type-param.scala diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index d6a99b12e3b3..d7f50d4638ab 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -73,7 +73,7 @@ object Annotations { /** Does this annotation refer to a parameter of `tl`? */ def refersToParamOf(tl: TermLambda)(using Context): Boolean = - val args = arguments + val args = tpd.allArguments(tree) if args.isEmpty then false else tree.existsSubTree: case id: (Ident | This) => id.tpe.stripped match diff --git a/tests/pos/dependent-annot-type-param.scala b/tests/pos/dependent-annot-type-param.scala new file mode 100644 index 000000000000..174e9f78fba6 --- /dev/null +++ b/tests/pos/dependent-annot-type-param.scala @@ -0,0 +1,5 @@ +class annot[T] extends annotation.Annotation +class Box[T]() +def f(x: Int): Int @annot[Box[x.type]] = x +def test = + val foo = f(42) From cb08b469025d96cbc8b454d588f14c9dd91448d5 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Thu, 21 Nov 2024 21:11:16 +0100 Subject: [PATCH 020/202] Make sure symbols in annotation trees are fresh before pickling --- .../tools/dotc/transform/PostTyper.scala | 34 ++++++++++++++----- tests/pos/annot-17939.scala | 8 +++++ tests/pos/annot-19846.scala | 9 +++++ tests/pos/annot-19846b.scala | 8 +++++ tests/pos/annot-i20272a.scala | 20 +++++++++++ 5 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 tests/pos/annot-17939.scala create mode 100644 tests/pos/annot-19846.scala create mode 100644 tests/pos/annot-19846b.scala create mode 100644 tests/pos/annot-i20272a.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 0feee53ca50f..146871ade3fe 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -2,7 +2,7 @@ package dotty.tools package dotc package transform -import dotty.tools.dotc.ast.{Trees, tpd, untpd, desugar} +import dotty.tools.dotc.ast.{Trees, tpd, untpd, desugar, TreeTypeMap} import scala.collection.mutable import core.* import dotty.tools.dotc.typer.Checking @@ -16,7 +16,7 @@ import Symbols.*, NameOps.* import ContextFunctionResults.annotateContextResults import config.Printers.typr import config.Feature -import util.SrcPos +import util.{SrcPos, Stats} import reporting.* import NameKinds.WildcardParamName import cc.* @@ -154,17 +154,39 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => case _ => case _ => + /** Returns a copy of the given tree with all symbols fresh. + * + * Used to guarantee that no symbols are shared between trees in different + * annotations. + */ + private def copySymbols(tree: Tree)(using Context) = + Stats.trackTime("Annotations copySymbols"): + val ttm = + new TreeTypeMap: + override def withMappedSyms(syms: List[Symbol]) = + withMappedSyms(syms, mapSymbols(syms, this, true)) + ttm(tree) + + /** Transforms the given annotation tree. */ private def transformAnnot(annot: Tree)(using Context): Tree = { val saved = inJavaAnnot inJavaAnnot = annot.symbol.is(JavaDefined) if (inJavaAnnot) checkValidJavaAnnotation(annot) - try transform(annot) + try transform(copySymbols(annot)) finally inJavaAnnot = saved } private def transformAnnot(annot: Annotation)(using Context): Annotation = annot.derivedAnnotation(transformAnnot(annot.tree)) + /** Transforms all annotations in the given type. */ + private def transformAnnots(using Context) = + new TypeMap: + def apply(tp: Type) = tp match + case tp @ AnnotatedType(parent, annot) => + tp.derivedAnnotatedType(mapOver(parent), transformAnnot(annot)) + case _ => mapOver(tp) + private def processMemberDef(tree: Tree)(using Context): tree.type = { val sym = tree.symbol Checking.checkValidOperator(sym) @@ -524,11 +546,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => super.transform(tree) case tree: TypeTree => val tpe = if tree.isInferred then CleanupRetains()(tree.tpe) else tree.tpe - tree.withType: - tpe match - case AnnotatedType(parent, annot) => - AnnotatedType(parent, transformAnnot(annot)) // TODO: Also map annotations embedded in type? - case _ => tpe + tree.withType(transformAnnots(tpe)) case Typed(Ident(nme.WILDCARD), _) => withMode(Mode.Pattern)(super.transform(tree)) // The added mode signals that bounds in a pattern need not diff --git a/tests/pos/annot-17939.scala b/tests/pos/annot-17939.scala new file mode 100644 index 000000000000..604143183af2 --- /dev/null +++ b/tests/pos/annot-17939.scala @@ -0,0 +1,8 @@ +import scala.annotation.Annotation +class myRefined[T](f: T => Boolean) extends Annotation + +class Box[T](val x: T) +class Box2(val x: Int) + +class A(a: String @myRefined((x: Int) => Box(3).x == 3)) // crash +class A2(a2: String @myRefined((x: Int) => Box2(3).x == 3)) // works diff --git a/tests/pos/annot-19846.scala b/tests/pos/annot-19846.scala new file mode 100644 index 000000000000..ff2f8f632eab --- /dev/null +++ b/tests/pos/annot-19846.scala @@ -0,0 +1,9 @@ +package dependentAnnotation + +class lambdaAnnot(g: () => Int) extends annotation.StaticAnnotation + +def f(x: Int): Int @lambdaAnnot(() => x + 1) = x + +@main def main = + val y: Int = 5 + val z = f(y) diff --git a/tests/pos/annot-19846b.scala b/tests/pos/annot-19846b.scala new file mode 100644 index 000000000000..09c24a5cf3cf --- /dev/null +++ b/tests/pos/annot-19846b.scala @@ -0,0 +1,8 @@ +class qualified[T](predicate: T => Boolean) extends annotation.StaticAnnotation + +class EqualPair(val x: Int, val y: Int @qualified[Int](it => it == x)) + +@main def main = + val p = EqualPair(42, 42) + val y = p.y + println(42) diff --git a/tests/pos/annot-i20272a.scala b/tests/pos/annot-i20272a.scala new file mode 100644 index 000000000000..e04ee1efcd99 --- /dev/null +++ b/tests/pos/annot-i20272a.scala @@ -0,0 +1,20 @@ +import language.experimental.captureChecking + + trait Iterable[T] { self: Iterable[T]^ => + def map[U](f: T => U): Iterable[U]^{this, f} + } + + object Test { + def assertEquals[A, B](a: A, b: B): Boolean = ??? + + def foo[T](level: Int, lines: Iterable[T]) = + lines.map(x => x) + + def bar(messages: Iterable[String]) = + foo(1, messages) + + val it: Iterable[String] = ??? + val msgs = bar(it) + + assertEquals(msgs, msgs) + } From ca3c7975ac68b0aba1893d82aadd02809bbc5ced Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Fri, 22 Nov 2024 02:34:57 +0100 Subject: [PATCH 021/202] Do not copy symbols in BodyAnnotations --- .../dotty/tools/dotc/transform/PostTyper.scala | 16 ++++++++++------ tests/pos/annot-body.scala | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 tests/pos/annot-body.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 146871ade3fe..898517806e50 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -168,19 +168,23 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => ttm(tree) /** Transforms the given annotation tree. */ - private def transformAnnot(annot: Tree)(using Context): Tree = { + private def transformAnnotTree(annot: Tree)(using Context): Tree = { val saved = inJavaAnnot inJavaAnnot = annot.symbol.is(JavaDefined) if (inJavaAnnot) checkValidJavaAnnotation(annot) - try transform(copySymbols(annot)) + try transform(annot) finally inJavaAnnot = saved } private def transformAnnot(annot: Annotation)(using Context): Annotation = - annot.derivedAnnotation(transformAnnot(annot.tree)) + val tree1 = + annot match + case _: BodyAnnotation => annot.tree + case _ => copySymbols(annot.tree) + annot.derivedAnnotation(transformAnnotTree(tree1)) /** Transforms all annotations in the given type. */ - private def transformAnnots(using Context) = + private def transformAnnotsIn(using Context) = new TypeMap: def apply(tp: Type) = tp match case tp @ AnnotatedType(parent, annot) => @@ -523,7 +527,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => Checking.checkRealizable(tree.tpt.tpe, tree.srcPos, "SAM type") super.transform(tree) case tree @ Annotated(annotated, annot) => - cpy.Annotated(tree)(transform(annotated), transformAnnot(annot)) + cpy.Annotated(tree)(transform(annotated), transformAnnotTree(annot)) case tree: AppliedTypeTree => if (tree.tpt.symbol == defn.andType) Checking.checkNonCyclicInherited(tree.tpe, tree.args.tpes, EmptyScope, tree.srcPos) @@ -546,7 +550,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => super.transform(tree) case tree: TypeTree => val tpe = if tree.isInferred then CleanupRetains()(tree.tpe) else tree.tpe - tree.withType(transformAnnots(tpe)) + tree.withType(transformAnnotsIn(tpe)) case Typed(Ident(nme.WILDCARD), _) => withMode(Mode.Pattern)(super.transform(tree)) // The added mode signals that bounds in a pattern need not diff --git a/tests/pos/annot-body.scala b/tests/pos/annot-body.scala new file mode 100644 index 000000000000..d8f6f79ae674 --- /dev/null +++ b/tests/pos/annot-body.scala @@ -0,0 +1,15 @@ +// This test checks that symbols in `BodyAnnotation` are not copied in +// `transformAnnot` during `PostTyper`. + +package json + +trait Reads[A] { + def reads(a: Any): A +} + +object JsMacroImpl { + inline def reads[A]: Reads[A] = + new Reads[A] { self => + def reads(a: Any) = ??? + } +} From 8a5a93af0391ceab775a16bd18f6a46d1c5bf88c Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Fri, 22 Nov 2024 13:51:41 +0100 Subject: [PATCH 022/202] Rethrow SuspendExceptions in CodeGen phase --- compiler/src/dotty/tools/backend/jvm/CodeGen.scala | 1 + tests/pos-macros/i21983/Test.scala | 13 +++++++++++++ tests/pos-macros/i21983/UsesTest.scala | 3 +++ tests/pos-macros/i21983/VisitorMacros.scala | 13 +++++++++++++ 4 files changed, 30 insertions(+) create mode 100644 tests/pos-macros/i21983/Test.scala create mode 100644 tests/pos-macros/i21983/UsesTest.scala create mode 100644 tests/pos-macros/i21983/VisitorMacros.scala diff --git a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala index c5b0ec0929b8..d4843cd56639 100644 --- a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala +++ b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala @@ -84,6 +84,7 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( registerGeneratedClass(mirrorClassNode, isArtifact = true) catch 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) diff --git a/tests/pos-macros/i21983/Test.scala b/tests/pos-macros/i21983/Test.scala new file mode 100644 index 000000000000..bf008583c7d9 --- /dev/null +++ b/tests/pos-macros/i21983/Test.scala @@ -0,0 +1,13 @@ +package example + +sealed trait Test + +object Test { + case object Foo extends Test + + val visitorType = mkVisitorType[Test] + + trait Visitor[A] { + type V[a] = visitorType.Out[a] + } +} diff --git a/tests/pos-macros/i21983/UsesTest.scala b/tests/pos-macros/i21983/UsesTest.scala new file mode 100644 index 000000000000..803e93c328c9 --- /dev/null +++ b/tests/pos-macros/i21983/UsesTest.scala @@ -0,0 +1,3 @@ +package example + +val _ = Test.Foo diff --git a/tests/pos-macros/i21983/VisitorMacros.scala b/tests/pos-macros/i21983/VisitorMacros.scala new file mode 100644 index 000000000000..5a5cc453c525 --- /dev/null +++ b/tests/pos-macros/i21983/VisitorMacros.scala @@ -0,0 +1,13 @@ +package example + +import scala.deriving.Mirror +import scala.quoted.* + +private def mkVisitorTypeImpl[T: Type](using q: Quotes): Expr[VisitorType[T]] = + '{new VisitorType[T]{}} + +transparent inline def mkVisitorType[T]: VisitorType[T] = ${ mkVisitorTypeImpl[T] } + +trait VisitorType[T] { + type Out[A] +} From f5c9eab427b4373e68c76eee52bcc13e1be811d5 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 5 Oct 2024 17:52:19 +0200 Subject: [PATCH 023/202] Recognize double annotated capabilities such as x*? x*? is x.type @reach @maybe. This was not recognized before. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 4 ++-- tests/neg-custom-args/captures/i21646.scala | 13 ++++++++++++ tests/neg-custom-args/captures/uses.scala | 20 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 tests/neg-custom-args/captures/i21646.scala create mode 100644 tests/neg-custom-args/captures/uses.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index a9272b73e605..56f479debf17 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -639,8 +639,8 @@ object CapsOfApply: class AnnotatedCapability(annot: Context ?=> ClassSymbol): def apply(tp: Type)(using Context) = AnnotatedType(tp, Annotation(annot, util.Spans.NoSpan)) - def unapply(tree: AnnotatedType)(using Context): Option[SingletonCaptureRef] = tree match - case AnnotatedType(parent: SingletonCaptureRef, ann) if ann.symbol == annot => Some(parent) + def unapply(tree: AnnotatedType)(using Context): Option[CaptureRef] = tree match + case AnnotatedType(parent: CaptureRef, ann) if ann.symbol == annot => Some(parent) case _ => None /** An extractor for `ref @annotation.internal.reachCapability`, which is used to express diff --git a/tests/neg-custom-args/captures/i21646.scala b/tests/neg-custom-args/captures/i21646.scala new file mode 100644 index 000000000000..42c493a9ea80 --- /dev/null +++ b/tests/neg-custom-args/captures/i21646.scala @@ -0,0 +1,13 @@ +import language.experimental.captureChecking +import caps.Capability + +trait File extends Capability + +class Resource[T <: Capability](gen: T): + def use[U](f: T => U): U = + f(gen) // error + +@main def run = + val myFile: File = ??? + val r = Resource(myFile) // error + () diff --git a/tests/neg-custom-args/captures/uses.scala b/tests/neg-custom-args/captures/uses.scala new file mode 100644 index 000000000000..b872c7b03ec7 --- /dev/null +++ b/tests/neg-custom-args/captures/uses.scala @@ -0,0 +1,20 @@ +class C +def test(x: C^, y: C^) = + class D { + println(x) + def foo() = println(y) + } + val d = D() + val _: D^{y} = d // error, should be ok + val _: D = d // error + + val f = () => println(D()) + val _: () ->{x} Unit = f // ok + val _: () -> Unit = f // should be error + + def g = () => + println(x) + () => println(y) + val _: () ->{x} () ->{y} Unit = g // error, should be ok + val _: () -> () -> Unit = g // error + From bbafb09fbe30b9ec66d1c078e00cff0b6ed58454 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 11 Oct 2024 14:13:33 +0200 Subject: [PATCH 024/202] Refactor narrowCaps --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 56f479debf17..12d68bbb5c34 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -468,29 +468,23 @@ extension (tp: Type) end CheckContraCaps object narrowCaps extends TypeMap: - /** Has the variance been flipped at this point? */ - private var isFlipped: Boolean = false - def apply(t: Type) = - val saved = isFlipped - try - if variance <= 0 then isFlipped = true - t.dealias match - case t1 @ CapturingType(p, cs) if cs.isUniversal && !isFlipped => - t1.derivedCapturingType(apply(p), ref.reach.singletonCaptureSet) - case t1 @ FunctionOrMethod(args, res @ Existential(_, _)) - if args.forall(_.isAlwaysPure) => - // Also map existentials in results to reach capabilities if all - // preceding arguments are known to be always pure - apply(t1.derivedFunctionOrMethod(args, Existential.toCap(res))) - case Existential(_, _) => - t - case _ => t match - case t @ CapturingType(p, cs) => - t.derivedCapturingType(apply(p), cs) // don't map capture set variables - case t => - mapOver(t) - finally isFlipped = saved + if variance <= 0 then t + else t.dealiasKeepAnnots match + case t @ CapturingType(p, cs) if cs.isUniversal => + t.derivedCapturingType(apply(p), ref.reach.singletonCaptureSet) + case t @ AnnotatedType(parent, ann) => + // Don't map annotations, which includes capture sets + t.derivedAnnotatedType(this(parent), ann) + case t @ FunctionOrMethod(args, res @ Existential(_, _)) + if args.forall(_.isAlwaysPure) => + // Also map existentials in results to reach capabilities if all + // preceding arguments are known to be always pure + apply(t.derivedFunctionOrMethod(args, Existential.toCap(res))) + case Existential(_, _) => + t + case _ => + mapOver(t) end narrowCaps ref match From d33c89a9df9c6ed3269a58222afdedfa82f1d39a Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 11 Oct 2024 15:25:15 +0200 Subject: [PATCH 025/202] Make sure dcs includes cs Previously, we violated that assumption is we too the deep capture set of a capture reference wiht singleton type. --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 12d68bbb5c34..78b3974a6074 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -228,12 +228,12 @@ extension (tp: Type) * in the type, as computed by `CaptureSet.ofTypeDeeply`. */ def deepCaptureSet(using Context): CaptureSet = - val dcs = CaptureSet.ofTypeDeeply(tp) - if dcs.isAlwaysEmpty then dcs + val dcs = CaptureSet.ofTypeDeeply(tp.widen.stripCapturing) + if dcs.isAlwaysEmpty then tp.captureSet else tp match case tp @ ReachCapability(_) => tp.singletonCaptureSet - case tp: SingletonCaptureRef => tp.reach.singletonCaptureSet - case _ => dcs + case tp: SingletonCaptureRef if tp.isTrackableRef => tp.reach.singletonCaptureSet + case _ => tp.captureSet ++ dcs /** A type capturing `ref` */ def capturing(ref: CaptureRef)(using Context): Type = From cdd03b71a41c76e71f677235500d4db51b6f1ea7 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 12 Oct 2024 18:31:51 +0200 Subject: [PATCH 026/202] Align deep capture sets with reach capabilities Count in dcs exactly those locations where a cap gets replaced by a reach capability. --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 26 ++++++++++++------- tests/neg-custom-args/captures/reaches.check | 10 ++++++- tests/neg-custom-args/captures/reaches.scala | 4 +-- .../captures/refine-reach-shallow.scala | 9 ++++--- .../captures/refine-withFile.scala | 4 +-- 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 81b4287961ba..835e413463bd 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1064,7 +1064,7 @@ object CaptureSet: case ref: (TermRef | TermParamRef) if ref.isMaxCapability => if ref.isTrackableRef then ref.singletonCaptureSet else CaptureSet.universal - case ReachCapability(ref1) => deepCaptureSet(ref1.widen) + case ReachCapability(ref1) => ref1.widen.deepCaptureSet .showing(i"Deep capture set of $ref: ${ref1.widen} = $result", capt) case _ => ofType(ref.underlying, followResult = true) @@ -1115,17 +1115,25 @@ object CaptureSet: /** The deep capture set of a type is the union of all covariant occurrences of * capture sets. Nested existential sets are approximated with `cap`. + * NOTE: The traversal logic needs to be in sync with narrowCaps in CaptureOps, which + * replaces caps with reach capabilties. */ def ofTypeDeeply(tp: Type)(using Context): CaptureSet = val collect = new TypeAccumulator[CaptureSet]: - def apply(cs: CaptureSet, t: Type) = t.dealias match - case t @ CapturingType(p, cs1) => - val cs2 = apply(cs, p) - if variance > 0 then cs2 ++ cs1 else cs2 - case t @ Existential(_, _) => - apply(cs, Existential.toCap(t)) - case _ => - foldOver(cs, t) + def apply(cs: CaptureSet, t: Type) = + if variance <= 0 then cs + else t.dealias match + case t @ CapturingType(p, cs1) => + this(cs, p) ++ cs1 + case t @ AnnotatedType(parent, ann) => + this(cs, parent) + case t @ FunctionOrMethod(args, res @ Existential(_, _)) + if args.forall(_.isAlwaysPure) => + this(cs, Existential.toCap(res)) + case t @ Existential(_, _) => + cs + case _ => + foldOver(cs, t) collect(CaptureSet.empty, tp) type AssumedContains = immutable.Map[TypeRef, SimpleIdentitySet[CaptureRef]] diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index f00fea09ed8c..b578934219f9 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -30,7 +30,7 @@ | ^^^^^^^^^^^^^^^^^^^ | Local reach capability id* leaks into capture scope of method test -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:62:27 -------------------------------------- -62 | val f1: File^{id*} = id(f) // error, since now id(f): File^ +62 | val f1: File^{id*} = id(f) // error, since now id(f): File^ // error | ^^^^^ | Found: File^{f} | Required: File^{id*} @@ -50,3 +50,11 @@ | Type argument () -> Unit does not conform to lower bound () => Unit | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/reaches.scala:61:31 ----------------------------------------------------------- +61 | val leaked = usingFile[File^{id*}]: f => // error + | ^^^ + | id* cannot be tracked since its capture set is empty +-- Error: tests/neg-custom-args/captures/reaches.scala:62:18 ----------------------------------------------------------- +62 | val f1: File^{id*} = id(f) // error, since now id(f): File^ // error + | ^^^ + | id* cannot be tracked since its capture set is empty diff --git a/tests/neg-custom-args/captures/reaches.scala b/tests/neg-custom-args/captures/reaches.scala index c33ba80a668b..4db8d0df74d8 100644 --- a/tests/neg-custom-args/captures/reaches.scala +++ b/tests/neg-custom-args/captures/reaches.scala @@ -58,8 +58,8 @@ def attack2 = val id: File^ -> File^ = x => x // val id: File^ -> EX C.File^C - val leaked = usingFile[File^{id*}]: f => - val f1: File^{id*} = id(f) // error, since now id(f): File^ + val leaked = usingFile[File^{id*}]: f => // error + val f1: File^{id*} = id(f) // error, since now id(f): File^ // error f1 class List[+A]: diff --git a/tests/neg-custom-args/captures/refine-reach-shallow.scala b/tests/neg-custom-args/captures/refine-reach-shallow.scala index 525d33fdb7c5..f78c99f919af 100644 --- a/tests/neg-custom-args/captures/refine-reach-shallow.scala +++ b/tests/neg-custom-args/captures/refine-reach-shallow.scala @@ -5,14 +5,15 @@ def test1(): Unit = val g: IO^ => IO^{f*} = f // error def test2(): Unit = val f: [R] -> (IO^ => R) -> R = ??? - val g: [R] -> (IO^{f*} => R) -> R = f // error + val ff = f + val g: [R] -> (IO^{f*} => R) -> R = f // error // error def test3(): Unit = val f: [R] -> (IO^ -> R) -> R = ??? - val g: [R] -> (IO^{f*} -> R) -> R = f // error + val g: [R] -> (IO^{f*} -> R) -> R = f // error // error def test4(): Unit = val xs: List[IO^] = ??? val ys: List[IO^{xs*}] = xs // ok def test5(): Unit = val f: [R] -> (IO^ -> R) -> IO^ = ??? - val g: [R] -> (IO^ -> R) -> IO^{f*} = f // error - val h: [R] -> (IO^{f*} -> R) -> IO^ = f // error + val g: [R] -> (IO^ -> R) -> IO^{f*} = f // error // error + val h: [R] -> (IO^{f*} -> R) -> IO^ = f // error // error diff --git a/tests/neg-custom-args/captures/refine-withFile.scala b/tests/neg-custom-args/captures/refine-withFile.scala index 823b62711d05..e7958ab66fc8 100644 --- a/tests/neg-custom-args/captures/refine-withFile.scala +++ b/tests/neg-custom-args/captures/refine-withFile.scala @@ -4,5 +4,5 @@ trait File val useFile: [R] -> (path: String) -> (op: File^ -> R) -> R = ??? def main(): Unit = val f: [R] -> (path: String) -> (op: File^ -> R) -> R = useFile - val g: [R] -> (path: String) -> (op: File^{f*} -> R) -> R = f // error - val leaked = g[File^{f*}]("test")(f => f) // boom + val g: [R] -> (path: String) -> (op: File^{f*} -> R) -> R = f // error // error + val leaked = g[File^{f*}]("test")(f => f) // error From 822550d035464912cc71b5ac5c3cbd0537f6b0fb Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 29 Oct 2024 15:26:25 +0100 Subject: [PATCH 027/202] Refactor handling of applications Simplify code that handles applications, avoiding adding pieces of mutable state. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 5 + .../dotty/tools/dotc/cc/CheckCaptures.scala | 104 ++++++++---------- .../dotty/tools/dotc/transform/Recheck.scala | 6 +- 3 files changed, 52 insertions(+), 63 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 78b3974a6074..9d1980037d74 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -273,6 +273,11 @@ extension (tp: Type) case _ => tp + /** The first element of this path type */ + final def pathRoot(using Context): Type = tp.dealias match + case tp1: NamedType if tp1.symbol.owner.isClass => tp1.prefix.pathRoot + case _ => tp + /** If this is a unboxed capturing type with nonempty capture set, its boxed version. * Or, if type is a TypeBounds of capturing types, the version where the bounds are boxed. * The identity for all other types. diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 2a3cd50d0e0c..b82c40a4185c 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -429,49 +429,16 @@ class CheckCaptures extends Recheck, SymTransformer: end markFree /** Include references captured by the called method in the current environment stack */ - def includeCallCaptures(sym: Symbol, pos: SrcPos)(using Context): Unit = - if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos) - - private val prefixCalls = util.EqHashSet[GenericApply]() - private val unboxedArgs = util.EqHashSet[Tree]() - - def handleCall(meth: Symbol, call: GenericApply, eval: () => Type)(using Context): Type = - if prefixCalls.remove(call) then return eval() - - val unboxedParamNames = - meth.rawParamss.flatMap: params => - params.collect: - case param if param.hasAnnotation(defn.UnboxAnnot) => - param.name - .toSet - - def markUnboxedArgs(call: GenericApply): Unit = call.fun.tpe.widen match - case MethodType(pnames) => - for (pname, arg) <- pnames.lazyZip(call.args) do - if unboxedParamNames.contains(pname) then - unboxedArgs.add(arg) - case _ => - - def markPrefixCalls(tree: Tree): Unit = tree match - case tree: GenericApply => - prefixCalls.add(tree) - markUnboxedArgs(tree) - markPrefixCalls(tree.fun) - case _ => - - markUnboxedArgs(call) - markPrefixCalls(call.fun) - val res = eval() - includeCallCaptures(meth, call.srcPos) - res - end handleCall + def includeCallCaptures(sym: Symbol, resType: Type, pos: SrcPos)(using Context): Unit = resType match + case _: MethodOrPoly => // wait until method is fully applied + case _ => + if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos) override def recheckIdent(tree: Ident, pt: Type)(using Context): Type = - if tree.symbol.is(Method) then - if tree.symbol.info.isParameterless then - // there won't be an apply; need to include call captures now - includeCallCaptures(tree.symbol, tree.srcPos) - else if !tree.symbol.isStatic then + val sym = tree.symbol + if sym.is(Method) then + includeCallCaptures(sym, sym.info, tree.srcPos) + else if !sym.isStatic then //debugShowEnvs() def addSelects(ref: TermRef, pt: Type): TermRef = pt match case pt: PathSelectionProto if ref.isTracked => @@ -479,11 +446,11 @@ class CheckCaptures extends Recheck, SymTransformer: // class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters. addSelects(ref.select(pt.sym).asInstanceOf[TermRef], pt.pt) case _ => ref - val ref = tree.symbol.termRef + val ref = sym.termRef val pathRef = addSelects(ref, pt) //if pathRef ne ref then // println(i"add selects $ref --> $pathRef") - markFree(tree.symbol, if false then ref else pathRef, tree.srcPos) + markFree(sym, if false then ref else pathRef, tree.srcPos) super.recheckIdent(tree, pt) override def selectionProto(tree: Select, pt: Type)(using Context): Type = @@ -541,6 +508,16 @@ class CheckCaptures extends Recheck, SymTransformer: selType }//.showing(i"recheck sel $tree, $qualType = $result") + /** Copy all @use annotations on method parameter symbols to the corresponding paramInfo types. + */ + override def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = + val paramInfosWithUses = funtpe.paramInfos.zipWithConserve(funtpe.paramNames): (formal, pname) => + val paramOpt = meth.rawParamss.nestedFind(_.name == pname) + paramOpt.flatMap(_.getAnnotation(defn.UnboxAnnot)) match + case Some(ann) => AnnotatedType(formal, ann) + case _ => formal + funtpe.derivedLambdaType(paramInfos = paramInfosWithUses) + override def recheckApply(tree: Apply, pt: Type)(using Context): Type = val meth = tree.fun.symbol @@ -575,15 +552,19 @@ class CheckCaptures extends Recheck, SymTransformer: tp.derivedCapturingType(forceBox(parent), refs) mapArgUsing(forceBox) else - handleCall(meth, tree, () => super.recheckApply(tree, pt)) + val res = super.recheckApply(tree, pt) + includeCallCaptures(meth, res, tree.srcPos) + res end recheckApply protected override def recheckArg(arg: Tree, formal: Type)(using Context): Type = val argType = recheck(arg, formal) - if unboxedArgs.contains(arg) then - capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}") - markFree(argType.deepCaptureSet, arg.srcPos) + formal match + case AnnotatedType(formal1, ann) if ann.symbol == defn.UnboxAnnot => + capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}") + markFree(argType.deepCaptureSet, arg.srcPos) + case _ => argType /** A specialized implementation of the apply rule. @@ -611,10 +592,10 @@ class CheckCaptures extends Recheck, SymTransformer: val appType = Existential.toCap(super.recheckApplication(tree, qualType, funType, argTypes)) val qualCaptures = qualType.captureSet val argCaptures = - for (arg, argType) <- tree.args.lazyZip(argTypes) yield - if unboxedArgs.remove(arg) // need to ensure the remove happens, that's why argCaptures is computed even if not needed. - then argType.deepCaptureSet - else argType.captureSet + for (argType, formal) <- argTypes.lazyZip(funType.paramInfos) yield + formal match + case AnnotatedType(_, ann) if ann.symbol == defn.UnboxAnnot => argType.deepCaptureSet + case _ => argType.captureSet appType match case appType @ CapturingType(appType1, refs) if qualType.exists @@ -709,8 +690,10 @@ class CheckCaptures extends Recheck, SymTransformer: i"Sealed type variable $pname", "be instantiated to", i"This is often caused by a local capability$where\nleaking as part of its result.", tree.srcPos) - try handleCall(meth, tree, () => Existential.toCap(super.recheckTypeApply(tree, pt))) - finally checkContains(tree) + val res = Existential.toCap(super.recheckTypeApply(tree, pt)) + includeCallCaptures(meth, res, tree.srcPos) + checkContains(tree) + res end recheckTypeApply /** Faced with a tree of form `caps.contansImpl[CS, r.type]`, check that `R` is a tracked @@ -1161,12 +1144,7 @@ class CheckCaptures extends Recheck, SymTransformer: (erefs /: erefs.elems): (erefs, eref) => eref match case eref: ThisType if isPureContext(ctx.owner, eref.cls) => - - def pathRoot(aref: Type): Type = aref match - case aref: NamedType if aref.symbol.owner.isClass => pathRoot(aref.prefix) - case _ => aref - - def isOuterRef(aref: Type): Boolean = pathRoot(aref) match + def isOuterRef(aref: Type): Boolean = aref.pathRoot match case aref: NamedType => eref.cls.isProperlyContainedIn(aref.symbol.owner) case aref: ThisType => eref.cls.isProperlyContainedIn(aref.cls) case _ => false @@ -1176,7 +1154,7 @@ class CheckCaptures extends Recheck, SymTransformer: // Include implicitly added outer references in the capture set of the class of `eref`. for outerRef <- outerRefs.elems do if !erefs.elems.contains(outerRef) - && !pathRoot(outerRef).isInstanceOf[ThisType] + && !outerRef.pathRoot.isInstanceOf[ThisType] // we don't need to add outer ThisTypes as these are anyway added as path // prefixes at the use site. And this exemption is required since capture sets // of non-local classes are always empty, so we can't add an outer this to them. @@ -1333,6 +1311,12 @@ class CheckCaptures extends Recheck, SymTransformer: /** If actual is a tracked CaptureRef `a` and widened is a capturing type T^C, * improve `T^C` to `T^{a}`, following the VAR rule of CC. + * TODO: We probably should do this also for other top-level occurrences of captures + * E.g. + * class Foo { def a: C^{io}; val def: C^{async} } + * val foo: Foo^{io, async} + * Then + * foo: Foo { def a: C^{foo}; def b: C^{foo} }^{foo} */ private def improveCaptures(widened: Type, actual: Type)(using Context): Type = actual match case ref: CaptureRef if ref.isTracked => diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index be1d9d8bee54..b7e4b9b14c92 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -99,7 +99,7 @@ object Recheck: * - in function and method parameter types * - under annotations */ - def normalizeByName(tp: Type)(using Context): Type = tp.dealias match + def normalizeByName(tp: Type)(using Context): Type = tp.dealiasKeepAnnots match case tp: ExprType => mapExprType(tp) case tp: PolyType => @@ -291,7 +291,7 @@ abstract class Recheck extends Phase, SymTransformer: protected def instantiate(mt: MethodType, argTypes: List[Type], sym: Symbol)(using Context): Type = mt.instantiate(argTypes) - /** A hook to massage the type of an applied method; currently not overridden */ + /** A hook to massage the type of an applied method */ protected def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = funtpe protected def recheckArg(arg: Tree, formal: Type)(using Context): Type = @@ -336,7 +336,7 @@ abstract class Recheck extends Phase, SymTransformer: assert(formals.isEmpty) Nil val argTypes = recheckArgs(tree.args, formals, fntpe.paramRefs) - recheckApplication(tree, qualType, fntpe1, argTypes) + recheckApplication(tree, qualType, fntpe, argTypes) //.showing(i"typed app $tree : $fntpe with ${tree.args}%, % : $argTypes%, % = $result") case tp => assert(false, i"unexpected type of ${tree.fun}: $tp") From e7c018b07486259c1406abf434b0718ece652187 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 30 Oct 2024 10:42:21 +0100 Subject: [PATCH 028/202] Handle reach capabilities correctly in markFree The correct point to address charging reach capabilities is in markFree itself: When a reach capability goes out of scope, and that capability is not a parameter, we need to continue with the underlying capture set. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 39 +++-- .../dotty/tools/dotc/cc/CheckCaptures.scala | 134 +++++++++--------- compiler/src/dotty/tools/dotc/cc/Setup.scala | 3 +- .../src/scala/collection/Iterator.scala | 3 +- .../captures/delayedRunops.check | 14 ++ .../captures/delayedRunops.scala | 28 ++++ tests/neg-custom-args/captures/i16114.check | 55 +++++++ tests/neg-custom-args/captures/i21347.check | 11 +- tests/neg-custom-args/captures/i21347.scala | 2 +- tests/neg-custom-args/captures/i21442.check | 3 +- tests/neg-custom-args/captures/path-use.check | 4 + tests/neg-custom-args/captures/path-use.scala | 25 ++++ tests/neg-custom-args/captures/reaches.check | 10 +- tests/neg-custom-args/captures/real-try.scala | 2 +- .../captures/unsound-reach-3.scala | 4 +- .../captures/unsound-reach-4.check | 10 +- .../captures/unsound-reach-4.scala | 4 +- tests/neg-custom-args/captures/uses.check | 28 ++++ .../neg-custom-args/captures/wf-reach-1.check | 4 + .../neg-custom-args/captures/wf-reach-1.scala | 2 + .../captures/widen-reach.check | 6 +- tests/neg/leak-problem.scala | 19 ++- .../captures/gears-problem.scala} | 3 +- tests/pos-custom-args/captures/path-use.scala | 16 ++- 24 files changed, 319 insertions(+), 110 deletions(-) create mode 100644 tests/neg-custom-args/captures/delayedRunops.check create mode 100644 tests/neg-custom-args/captures/delayedRunops.scala create mode 100644 tests/neg-custom-args/captures/i16114.check create mode 100644 tests/neg-custom-args/captures/path-use.check create mode 100644 tests/neg-custom-args/captures/path-use.scala create mode 100644 tests/neg-custom-args/captures/uses.check create mode 100644 tests/neg-custom-args/captures/wf-reach-1.check create mode 100644 tests/neg-custom-args/captures/wf-reach-1.scala rename tests/{pos/gears-probem.scala => pos-custom-args/captures/gears-problem.scala} (89%) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 9d1980037d74..f9c33695e9fe 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -221,19 +221,22 @@ extension (tp: Type) case tp: SingletonCaptureRef => tp.captureSetOfInfo case _ => CaptureSet.ofType(tp, followResult = false) - /** The deep capture set of a type. - * For singleton capabilities `x` and reach capabilities `x*`, this is `{x*}`, provided - * the underlying capture set resulting from traversing the type is non-empty. - * For other types this is the union of all covariant capture sets embedded - * in the type, as computed by `CaptureSet.ofTypeDeeply`. + /** The deep capture set of a type. This is by default the union of all + * covariant capture sets embedded in the widened type, as computed by + * `CaptureSet.ofTypeDeeply`. If that set is nonempty, and the type is + * a singleton capability `x` or a reach capability `x*`, the deep capture + * set can be narrowed to`{x*}`. */ def deepCaptureSet(using Context): CaptureSet = val dcs = CaptureSet.ofTypeDeeply(tp.widen.stripCapturing) if dcs.isAlwaysEmpty then tp.captureSet else tp match - case tp @ ReachCapability(_) => tp.singletonCaptureSet - case tp: SingletonCaptureRef if tp.isTrackableRef => tp.reach.singletonCaptureSet - case _ => tp.captureSet ++ dcs + case tp @ ReachCapability(_) => + tp.singletonCaptureSet + case tp: SingletonCaptureRef if tp.isTrackableRef => + tp.reach.singletonCaptureSet + case _ => + tp.captureSet ++ dcs /** A type capturing `ref` */ def capturing(ref: CaptureRef)(using Context): Type = @@ -274,10 +277,28 @@ extension (tp: Type) tp /** The first element of this path type */ - final def pathRoot(using Context): Type = tp.dealias match + final def pathRoot(using Context): Type = tp.dealiasKeepAnnots match case tp1: NamedType if tp1.symbol.owner.isClass => tp1.prefix.pathRoot + case ReachCapability(tp1) => tp1.pathRoot case _ => tp + /** If this part starts with `C.this`, the class `C`. + * Otherwise, if it starts with a reference `r`, `r`'s owner. + * Otherwise NoSymbol. + */ + final def pathOwner(using Context): Symbol = pathRoot match + case tp1: NamedType => tp1.symbol.owner + case tp1: ThisType => tp1.cls + case _ => NoSymbol + + final def isParamPath(using Context): Boolean = tp.dealias match + case tp1: NamedType => + tp1.prefix match + case _: ThisType | NoPrefix => + tp1.symbol.is(Param) || tp1.symbol.is(ParamAccessor) + case prefix => prefix.isParamPath + case _ => false + /** If this is a unboxed capturing type with nonempty capture set, its boxed version. * Or, if type is a TypeBounds of capturing types, the version where the bounds are boxed. * The identity for all other types. diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index b82c40a4185c..788da0dbcc57 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -333,20 +333,22 @@ class CheckCaptures extends Recheck, SymTransformer: then CaptureSet.Var(sym.owner, level = sym.ccLevel) else CaptureSet.empty) - /** For all nested environments up to `limit` or a closed environment perform `op`, - * but skip environmenrts directly enclosing environments of kind ClosureResult. + /** The next environment enclosing `env` that needs to be charged + * with free references. + * Skips environments directly enclosing environments of kind ClosureResult. + * @param included Whether an environment is included in the range of + * environments to charge. Once `included` is false, no + * more environments need to be charged. */ - def forallOuterEnvsUpTo(limit: Symbol)(op: Env => Unit)(using Context): Unit = - def recur(env: Env, skip: Boolean): Unit = - if env.isOpen && env.owner != limit then - if !skip then op(env) - if !env.isOutermost then - var nextEnv = env.outer - if env.owner.isConstructor then - if nextEnv.owner != limit && !nextEnv.isOutermost then - nextEnv = nextEnv.outer - recur(nextEnv, skip = env.kind == EnvKind.ClosureResult) - recur(curEnv, skip = false) + def nextEnvToCharge(env: Env, included: Env => Boolean)(using Context): Env = + var nextEnv = env.outer + if env.owner.isConstructor then + if included(nextEnv) then nextEnv = nextEnv.outer + if env.kind == EnvKind.ClosureResult then + // skip this one + nextEnvToCharge(nextEnv, included) + else + nextEnv /** A description where this environment comes from */ private def provenance(env: Env)(using Context): String = @@ -360,7 +362,6 @@ class CheckCaptures extends Recheck, SymTransformer: else i"\nof the enclosing ${owner.showLocated}" - /** Include `sym` in the capture sets of all enclosing environments nested in the * the environment in which `sym` is defined. */ @@ -369,9 +370,12 @@ class CheckCaptures extends Recheck, SymTransformer: def markFree(sym: Symbol, ref: TermRef, pos: SrcPos)(using Context): Unit = if sym.exists && ref.isTracked then - forallOuterEnvsUpTo(sym.enclosure): env => - capt.println(i"Mark $sym with cs ${ref.captureSet} free in ${env.owner}") - checkElem(ref, env.captured, pos, provenance(env)) + def recur(env: Env): Unit = + if env.isOpen && env.owner != sym.enclosure then + capt.println(i"Mark $sym with cs ${ref.captureSet} free in ${env.owner}") + checkElem(ref, env.captured, pos, provenance(env)) + recur(nextEnvToCharge(env, _.owner != sym.enclosure)) + recur(curEnv) /** Make sure (projected) `cs` is a subset of the capture sets of all enclosing * environments. At each stage, only include references from `cs` that are outside @@ -386,46 +390,53 @@ class CheckCaptures extends Recheck, SymTransformer: else !sym.isContainedIn(env.owner) - def checkSubsetEnv(cs: CaptureSet, env: Env)(using Context): Unit = - // Only captured references that are visible from the environment - // should be included. - val included = cs.filter: c => - c.stripReach match - case ref: NamedType => - val refSym = ref.symbol - val refOwner = refSym.owner - val isVisible = isVisibleFromEnv(refOwner, env) - if isVisible && !ref.isRootCapability then - ref match - case ref: TermRef if ref.prefix `ne` NoPrefix => - // If c is a path of a class defined outside the environment, - // we check the capture set of its info. - checkSubsetEnv(ref.captureSetOfInfo, env) - case _ => - if !isVisible - && (c.isReach || ref.isType) - && (!ccConfig.useSealed || refSym.is(Param)) - && refOwner == env.owner - then - if refSym.hasAnnotation(defn.UnboxAnnot) then - capt.println(i"exempt: $ref in $refOwner") - else - // Reach capabilities that go out of scope have to be approximated - // by their underlying capture set, which cannot be universal. - // Reach capabilities of @unboxed parameters are exempted. - val cs = CaptureSet.ofInfo(c) - cs.disallowRootCapability: () => - report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", pos) - checkSubset(cs, env.captured, pos, provenance(env)) - isVisible - case ref: ThisType => isVisibleFromEnv(ref.cls, env) - case _ => false - checkSubset(included, env.captured, pos, provenance(env)) - capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") - - if !cs.isAlwaysEmpty then - forallOuterEnvsUpTo(ctx.owner.topLevelClass): env => - checkSubsetEnv(cs, env) + def checkUseDeclared(c: CaptureRef, env: Env) = + c.pathRoot match + case ref: NamedType if !ref.symbol.hasAnnotation(defn.UnboxAnnot) => + val what = if ref.isType then "Capture set parameter" else "Local reach capability" + report.error( + em"""$what $c leaks into capture scope of ${env.ownerString}. + |To allow this, the ${ref.symbol} should be declared with a @use annotation""", pos) + case _ => + + def recur(cs: CaptureSet, env: Env)(using Context): Unit = + if env.isOpen && !env.owner.isStaticOwner && !cs.isAlwaysEmpty then + // Only captured references that are visible from the environment + // should be included. + val included = cs.filter: c => + val isVisible = c.pathRoot match + case ref: NamedType => isVisibleFromEnv(ref.symbol.owner, env) + case ref: ThisType => isVisibleFromEnv(ref.cls, env) + case ref => + false + if !isVisible then + c match + case ReachCapability(c1) => + if c1.isParamPath then + checkUseDeclared(c, env) + else + // When a reach capabilty x* where `x` is not a parameter goes out + // of scope, we need to continue with `x`'s underlying deep capture set. + // It is an error if that set contains cap. + // The same is not an issue for normal capabilities since in a local + // definition `val x = e`, the capabilities of `e` have already been charged. + // Note: It's not true that the underlying capture set of a reach capability + // is always cap. Reach capabilities over paths depend on the prefix, which + // might turn a cap into something else. + // The path-use.scala neg test contains an example. + val underlying = CaptureSet.ofTypeDeeply(c1.widen) + capt.println(i"Widen reach $c to $underlying in ${env.owner}") + underlying.disallowRootCapability: () => + report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", pos) + recur(underlying, env) + case c: TypeRef if c.isParamPath => + checkUseDeclared(c, env) + case _ => + isVisible + checkSubset(included, env.captured, pos, provenance(env)) + capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") + recur(included, nextEnvToCharge(env, !_.owner.isStaticOwner)) + recur(cs, curEnv) end markFree /** Include references captured by the called method in the current environment stack */ @@ -1144,13 +1155,8 @@ class CheckCaptures extends Recheck, SymTransformer: (erefs /: erefs.elems): (erefs, eref) => eref match case eref: ThisType if isPureContext(ctx.owner, eref.cls) => - def isOuterRef(aref: Type): Boolean = aref.pathRoot match - case aref: NamedType => eref.cls.isProperlyContainedIn(aref.symbol.owner) - case aref: ThisType => eref.cls.isProperlyContainedIn(aref.cls) - case _ => false - - val outerRefs = arefs.filter(isOuterRef) - + val outerRefs = arefs.filter: aref => + eref.cls.isProperlyContainedIn(aref.pathOwner) // Include implicitly added outer references in the capture set of the class of `eref`. for outerRef <- outerRefs.elems do if !erefs.elems.contains(outerRef) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 7cd12b999618..a64703bf059e 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -763,7 +763,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: report.warning(em"redundant capture: $dom already accounts for $ref", pos) if ref.captureSetOfInfo.elems.isEmpty && !ref.derivesFrom(defn.Caps_Capability) then - report.error(em"$ref cannot be tracked since its capture set is empty", pos) + val deepStr = if ref.isReach then " deep" else "" + report.error(em"$ref cannot be tracked since its$deepStr capture set is empty", pos) check(parent.captureSet, parent) val others = diff --git a/scala2-library-cc/src/scala/collection/Iterator.scala b/scala2-library-cc/src/scala/collection/Iterator.scala index 4d1b0ed4ff95..22632d738199 100644 --- a/scala2-library-cc/src/scala/collection/Iterator.scala +++ b/scala2-library-cc/src/scala/collection/Iterator.scala @@ -1159,7 +1159,8 @@ object Iterator extends IterableFactory[Iterator] { // If we advanced the current iterator to a ConcatIterator, merge it into this one @tailrec def merge(): Unit = if (current.isInstanceOf[ConcatIterator[_]]) { - val c = current.asInstanceOf[ConcatIterator[A]] + val c: ConcatIterator[A] { val from: Iterator[A] } + = current.asInstanceOf current = c.current.asInstanceOf // !!! CC unsafe op currentHasNextChecked = c.currentHasNextChecked if (c.tail != null) { diff --git a/tests/neg-custom-args/captures/delayedRunops.check b/tests/neg-custom-args/captures/delayedRunops.check new file mode 100644 index 000000000000..68da4672acf5 --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops.check @@ -0,0 +1,14 @@ +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:16:13 ----------------------------------------------------- +16 | runOps(ops1) // error + | ^^^^ + | reference ops* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:22:13 ----------------------------------------------------- +22 | runOps(ops1) // error + | ^^^^ + | Local reach capability ops1* leaks into capture scope of enclosing function +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:28:13 ----------------------------------------------------- +28 | runOps(ops1) // error + | ^^^^ + | reference ops* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit diff --git a/tests/neg-custom-args/captures/delayedRunops.scala b/tests/neg-custom-args/captures/delayedRunops.scala new file mode 100644 index 000000000000..02786bcb836a --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops.scala @@ -0,0 +1,28 @@ +import language.experimental.captureChecking +import caps.unbox + + // ok + def runOps(@unbox ops: List[() => Unit]): Unit = + ops.foreach(op => op()) + + // ok + def delayedRunOps(@unbox ops: List[() => Unit]): () ->{ops*} Unit = // @unbox should not be necessary in the future + () => runOps(ops) + + // unsound: impure operation pretended pure + def delayedRunOps1(ops: List[() => Unit]): () ->{} Unit = + () => + val ops1 = ops + runOps(ops1) // error + + // unsound: impure operation pretended pure + def delayedRunOps2(ops: List[() => Unit]): () ->{} Unit = + () => + val ops1: List[() => Unit] = ops + runOps(ops1) // error + + // unsound: impure operation pretended pure + def delayedRunOps3(ops: List[() => Unit]): () ->{} Unit = + () => + val ops1: List[() ->{ops*} Unit] = ops + runOps(ops1) // error diff --git a/tests/neg-custom-args/captures/i16114.check b/tests/neg-custom-args/captures/i16114.check new file mode 100644 index 000000000000..6d81d8d63747 --- /dev/null +++ b/tests/neg-custom-args/captures/i16114.check @@ -0,0 +1,55 @@ +-- Error: tests/neg-custom-args/captures/i16114.scala:18:12 ------------------------------------------------------------ +18 | expect[Cap^] { // error + | ^^^^^^^^^^^^ + | Sealed type variable T cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. + | This is often caused by a local capability in an argument of method expect + | leaking as part of its result. +-- Error: tests/neg-custom-args/captures/i16114.scala:20:8 ------------------------------------------------------------- +20 | fs // error (limitation) + | ^^ + | (fs : Cap^) cannot be referenced here; it is not included in the allowed capture set {io} + | of an enclosing function literal with expected type Unit ->{io} Unit +-- Error: tests/neg-custom-args/captures/i16114.scala:24:12 ------------------------------------------------------------ +24 | expect[Cap^] { // error + | ^^^^^^^^^^^^ + | Sealed type variable T cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. + | This is often caused by a local capability in an argument of method expect + | leaking as part of its result. +-- Error: tests/neg-custom-args/captures/i16114.scala:26:8 ------------------------------------------------------------- +26 | io // error (limitation) + | ^^ + | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {fs} + | of an enclosing function literal with expected type Unit ->{fs} Unit +-- Error: tests/neg-custom-args/captures/i16114.scala:30:12 ------------------------------------------------------------ +30 | expect[Cap^] { // error + | ^^^^^^^^^^^^ + | Sealed type variable T cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. + | This is often caused by a local capability in an argument of method expect + | leaking as part of its result. +-- Error: tests/neg-custom-args/captures/i16114.scala:36:12 ------------------------------------------------------------ +36 | expect[Cap^](io) // error + | ^^^^^^^^^^^^ + | Sealed type variable T cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. + | This is often caused by a local capability in an argument of method expect + | leaking as part of its result. +-- Error: tests/neg-custom-args/captures/i16114.scala:39:12 ------------------------------------------------------------ +39 | expect[Cap^] { // error + | ^^^^^^^^^^^^ + | Sealed type variable T cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. + | This is often caused by a local capability in an argument of method expect + | leaking as part of its result. +-- Error: tests/neg-custom-args/captures/i16114.scala:40:8 ------------------------------------------------------------- +40 | io.use() // error + | ^^ + | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type Unit -> Unit +-- Error: tests/neg-custom-args/captures/i16114.scala:41:8 ------------------------------------------------------------- +41 | io // error + | ^^ + | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type Unit -> Unit diff --git a/tests/neg-custom-args/captures/i21347.check b/tests/neg-custom-args/captures/i21347.check index c680a54d3efc..e1845d9778d5 100644 --- a/tests/neg-custom-args/captures/i21347.check +++ b/tests/neg-custom-args/captures/i21347.check @@ -1,15 +1,12 @@ -- Error: tests/neg-custom-args/captures/i21347.scala:4:15 ------------------------------------------------------------- 4 | ops.foreach: op => // error | ^ - | Local reach capability C leaks into capture scope of method runOps + | Capture set parameter C leaks into capture scope of method runOps. + | To allow this, the type C should be declared with a @use annotation 5 | op() --- Error: tests/neg-custom-args/captures/i21347.scala:8:14 ------------------------------------------------------------- -8 | () => runOps(f :: Nil) // error - | ^^^^^^^^^^^^^^^^ - | reference (caps.cap : caps.Capability) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Unit -- Error: tests/neg-custom-args/captures/i21347.scala:11:15 ------------------------------------------------------------ 11 | ops.foreach: op => // error | ^ - | Local reach capability ops* leaks into capture scope of method runOpsAlt + | Local reach capability ops* leaks into capture scope of method runOpsAlt. + | To allow this, the parameter ops should be declared with a @use annotation 12 | op() diff --git a/tests/neg-custom-args/captures/i21347.scala b/tests/neg-custom-args/captures/i21347.scala index 41887be6a78a..54fe859caedd 100644 --- a/tests/neg-custom-args/captures/i21347.scala +++ b/tests/neg-custom-args/captures/i21347.scala @@ -5,7 +5,7 @@ def runOps[C^](ops: List[() ->{C^} Unit]): Unit = op() def boom(f: () => Unit): () -> Unit = - () => runOps(f :: Nil) // error + () => runOps(f :: Nil) // now ok def runOpsAlt(ops: List[() => Unit]): Unit = ops.foreach: op => // error diff --git a/tests/neg-custom-args/captures/i21442.check b/tests/neg-custom-args/captures/i21442.check index a3bbf65c5988..30becfea0215 100644 --- a/tests/neg-custom-args/captures/i21442.check +++ b/tests/neg-custom-args/captures/i21442.check @@ -1,7 +1,8 @@ -- Error: tests/neg-custom-args/captures/i21442.scala:9:13 ------------------------------------------------------------- 9 | val io = x.unbox // error: local reach capability {x*} leaks | ^^^^^^^ - | Local reach capability x* leaks into capture scope of method foo + | Local reach capability x* leaks into capture scope of method foo. + | To allow this, the parameter x should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/i21442.scala:17:14 ------------------------------------------------------------ 17 | val io = x1.unbox // error | ^^^^^^^^ diff --git a/tests/neg-custom-args/captures/path-use.check b/tests/neg-custom-args/captures/path-use.check new file mode 100644 index 000000000000..e09ee232dd17 --- /dev/null +++ b/tests/neg-custom-args/captures/path-use.check @@ -0,0 +1,4 @@ +-- Error: tests/neg-custom-args/captures/path-use.scala:18:32 ---------------------------------------------------------- +18 | val g = () => println(c.procs.head) // error, local reach capability c.procs* leaks + | ^^^^^^^^^^^^ + | Local reach capability c.procs* leaks into capture scope of method test diff --git a/tests/neg-custom-args/captures/path-use.scala b/tests/neg-custom-args/captures/path-use.scala new file mode 100644 index 000000000000..31feb4c0adf4 --- /dev/null +++ b/tests/neg-custom-args/captures/path-use.scala @@ -0,0 +1,25 @@ +import language.experimental.namedTuples + +class IO + +class C(val f: IO^): + val procs: List[Proc] = ??? + +type Proc = () => Unit + +def test(io: IO^) = + val c = C(io) + val f = () => println(c.f) + val _: () ->{c.f} Unit = f + + val x = c.procs + val _: List[() ->{c.procs*} Unit] = x + + val g = () => println(c.procs.head) // error, local reach capability c.procs* leaks + val _: () ->{c.procs*} Unit = g + + val cc: C { val f: IO^{io}; val procs: List[() ->{io} Unit] }^{io} = + ??? + + val gg = () => println(cc.procs.head) // OK, since cc.procs* has {io} as underlying capture set + val _: () ->{io} Unit = gg diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index b578934219f9..0121f0cf7a55 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -39,11 +39,13 @@ -- Error: tests/neg-custom-args/captures/reaches.scala:79:10 ----------------------------------------------------------- 79 | ps.map((x, y) => compose1(x, y)) // error // error | ^ - | Local reach capability ps* leaks into capture scope of method mapCompose + | Local reach capability ps* leaks into capture scope of method mapCompose. + | To allow this, the parameter ps should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/reaches.scala:79:13 ----------------------------------------------------------- 79 | ps.map((x, y) => compose1(x, y)) // error // error | ^ - | Local reach capability ps* leaks into capture scope of method mapCompose + | Local reach capability ps* leaks into capture scope of method mapCompose. + | To allow this, the parameter ps should be declared with a @use annotation -- [E057] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:53:51 -------------------------------------- 53 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error | ^ @@ -53,8 +55,8 @@ -- Error: tests/neg-custom-args/captures/reaches.scala:61:31 ----------------------------------------------------------- 61 | val leaked = usingFile[File^{id*}]: f => // error | ^^^ - | id* cannot be tracked since its capture set is empty + | id* cannot be tracked since its deep capture set is empty -- Error: tests/neg-custom-args/captures/reaches.scala:62:18 ----------------------------------------------------------- 62 | val f1: File^{id*} = id(f) // error, since now id(f): File^ // error | ^^^ - | id* cannot be tracked since its capture set is empty + | id* cannot be tracked since its deep capture set is empty diff --git a/tests/neg-custom-args/captures/real-try.scala b/tests/neg-custom-args/captures/real-try.scala index 51f1a0fdea5a..8e60d4fe7326 100644 --- a/tests/neg-custom-args/captures/real-try.scala +++ b/tests/neg-custom-args/captures/real-try.scala @@ -35,4 +35,4 @@ def test(): Unit = case _: Ex1 => ??? case _: Ex2 => ??? - b.x + b diff --git a/tests/neg-custom-args/captures/unsound-reach-3.scala b/tests/neg-custom-args/captures/unsound-reach-3.scala index 0063216e957e..985beb7ae55d 100644 --- a/tests/neg-custom-args/captures/unsound-reach-3.scala +++ b/tests/neg-custom-args/captures/unsound-reach-3.scala @@ -16,8 +16,8 @@ def bad(): Unit = val boom: Foo[File^{backdoor*}] = backdoor var escaped: File^{backdoor*} = null - withFile("hello.txt"): f => // error - escaped = boom.use(f) + withFile("hello.txt"): f => + escaped = boom.use(f) // error // boom.use: (x: File^) -> File^{backdoor*}, it is a selection so reach capabilities are allowed // f: File^, so there is no reach capabilities diff --git a/tests/neg-custom-args/captures/unsound-reach-4.check b/tests/neg-custom-args/captures/unsound-reach-4.check index d359b298555e..ed8e29cdf511 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.check +++ b/tests/neg-custom-args/captures/unsound-reach-4.check @@ -1,6 +1,4 @@ --- Error: tests/neg-custom-args/captures/unsound-reach-4.scala:21:25 --------------------------------------------------- -21 | withFile("hello.txt"): f => // error - | ^ - | Reach capability backdoor* and universal capability cap cannot both - | appear in the type (f: File^) ->{backdoor*} Unit of this expression -22 | escaped = boom.use(f) +-- Error: tests/neg-custom-args/captures/unsound-reach-4.scala:22:22 --------------------------------------------------- +22 | escaped = boom.use(f) // error + | ^^^^^^^^^^^ + | Local reach capability backdoor* leaks into capture scope of method bad diff --git a/tests/neg-custom-args/captures/unsound-reach-4.scala b/tests/neg-custom-args/captures/unsound-reach-4.scala index bc66085614f2..14050b4afff2 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.scala +++ b/tests/neg-custom-args/captures/unsound-reach-4.scala @@ -18,5 +18,5 @@ def bad(): Unit = val boom: Foo[File^{backdoor*}] = backdoor var escaped: File^{backdoor*} = null - withFile("hello.txt"): f => // error - escaped = boom.use(f) + withFile("hello.txt"): f => + escaped = boom.use(f) // error diff --git a/tests/neg-custom-args/captures/uses.check b/tests/neg-custom-args/captures/uses.check new file mode 100644 index 000000000000..de583d75db06 --- /dev/null +++ b/tests/neg-custom-args/captures/uses.check @@ -0,0 +1,28 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:8:17 ------------------------------------------ +8 | val _: D^{y} = d // error, should be ok + | ^ + | Found: (d : D^{x, y}) + | Required: D^{y} + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:9:13 ------------------------------------------ +9 | val _: D = d // error + | ^ + | Found: (d : D^{x, y}) + | Required: D + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:18:34 ----------------------------------------- +18 | val _: () ->{x} () ->{y} Unit = g // error, should be ok + | ^ + | Found: () ->{x, y} (ex$7: caps.Exists) -> () ->{y} Unit + | Required: () ->{x} () ->{y} Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:19:28 ----------------------------------------- +19 | val _: () -> () -> Unit = g // error + | ^ + | Found: () ->{x, y} (ex$7: caps.Exists) -> () ->{y} Unit + | Required: () -> () -> Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/wf-reach-1.check b/tests/neg-custom-args/captures/wf-reach-1.check new file mode 100644 index 000000000000..6a3ac9771a11 --- /dev/null +++ b/tests/neg-custom-args/captures/wf-reach-1.check @@ -0,0 +1,4 @@ +-- Error: tests/neg-custom-args/captures/wf-reach-1.scala:2:17 --------------------------------------------------------- +2 | val y: Object^{x*} = ??? // error + | ^^ + | x* cannot be tracked since its deep capture set is empty diff --git a/tests/neg-custom-args/captures/wf-reach-1.scala b/tests/neg-custom-args/captures/wf-reach-1.scala new file mode 100644 index 000000000000..c8901c7ae4a8 --- /dev/null +++ b/tests/neg-custom-args/captures/wf-reach-1.scala @@ -0,0 +1,2 @@ +def test(x: List[() -> Unit]) = + val y: Object^{x*} = ??? // error diff --git a/tests/neg-custom-args/captures/widen-reach.check b/tests/neg-custom-args/captures/widen-reach.check index 06d21ff445d8..abc065a1e2bc 100644 --- a/tests/neg-custom-args/captures/widen-reach.check +++ b/tests/neg-custom-args/captures/widen-reach.check @@ -1,11 +1,13 @@ -- Error: tests/neg-custom-args/captures/widen-reach.scala:13:26 ------------------------------------------------------- 13 | val y2: IO^ -> IO^ = y1.foo // error | ^^^^^^ - | Local reach capability x* leaks into capture scope of method test + | Local reach capability x* leaks into capture scope of method test. + | To allow this, the parameter x should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/widen-reach.scala:14:30 ------------------------------------------------------- 14 | val y3: IO^ -> IO^{x*} = y1.foo // error | ^^^^^^ - | Local reach capability x* leaks into capture scope of method test + | Local reach capability x* leaks into capture scope of method test. + | To allow this, the parameter x should be declared with a @use annotation -- [E164] Declaration Error: tests/neg-custom-args/captures/widen-reach.scala:9:6 -------------------------------------- 9 | val foo: IO^ -> IO^ = x => x // error | ^ diff --git a/tests/neg/leak-problem.scala b/tests/neg/leak-problem.scala index 354d54d86707..dea2496d6c77 100644 --- a/tests/neg/leak-problem.scala +++ b/tests/neg/leak-problem.scala @@ -1,4 +1,5 @@ import language.experimental.captureChecking +import caps.unbox // Some capabilities that should be used locally trait Async: @@ -16,12 +17,28 @@ def useBoxedAsync(x: Box[Async^]): Unit = def useBoxedAsync1(x: Box[Async^]): Unit = x.get.read() // error def test(): Unit = + def useBoxedAsync(@unbox x: Box[Async^]): Unit = + val t0 = x + val t1 = t0.get + t1.read() + + def useBoxedAsync1(@unbox x: Box[Async^]): Unit = x.get.read() + + val xs: Box[Async^] = ??? + val xsLambda = () => useBoxedAsync(xs) // error + val _: () ->{xs*} Unit = xsLambda + val _: () -> Unit = xsLambda // error + val useBoxedAsync2 = (x: Box[Async^]) => val t0 = x val t1 = x.get // error t1.read() - val f: Box[Async^] => Unit = (x: Box[Async^]) => useBoxedAsync(x) + val xsLambda2 = () => useBoxedAsync2(xs) + val _: () ->{xs*} Unit = xsLambda2 + val _: () -> Unit = xsLambda2 + + val f: Box[Async^] => Unit = (x: Box[Async^]) => useBoxedAsync(x) // error def boom(x: Async^): () ->{f} Unit = () => f(Box(x)) diff --git a/tests/pos/gears-probem.scala b/tests/pos-custom-args/captures/gears-problem.scala similarity index 89% rename from tests/pos/gears-probem.scala rename to tests/pos-custom-args/captures/gears-problem.scala index 2e445c985de2..74f108f93e80 100644 --- a/tests/pos/gears-probem.scala +++ b/tests/pos-custom-args/captures/gears-problem.scala @@ -1,4 +1,5 @@ import language.experimental.captureChecking +import caps.unbox trait Future[+T]: def await: T @@ -10,7 +11,7 @@ class Collector[T](val futures: Seq[Future[T]^]): val results: Channel[Future[T]^{futures*}] = ??? end Collector -extension [T](fs: Seq[Future[T]^]) +extension [T](@unbox fs: Seq[Future[T]^]) def awaitAll = val collector: Collector[T]{val futures: Seq[Future[T]^{fs*}]} = Collector(fs) diff --git a/tests/pos-custom-args/captures/path-use.scala b/tests/pos-custom-args/captures/path-use.scala index 5eb2b60fd218..181afc41bcc2 100644 --- a/tests/pos-custom-args/captures/path-use.scala +++ b/tests/pos-custom-args/captures/path-use.scala @@ -1,4 +1,5 @@ import language.experimental.namedTuples +import caps.unbox class IO @@ -8,12 +9,13 @@ class C(val f: IO^): type Proc = () => Unit def test(io: IO^) = - val c = C(io) - val f = () => println(c.f) - val _: () ->{c.f} Unit = f + def test1(@unbox c: C { val f: IO^{io}}^{io}) = + val f = () => println(c.f) + val _: () ->{c.f} Unit = f - val x = c.procs - val _: List[() ->{c.procs*} Unit] = x + val x = c.procs + val _: List[() ->{c.procs*} Unit] = x - val g = () => println(c.procs.head) - val _: () ->{c.procs*} Unit = g + val g = () => println(c.procs.head) + val _: () ->{c.procs*} Unit = g + test1(C(io)) From fcf40454ff617ec2e06b339d01e8668a9579be7a Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 30 Oct 2024 12:27:12 +0100 Subject: [PATCH 029/202] Rename @unbox to @use --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 18 +++++++++--------- .../dotty/tools/dotc/core/Definitions.scala | 2 +- library/src/scala/caps.scala | 2 +- .../captures/delayedRunops.scala | 6 +++--- tests/neg-custom-args/captures/i15749a.scala | 4 ++-- tests/neg-custom-args/captures/i21614.scala | 6 +++--- .../captures/leak-problem-2.scala | 2 +- tests/neg-custom-args/captures/reaches.scala | 8 ++++---- .../captures/spread-problem.scala | 2 +- .../captures/unbox-overrides.check | 8 ++++---- .../captures/unbox-overrides.scala | 8 ++++---- tests/neg/i20503.scala | 4 ++-- tests/neg/leak-problem-unboxed.scala | 6 +++--- tests/neg/leak-problem.scala | 6 +++--- tests/pos-custom-args/captures/Buffer.scala | 2 +- tests/pos-custom-args/captures/dep-reach.scala | 6 +++--- .../captures/gears-problem.scala | 4 ++-- tests/pos-custom-args/captures/path-use.scala | 5 +++-- tests/pos-custom-args/captures/reaches.scala | 4 ++-- tests/pos/Buffer.scala | 2 +- tests/pos/cc-poly-source-capability.scala | 4 ++-- tests/pos/cc-poly-source.scala | 4 ++-- tests/pos/gears-probem-1.scala | 4 ++-- tests/pos/i18699.scala | 4 ++-- tests/pos/reach-capability.scala | 4 ++-- tests/pos/reach-problem.scala | 4 ++-- 26 files changed, 65 insertions(+), 64 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 788da0dbcc57..5b4105a0b778 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -392,7 +392,7 @@ class CheckCaptures extends Recheck, SymTransformer: def checkUseDeclared(c: CaptureRef, env: Env) = c.pathRoot match - case ref: NamedType if !ref.symbol.hasAnnotation(defn.UnboxAnnot) => + case ref: NamedType if !ref.symbol.hasAnnotation(defn.UseAnnot) => val what = if ref.isType then "Capture set parameter" else "Local reach capability" report.error( em"""$what $c leaks into capture scope of ${env.ownerString}. @@ -524,7 +524,7 @@ class CheckCaptures extends Recheck, SymTransformer: override def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = val paramInfosWithUses = funtpe.paramInfos.zipWithConserve(funtpe.paramNames): (formal, pname) => val paramOpt = meth.rawParamss.nestedFind(_.name == pname) - paramOpt.flatMap(_.getAnnotation(defn.UnboxAnnot)) match + paramOpt.flatMap(_.getAnnotation(defn.UseAnnot)) match case Some(ann) => AnnotatedType(formal, ann) case _ => formal funtpe.derivedLambdaType(paramInfos = paramInfosWithUses) @@ -572,7 +572,7 @@ class CheckCaptures extends Recheck, SymTransformer: def recheckArg(arg: Tree, formal: Type)(using Context): Type = val argType = recheck(arg, formal) formal match - case AnnotatedType(formal1, ann) if ann.symbol == defn.UnboxAnnot => + case AnnotatedType(formal1, ann) if ann.symbol == defn.UseAnnot => capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}") markFree(argType.deepCaptureSet, arg.srcPos) case _ => @@ -586,13 +586,13 @@ class CheckCaptures extends Recheck, SymTransformer: * --------------------- * E |- f(a): Tr^C * - * If the function `f` does not have an `@unboxed` parameter, then + * If the function `f` does not have an `@use` parameter, then * any unboxing it does would be charged to the environment of the function * so they have to appear in Cq. Since any capabilities of the result of the * application must already be present in the application, an upper * approximation of the result capture set is Cq \union Ca, where `Ca` * is the capture set of the argument. - * If the function `f` does have an `@unboxed` parameter, then it could in addition + * If the function `f` does have an `@use` parameter, then it could in addition * unbox reach capabilities over its formal parameter. Therefore, the approximation * would be `Cq \union dcs(Ca)` instead. * If the approximation is known to subcapture the declared result Cr, we pick it for C @@ -605,7 +605,7 @@ class CheckCaptures extends Recheck, SymTransformer: val argCaptures = for (argType, formal) <- argTypes.lazyZip(funType.paramInfos) yield formal match - case AnnotatedType(_, ann) if ann.symbol == defn.UnboxAnnot => argType.deepCaptureSet + case AnnotatedType(_, ann) if ann.symbol == defn.UseAnnot => argType.deepCaptureSet case _ => argType.captureSet appType match case appType @ CapturingType(appType1, refs) @@ -1384,16 +1384,16 @@ class CheckCaptures extends Recheck, SymTransformer: override def checkInheritedTraitParameters: Boolean = false - /** Check that overrides don't change the @unbox status of their parameters */ + /** Check that overrides don't change the @use status of their parameters */ override def additionalChecks(member: Symbol, other: Symbol)(using Context): Unit = for (params1, params2) <- member.rawParamss.lazyZip(other.rawParamss) (param1, param2) <- params1.lazyZip(params2) do - if param1.hasAnnotation(defn.UnboxAnnot) != param2.hasAnnotation(defn.UnboxAnnot) then + if param1.hasAnnotation(defn.UseAnnot) != param2.hasAnnotation(defn.UseAnnot) then report.error( OverrideError( - i"has a parameter ${param1.name} with different @unbox status than the corresponding parameter in the overridden definition", + i"has a parameter ${param1.name} with different @use status than the corresponding parameter in the overridden definition", self, member, other, self.memberInfo(member), self.memberInfo(other) ), if member.owner == clazz then member.srcPos else clazz.srcPos diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 0195a4ddbf34..1e36cb6f22db 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1057,12 +1057,12 @@ class Definitions { @tu lazy val ExperimentalAnnot: ClassSymbol = requiredClass("scala.annotation.experimental") @tu lazy val ThrowsAnnot: ClassSymbol = requiredClass("scala.throws") @tu lazy val TransientAnnot: ClassSymbol = requiredClass("scala.transient") - @tu lazy val UnboxAnnot: ClassSymbol = requiredClass("scala.caps.unbox") @tu lazy val UncheckedAnnot: ClassSymbol = requiredClass("scala.unchecked") @tu lazy val UncheckedStableAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedStable") @tu lazy val UncheckedVarianceAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedVariance") @tu lazy val UncheckedCapturesAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedCaptures") @tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.untrackedCaptures") + @tu lazy val UseAnnot: ClassSymbol = requiredClass("scala.caps.use") @tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") @tu lazy val BeanGetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanGetter") @tu lazy val BeanSetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanSetter") diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala index 9911ef920116..6c7610e410c0 100644 --- a/library/src/scala/caps.scala +++ b/library/src/scala/caps.scala @@ -55,7 +55,7 @@ import annotation.{experimental, compileTimeOnly, retainsCap} /** This should go into annotations. For now it is here, so that we * can experiment with it quickly between minor releases */ - final class unbox extends annotation.StaticAnnotation + final class use extends annotation.StaticAnnotation object unsafe: diff --git a/tests/neg-custom-args/captures/delayedRunops.scala b/tests/neg-custom-args/captures/delayedRunops.scala index 02786bcb836a..191118fa19c9 100644 --- a/tests/neg-custom-args/captures/delayedRunops.scala +++ b/tests/neg-custom-args/captures/delayedRunops.scala @@ -1,12 +1,12 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use // ok - def runOps(@unbox ops: List[() => Unit]): Unit = + def runOps(@use ops: List[() => Unit]): Unit = ops.foreach(op => op()) // ok - def delayedRunOps(@unbox ops: List[() => Unit]): () ->{ops*} Unit = // @unbox should not be necessary in the future + def delayedRunOps(@use ops: List[() => Unit]): () ->{ops*} Unit = // @use should not be necessary in the future () => runOps(ops) // unsound: impure operation pretended pure diff --git a/tests/neg-custom-args/captures/i15749a.scala b/tests/neg-custom-args/captures/i15749a.scala index 57fca27fae66..d3c1fce13322 100644 --- a/tests/neg-custom-args/captures/i15749a.scala +++ b/tests/neg-custom-args/captures/i15749a.scala @@ -1,5 +1,5 @@ import caps.cap -import caps.unbox +import caps.use class Unit object u extends Unit @@ -18,7 +18,7 @@ def test = def force[A](thunk: Unit ->{cap} A): A = thunk(u) - def forceWrapper[A](@unbox mx: Wrapper[Unit ->{cap} A]): Wrapper[A] = + def forceWrapper[A](@use mx: Wrapper[Unit ->{cap} A]): Wrapper[A] = // Γ ⊢ mx: Wrapper[□ {cap} Unit => A] // `force` should be typed as ∀(□ {cap} Unit -> A) A, but it can not strictMap[Unit ->{mx*} A, A](mx)(t => force[A](t)) // error // should work diff --git a/tests/neg-custom-args/captures/i21614.scala b/tests/neg-custom-args/captures/i21614.scala index a5ed25d818a5..69aef446e9e2 100644 --- a/tests/neg-custom-args/captures/i21614.scala +++ b/tests/neg-custom-args/captures/i21614.scala @@ -1,12 +1,12 @@ import language.experimental.captureChecking import caps.Capability -import caps.unbox +import caps.use trait File extends Capability class Logger(f: File^) extends Capability // <- will work if we remove the extends clause -def mkLoggers1[F <: File^](@unbox files: List[F]): List[Logger^] = +def mkLoggers1[F <: File^](@use files: List[F]): List[Logger^] = files.map((f: F) => new Logger(f)) // error, Q: can we make this pass (see #19076)? -def mkLoggers2(@unbox files: List[File^]): List[Logger^] = +def mkLoggers2(@use files: List[File^]): List[Logger^] = files.map(new Logger(_)) // error, Q: can we improve the error message? diff --git a/tests/neg-custom-args/captures/leak-problem-2.scala b/tests/neg-custom-args/captures/leak-problem-2.scala index 08a3a6c2d9ca..8ca298dbdd1e 100644 --- a/tests/neg-custom-args/captures/leak-problem-2.scala +++ b/tests/neg-custom-args/captures/leak-problem-2.scala @@ -2,7 +2,7 @@ import language.experimental.captureChecking trait Source[+T] -def race[T](@caps.unbox sources: Seq[Source[T]^]): Source[T]^{sources*} = ??? +def race[T](@caps.use sources: Seq[Source[T]^]): Source[T]^{sources*} = ??? def raceTwo[T](src1: Source[T]^, src2: Source[T]^): Source[T]^{} = race(Seq(src1, src2)) // error diff --git a/tests/neg-custom-args/captures/reaches.scala b/tests/neg-custom-args/captures/reaches.scala index 4db8d0df74d8..066c40031553 100644 --- a/tests/neg-custom-args/captures/reaches.scala +++ b/tests/neg-custom-args/captures/reaches.scala @@ -1,4 +1,4 @@ -import caps.unbox +import caps.use class File: def write(): Unit = ??? @@ -11,7 +11,7 @@ class Ref[T](init: T): def get: T = x def set(y: T) = { x = y } -def runAll0(@unbox xs: List[Proc]): Unit = +def runAll0(@use xs: List[Proc]): Unit = var cur: List[() ->{xs*} Unit] = xs while cur.nonEmpty do val next: () ->{xs*} Unit = cur.head @@ -21,7 +21,7 @@ def runAll0(@unbox xs: List[Proc]): Unit = usingFile: f => cur = (() => f.write()) :: Nil // error -def runAll1(@unbox xs: List[Proc]): Unit = +def runAll1(@use xs: List[Proc]): Unit = val cur = Ref[List[() ->{xs*} Unit]](xs) // OK, by revised VAR while cur.get.nonEmpty do val next: () ->{xs*} Unit = cur.get.head @@ -78,5 +78,5 @@ def compose1[A, B, C](f: A => B, g: B => C): A ->{f, g} C = def mapCompose[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] = ps.map((x, y) => compose1(x, y)) // error // error -def mapCompose2[A](@unbox ps: List[(A => A, A => A)]): List[A ->{ps*} A] = +def mapCompose2[A](@use ps: List[(A => A, A => A)]): List[A ->{ps*} A] = ps.map((x, y) => compose1(x, y)) diff --git a/tests/neg-custom-args/captures/spread-problem.scala b/tests/neg-custom-args/captures/spread-problem.scala index 579c7817b9c1..75f3a615cde8 100644 --- a/tests/neg-custom-args/captures/spread-problem.scala +++ b/tests/neg-custom-args/captures/spread-problem.scala @@ -2,7 +2,7 @@ import language.experimental.captureChecking trait Source[+T] -def race[T](@caps.unbox sources: (Source[T]^)*): Source[T]^{sources*} = ??? +def race[T](@caps.use sources: (Source[T]^)*): Source[T]^{sources*} = ??? def raceTwo[T](src1: Source[T]^, src2: Source[T]^): Source[T]^{} = race(Seq(src1, src2)*) // error diff --git a/tests/neg-custom-args/captures/unbox-overrides.check b/tests/neg-custom-args/captures/unbox-overrides.check index b9a3be7bffbc..dbffc164b5c5 100644 --- a/tests/neg-custom-args/captures/unbox-overrides.check +++ b/tests/neg-custom-args/captures/unbox-overrides.check @@ -2,20 +2,20 @@ 8 | def foo(x: C): C // error | ^ |error overriding method foo in trait A of type (x: C): C; - | method foo of type (x: C): C has a parameter x with different @unbox status than the corresponding parameter in the overridden definition + | method foo of type (x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition | | longer explanation available when compiling with `-explain` -- [E164] Declaration Error: tests/neg-custom-args/captures/unbox-overrides.scala:9:6 ---------------------------------- -9 | def bar(@unbox x: C): C // error +9 | def bar(@use x: C): C // error | ^ |error overriding method bar in trait A of type (x: C): C; - | method bar of type (x: C): C has a parameter x with different @unbox status than the corresponding parameter in the overridden definition + | method bar of type (x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition | | longer explanation available when compiling with `-explain` -- [E164] Declaration Error: tests/neg-custom-args/captures/unbox-overrides.scala:15:15 -------------------------------- 15 |abstract class C extends A[C], B2 // error | ^ |error overriding method foo in trait A of type (x: C): C; - | method foo in trait B2 of type (x: C): C has a parameter x with different @unbox status than the corresponding parameter in the overridden definition + | method foo in trait B2 of type (x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/unbox-overrides.scala b/tests/neg-custom-args/captures/unbox-overrides.scala index 5abb5013bfbe..6164ca274eaf 100644 --- a/tests/neg-custom-args/captures/unbox-overrides.scala +++ b/tests/neg-custom-args/captures/unbox-overrides.scala @@ -1,15 +1,15 @@ -import caps.unbox +import caps.use trait A[X]: - def foo(@unbox x: X): X + def foo(@use x: X): X def bar(x: X): X trait B extends A[C]: def foo(x: C): C // error - def bar(@unbox x: C): C // error + def bar(@use x: C): C // error trait B2: def foo(x: C): C - def bar(@unbox x: C): C + def bar(@use x: C): C abstract class C extends A[C], B2 // error diff --git a/tests/neg/i20503.scala b/tests/neg/i20503.scala index 3fb0573f6c2f..828b6ce71137 100644 --- a/tests/neg/i20503.scala +++ b/tests/neg/i20503.scala @@ -1,5 +1,5 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use class List[+A]: def head: A = ??? @@ -8,7 +8,7 @@ class List[+A]: def foreach[U](f: A => U): Unit = ??? def nonEmpty: Boolean = ??? -def runOps(@unbox ops: List[() => Unit]): Unit = +def runOps(@use ops: List[() => Unit]): Unit = // See i20156, due to limitation in expressiveness of current system, // we could map over the list of impure elements. OK with existentials. ops.foreach(op => op()) diff --git a/tests/neg/leak-problem-unboxed.scala b/tests/neg/leak-problem-unboxed.scala index 7de3d84bfcca..aedd7c889112 100644 --- a/tests/neg/leak-problem-unboxed.scala +++ b/tests/neg/leak-problem-unboxed.scala @@ -1,5 +1,5 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use // Some capabilities that should be used locally trait Async: @@ -9,12 +9,12 @@ def usingAsync[X](op: Async^ => X): X = ??? case class Box[+T](get: T) -def useBoxedAsync(@unbox x: Box[Async^]): Unit = +def useBoxedAsync(@use x: Box[Async^]): Unit = val t0 = x val t1 = t0.get // ok t1.read() -def useBoxedAsync1(@unbox x: Box[Async^]): Unit = x.get.read() // ok +def useBoxedAsync1(@use x: Box[Async^]): Unit = x.get.read() // ok def test(): Unit = diff --git a/tests/neg/leak-problem.scala b/tests/neg/leak-problem.scala index dea2496d6c77..c842280c0587 100644 --- a/tests/neg/leak-problem.scala +++ b/tests/neg/leak-problem.scala @@ -1,5 +1,5 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use // Some capabilities that should be used locally trait Async: @@ -17,12 +17,12 @@ def useBoxedAsync(x: Box[Async^]): Unit = def useBoxedAsync1(x: Box[Async^]): Unit = x.get.read() // error def test(): Unit = - def useBoxedAsync(@unbox x: Box[Async^]): Unit = + def useBoxedAsync(@use x: Box[Async^]): Unit = val t0 = x val t1 = t0.get t1.read() - def useBoxedAsync1(@unbox x: Box[Async^]): Unit = x.get.read() + def useBoxedAsync1(@use x: Box[Async^]): Unit = x.get.read() val xs: Box[Async^] = ??? val xsLambda = () => useBoxedAsync(xs) // error diff --git a/tests/pos-custom-args/captures/Buffer.scala b/tests/pos-custom-args/captures/Buffer.scala index 2412e5b388ca..9ecd51ffa62a 100644 --- a/tests/pos-custom-args/captures/Buffer.scala +++ b/tests/pos-custom-args/captures/Buffer.scala @@ -10,7 +10,7 @@ trait Buffer[A]: val s = 10 // capture checking: we need the copy since we box/unbox on g* on the next line // TODO: This looks fishy, need to investigate - // Alternative would be to mark `f` as @unbox. It's not inferred + // Alternative would be to mark `f` as @use. It's not inferred // since `^ appears in a function result, not under a box. val newElems = new Array[(IterableOnce[A]^{f})](s) var i = 0 diff --git a/tests/pos-custom-args/captures/dep-reach.scala b/tests/pos-custom-args/captures/dep-reach.scala index c81197aa738d..1ee6fc3d17f9 100644 --- a/tests/pos-custom-args/captures/dep-reach.scala +++ b/tests/pos-custom-args/captures/dep-reach.scala @@ -1,10 +1,10 @@ -import caps.unbox +import caps.use object Test: class C type Proc = () => Unit def f(c: C^, d: C^): () ->{c, d} Unit = - def foo(@unbox xs: Proc*): () ->{xs*} Unit = + def foo(@use xs: Proc*): () ->{xs*} Unit = xs.head val a: () ->{c} Unit = () => () val b: () ->{d} Unit = () => () @@ -13,7 +13,7 @@ object Test: def g(c: C^, d: C^): () ->{c, d} Unit = - def foo(@unbox xs: Seq[() => Unit]): () ->{xs*} Unit = + def foo(@use xs: Seq[() => Unit]): () ->{xs*} Unit = xs.head val a: () ->{c} Unit = () => () diff --git a/tests/pos-custom-args/captures/gears-problem.scala b/tests/pos-custom-args/captures/gears-problem.scala index 74f108f93e80..c6ef72bc421e 100644 --- a/tests/pos-custom-args/captures/gears-problem.scala +++ b/tests/pos-custom-args/captures/gears-problem.scala @@ -1,5 +1,5 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use trait Future[+T]: def await: T @@ -11,7 +11,7 @@ class Collector[T](val futures: Seq[Future[T]^]): val results: Channel[Future[T]^{futures*}] = ??? end Collector -extension [T](@unbox fs: Seq[Future[T]^]) +extension [T](@use fs: Seq[Future[T]^]) def awaitAll = val collector: Collector[T]{val futures: Seq[Future[T]^{fs*}]} = Collector(fs) diff --git a/tests/pos-custom-args/captures/path-use.scala b/tests/pos-custom-args/captures/path-use.scala index 181afc41bcc2..629fa04315a7 100644 --- a/tests/pos-custom-args/captures/path-use.scala +++ b/tests/pos-custom-args/captures/path-use.scala @@ -1,5 +1,5 @@ import language.experimental.namedTuples -import caps.unbox +import caps.use class IO @@ -9,7 +9,7 @@ class C(val f: IO^): type Proc = () => Unit def test(io: IO^) = - def test1(@unbox c: C { val f: IO^{io}}^{io}) = + def test1(@use c: C { val f: IO^{io}}^{io}) = val f = () => println(c.f) val _: () ->{c.f} Unit = f @@ -19,3 +19,4 @@ def test(io: IO^) = val g = () => println(c.procs.head) val _: () ->{c.procs*} Unit = g test1(C(io)) + diff --git a/tests/pos-custom-args/captures/reaches.scala b/tests/pos-custom-args/captures/reaches.scala index ab0da9b67d18..cbe88e60020b 100644 --- a/tests/pos-custom-args/captures/reaches.scala +++ b/tests/pos-custom-args/captures/reaches.scala @@ -1,4 +1,4 @@ -import caps.unbox +import caps.use class C def f(xs: List[C^]) = @@ -22,7 +22,7 @@ extension [A](x: A) def :: (xs: List[A]): List[A] = ??? object Nil extends List[Nothing] -def runAll(@unbox xs: List[Proc]): Unit = +def runAll(@use xs: List[Proc]): Unit = var cur: List[() ->{xs*} Unit] = xs // OK, by revised VAR while cur.nonEmpty do val next: () ->{xs*} Unit = cur.head diff --git a/tests/pos/Buffer.scala b/tests/pos/Buffer.scala index 2412e5b388ca..9ecd51ffa62a 100644 --- a/tests/pos/Buffer.scala +++ b/tests/pos/Buffer.scala @@ -10,7 +10,7 @@ trait Buffer[A]: val s = 10 // capture checking: we need the copy since we box/unbox on g* on the next line // TODO: This looks fishy, need to investigate - // Alternative would be to mark `f` as @unbox. It's not inferred + // Alternative would be to mark `f` as @use. It's not inferred // since `^ appears in a function result, not under a box. val newElems = new Array[(IterableOnce[A]^{f})](s) var i = 0 diff --git a/tests/pos/cc-poly-source-capability.scala b/tests/pos/cc-poly-source-capability.scala index 3b6c0bde1398..363f261dadc1 100644 --- a/tests/pos/cc-poly-source-capability.scala +++ b/tests/pos/cc-poly-source-capability.scala @@ -1,7 +1,7 @@ import language.experimental.captureChecking import annotation.experimental import caps.{CapSet, Capability} -import caps.unbox +import caps.use @experimental object Test: @@ -18,7 +18,7 @@ import caps.unbox def allListeners: Set[Listener^{X^}] = listeners - def test1(async1: Async, @unbox others: List[Async]) = + def test1(async1: Async, @use others: List[Async]) = val src = Source[CapSet^{async1, others*}] val lst1 = listener(async1) val lsts = others.map(listener) diff --git a/tests/pos/cc-poly-source.scala b/tests/pos/cc-poly-source.scala index 4cfbbaa06936..2de5c6d67340 100644 --- a/tests/pos/cc-poly-source.scala +++ b/tests/pos/cc-poly-source.scala @@ -1,7 +1,7 @@ import language.experimental.captureChecking import annotation.experimental import caps.{CapSet, Capability} -import caps.unbox +import caps.use @experimental object Test: @@ -25,7 +25,7 @@ import caps.unbox val ls = src.allListeners val _: Set[Listener^{lbl1, lbl2}] = ls - def test2(@unbox lbls: List[Label^]) = + def test2(@use lbls: List[Label^]) = def makeListener(lbl: Label^): Listener^{lbl} = ??? val listeners = lbls.map(makeListener) val src = Source[CapSet^{lbls*}] diff --git a/tests/pos/gears-probem-1.scala b/tests/pos/gears-probem-1.scala index ab71616b72fc..d363515afece 100644 --- a/tests/pos/gears-probem-1.scala +++ b/tests/pos/gears-probem-1.scala @@ -1,5 +1,5 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use trait Future[+T]: def await: T @@ -17,7 +17,7 @@ class Result[+T, +E]: case class Err[+E](e: E) extends Result[Nothing, E] case class Ok[+T](x: T) extends Result[T, Nothing] -extension [T](@unbox fs: Seq[Future[T]^]) +extension [T](@use fs: Seq[Future[T]^]) def awaitAll = val collector//: Collector[T]{val futures: Seq[Future[T]^{fs*}]} = Collector(fs) diff --git a/tests/pos/i18699.scala b/tests/pos/i18699.scala index 1937d7dca8c5..5a0a9357f774 100644 --- a/tests/pos/i18699.scala +++ b/tests/pos/i18699.scala @@ -1,9 +1,9 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use trait Cap: def use: Int = 42 -def test2(@unbox cs: List[Cap^]): Unit = +def test2(@use cs: List[Cap^]): Unit = val t0: Cap^{cs*} = cs.head // error var t1: Cap^{cs*} = cs.head // error diff --git a/tests/pos/reach-capability.scala b/tests/pos/reach-capability.scala index 50ea479ec3c1..08e82a1dabe9 100644 --- a/tests/pos/reach-capability.scala +++ b/tests/pos/reach-capability.scala @@ -1,7 +1,7 @@ import language.experimental.captureChecking import annotation.experimental import caps.Capability -import caps.unbox +import caps.use @experimental object Test2: @@ -12,7 +12,7 @@ import caps.unbox class Listener - def test2(@unbox lbls: List[Label]) = + def test2(@use lbls: List[Label]) = def makeListener(lbl: Label): Listener^{lbl} = ??? val listeners = lbls.map(makeListener) // should work diff --git a/tests/pos/reach-problem.scala b/tests/pos/reach-problem.scala index d6b7b79011a6..f36a4af14ad2 100644 --- a/tests/pos/reach-problem.scala +++ b/tests/pos/reach-problem.scala @@ -1,11 +1,11 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use class Box[T](items: Seq[T^]): def getOne: T^{items*} = ??? object Box: - def getOne[T](@unbox items: Seq[T^]): T^{items*} = + def getOne[T](@use items: Seq[T^]): T^{items*} = val bx = Box(items) bx.getOne /* From 7c2a76b943c34502493adbd4375ebbe713b79c69 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 30 Oct 2024 18:34:09 +0100 Subject: [PATCH 030/202] Make sure we don't lose `erased` in method types on Setup --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 5 ++- .../src/dotty/tools/dotc/core/Types.scala | 34 +++++++++++-------- .../captures/erased-methods.scala | 20 +++++++++++ 3 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 tests/pos-custom-args/captures/erased-methods.scala diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index a64703bf059e..4df157ed42e7 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -535,13 +535,16 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // substitute `x.f.type`, `x` becomes a `TermParamRef`. But the new method // type is still under initialization and `paramInfos` is still `null`, // so the new `NamedType` will not have a denoation. + def adaptedInfo(psym: Symbol, info: mt.PInfo): mt.PInfo = mt.companion match + case mtc: MethodTypeCompanion => mtc.adaptParamInfo(psym, info).asInstanceOf[mt.PInfo] + case _ => info mt.companion(mt.paramNames)( mt1 => if !paramSignatureChanges && !mt.isParamDependent && prevLambdas.isEmpty then mt.paramInfos else val subst = SubstParams(psyms :: prevPsymss, mt1 :: prevLambdas) - psyms.map(psym => subst(psym.nextInfo).asInstanceOf[mt.PInfo]), + psyms.map(psym => adaptedInfo(psym, subst(psym.nextInfo).asInstanceOf[mt.PInfo])), mt1 => integrateRT(mt.resType, psymss.tail, resType, psyms :: prevPsymss, mt1 :: prevLambdas) ) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e497c541166c..62bbd2be2773 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4175,24 +4175,28 @@ object Types extends TypeUtils { * - wrap types of parameters that have an @allowConversions annotation with Into[_] */ def fromSymbols(params: List[Symbol], resultType: Type)(using Context): MethodType = + apply(params.map(_.name.asTermName))( + tl => params.map(p => tl.integrate(params, adaptParamInfo(p))), + tl => tl.integrate(params, resultType)) + + /** Adapt info of parameter symbol to be integhrated into corresponding MethodType + * using the scheme described in `fromSymbols`. + */ + def adaptParamInfo(param: Symbol, pinfo: Type)(using Context): Type = def addAnnotation(tp: Type, cls: ClassSymbol, param: Symbol): Type = tp match case ExprType(resType) => ExprType(addAnnotation(resType, cls, param)) case _ => AnnotatedType(tp, Annotation(cls, param.span)) - - def paramInfo(param: Symbol) = - var paramType = param.info - .annotatedToRepeated - .mapIntoAnnot(defn.IntoAnnot, defn.IntoParamAnnot) - if param.is(Inline) then - paramType = addAnnotation(paramType, defn.InlineParamAnnot, param) - if param.is(Erased) then - paramType = addAnnotation(paramType, defn.ErasedParamAnnot, param) - paramType - - apply(params.map(_.name.asTermName))( - tl => params.map(p => tl.integrate(params, paramInfo(p))), - tl => tl.integrate(params, resultType)) - end fromSymbols + var paramType = pinfo + .annotatedToRepeated + .mapIntoAnnot(defn.IntoAnnot, defn.IntoParamAnnot) + if param.is(Inline) then + paramType = addAnnotation(paramType, defn.InlineParamAnnot, param) + if param.is(Erased) then + paramType = addAnnotation(paramType, defn.ErasedParamAnnot, param) + paramType + + def adaptParamInfo(param: Symbol)(using Context): Type = + adaptParamInfo(param, param.info) def apply(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(using Context): MethodType = checkValid(unique(new CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self))) diff --git a/tests/pos-custom-args/captures/erased-methods.scala b/tests/pos-custom-args/captures/erased-methods.scala new file mode 100644 index 000000000000..911c779e08e5 --- /dev/null +++ b/tests/pos-custom-args/captures/erased-methods.scala @@ -0,0 +1,20 @@ +import language.experimental.saferExceptions +import language.experimental.erasedDefinitions +import language.experimental.captureChecking + +class Ex1 extends Exception("Ex1") +class Ex2 extends Exception("Ex2") +class Ex3 extends Exception("Ex3") + +def foo8a(i: Int) = + (erased xx1: CanThrow[Ex2]^) ?=> throw new Ex2 + +def foo9a(i: Int) + : (erased x$0: CanThrow[Ex3]^) + ?=> (erased x$1: CanThrow[Ex2]^) + ?=> (erased x$2: CanThrow[Ex1]^) + ?=> Unit + = (erased x$1: CanThrow[Ex3]^) + ?=> (erased x$2: CanThrow[Ex2]^) + ?=> (erased x$3: CanThrow[Ex1]^) + ?=> throw new Ex3 From 2747f0329dadee5ec67f7db30c1dad8f2f1796d3 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 30 Oct 2024 19:48:25 +0100 Subject: [PATCH 031/202] Drop curried use scheme Drop the scheme where we only charge the last arrow of a curried lambda with elements used in the body. On the one hand, this is unsound without compensation measures (like, restricting to reach capabilities, or taking all capture sets of a named curried function as the underlying reference). On the other hand, this should be generalized to all closures and anonymous functions forming the right hand sides of methods. --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 3 +++ .../src/dotty/tools/dotc/cc/CheckCaptures.scala | 9 ++++++--- compiler/src/dotty/tools/dotc/cc/Setup.scala | 2 +- .../captures/curried-closures.scala | 2 +- tests/neg-custom-args/captures/i21620.check | 13 +++++++++++++ tests/neg-custom-args/captures/i21620.scala | 11 +++++++++++ tests/neg-custom-args/captures/leaked-curried.check | 6 ++++-- tests/pos-custom-args/captures/i21620.scala | 11 ----------- 8 files changed, 39 insertions(+), 18 deletions(-) rename tests/{pos-custom-args => neg-custom-args}/captures/curried-closures.scala (91%) delete mode 100644 tests/pos-custom-args/captures/i21620.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index f9c33695e9fe..e4d80b7f6982 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -40,6 +40,9 @@ object ccConfig: */ inline val handleEtaExpansionsSpecially = false + /** If enabled we drop inner uses in outer arrows of a curried function */ + inline val DropOuterUsesInCurried = false + /** If true, use existential capture set variables */ def useExistentials(using Context) = Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.5`) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 5b4105a0b778..902965c13476 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -36,7 +36,8 @@ object CheckCaptures: case NestedInOwner // environment is a temporary one nested in the owner's environment, // and does not have a different actual owner symbol // (this happens when doing box adaptation). - case ClosureResult // environment is for the result of a closure + case ClosureResult // environment is for the result of a closure, + // used only under ccConfig.DropOuterUsesInCurried case Boxed // environment is inside a box (in which case references are not counted) /** A class describing environments. @@ -183,7 +184,9 @@ object CheckCaptures: if ccConfig.useSealed then check.traverse(tp) end disallowRootCapabilitiesIn - /** Attachment key for bodies of closures, provided they are values */ + /** Attachment key for bodies of closures, provided they are values. + * Used only under ccConfig.DropOuterUsesInCurried + */ val ClosureBodyValue = Property.Key[Unit] /** A prototype that indicates selection with an immutable value */ @@ -733,7 +736,7 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheckClosureBlock(mdef: DefDef, expr: Closure, pt: Type)(using Context): Type = mdef.rhs match - case rhs @ closure(_, _, _) => + case rhs @ closure(_, _, _) if ccConfig.DropOuterUsesInCurried => // In a curried closure `x => y => e` don't leak capabilities retained by // the second closure `y => e` into the first one. This is an approximation // of the CC rule which says that a closure contributes captures to its diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 4df157ed42e7..3593e00ad8b8 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -302,7 +302,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => res ) val fntpe = defn.PolyFunctionOf(mt) - if !encl.isEmpty && resDecomposed.isEmpty then + if !encl.isEmpty && (!ccConfig.DropOuterUsesInCurried || resDecomposed.isEmpty) then val cs = CaptureSet(encl.map(_.paramRefs.head)*) CapturingType(fntpe, cs, boxed = false) else fntpe diff --git a/tests/pos-custom-args/captures/curried-closures.scala b/tests/neg-custom-args/captures/curried-closures.scala similarity index 91% rename from tests/pos-custom-args/captures/curried-closures.scala rename to tests/neg-custom-args/captures/curried-closures.scala index 262dd4b66b92..426f0df85022 100644 --- a/tests/pos-custom-args/captures/curried-closures.scala +++ b/tests/neg-custom-args/captures/curried-closures.scala @@ -30,5 +30,5 @@ def Test4(g: OutputStream^) = val _: (f: OutputStream^) ->{} Int ->{f} Unit = later val later2 = () => (y: Int) => xs.foreach(x => g.write(x + y)) - val _: () ->{} Int ->{g} Unit = later2 + val _: () ->{} Int ->{g} Unit = later2 // error, inferred type is () ->{later2} Int ->{g} Unit diff --git a/tests/neg-custom-args/captures/i21620.check b/tests/neg-custom-args/captures/i21620.check index 3a09ba978574..ddfcdafab36f 100644 --- a/tests/neg-custom-args/captures/i21620.check +++ b/tests/neg-custom-args/captures/i21620.check @@ -4,6 +4,12 @@ | A pure expression does nothing in statement position | | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg-custom-args/captures/i21620.scala:14:4 ------------------------------------ +14 | x + | ^ + | A pure expression does nothing in statement position + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21620.scala:9:31 ---------------------------------------- 9 | val _: () -> () ->{x} Unit = f // error | ^ @@ -11,3 +17,10 @@ | Required: () -> () ->{x} Unit | | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21620.scala:20:33 --------------------------------------- +20 | val _: () ->{} () ->{x} Unit = f // error, but could be OK + | ^ + | Found: () ->{f} () ->{x} Unit + | Required: () -> () ->{x} Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21620.scala b/tests/neg-custom-args/captures/i21620.scala index a21a41a10863..3b7ba5a9fd5a 100644 --- a/tests/neg-custom-args/captures/i21620.scala +++ b/tests/neg-custom-args/captures/i21620.scala @@ -8,3 +8,14 @@ def test(x: C^) = () => foo() val _: () -> () ->{x} Unit = f // error () + +def test2(x: C^) = + def foo() = + x + () + val f = () => + // println() // uncomenting would give an error, but with + // a different way of handling curried functions should be OK + () => foo() + val _: () ->{} () ->{x} Unit = f // error, but could be OK + () diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index 3f0a9800a4ec..63359e7bb8b8 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,8 +1,10 @@ -- Error: tests/neg-custom-args/captures/leaked-curried.scala:14:20 ---------------------------------------------------- 14 | () => () => io // error | ^^ - |(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Fuzz + | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> () ->{io} (ex$7: caps.Exists) -> Cap^{ex$7} -- Error: tests/neg-custom-args/captures/leaked-curried.scala:17:20 ---------------------------------------------------- 17 | () => () => io // error | ^^ - |(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Foo + | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> () ->{io} (ex$15: caps.Exists) -> Cap^{ex$15} diff --git a/tests/pos-custom-args/captures/i21620.scala b/tests/pos-custom-args/captures/i21620.scala deleted file mode 100644 index b2c382aa4c75..000000000000 --- a/tests/pos-custom-args/captures/i21620.scala +++ /dev/null @@ -1,11 +0,0 @@ -class C -def test(x: C^) = - def foo() = - x - () - val f = () => - // println() // uncomenting would give an error, but with - // a different way of handling curried functions should be OK - () => foo() - val _: () -> () ->{x} Unit = f - () From 17a4a8c9f1d66a2f9333f7224d94abe9977ecac4 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 31 Oct 2024 11:45:57 +0100 Subject: [PATCH 032/202] Stop markFree at nested methods Use sets of nested methods are anyway charged on call. --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 12 ++++++++++-- compiler/src/dotty/tools/dotc/cc/Setup.scala | 12 ++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 902965c13476..0cd795b018f2 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -365,6 +365,12 @@ class CheckCaptures extends Recheck, SymTransformer: else i"\nof the enclosing ${owner.showLocated}" + def isOfNestedMethod(env: Env | Null)(using Context) = + env != null + && env.owner.is(Method) + && env.owner.owner.isTerm + && !env.owner.isAnonymousFunction + /** Include `sym` in the capture sets of all enclosing environments nested in the * the environment in which `sym` is defined. */ @@ -377,7 +383,8 @@ class CheckCaptures extends Recheck, SymTransformer: if env.isOpen && env.owner != sym.enclosure then capt.println(i"Mark $sym with cs ${ref.captureSet} free in ${env.owner}") checkElem(ref, env.captured, pos, provenance(env)) - recur(nextEnvToCharge(env, _.owner != sym.enclosure)) + if !isOfNestedMethod(env) then + recur(nextEnvToCharge(env, _.owner != sym.enclosure)) recur(curEnv) /** Make sure (projected) `cs` is a subset of the capture sets of all enclosing @@ -438,7 +445,8 @@ class CheckCaptures extends Recheck, SymTransformer: isVisible checkSubset(included, env.captured, pos, provenance(env)) capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") - recur(included, nextEnvToCharge(env, !_.owner.isStaticOwner)) + if !isOfNestedMethod(env) then + recur(included, nextEnvToCharge(env, !_.owner.isStaticOwner)) recur(cs, curEnv) end markFree diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 3593e00ad8b8..f3ca15a8634d 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -23,8 +23,20 @@ import dotty.tools.dotc.util.NoSourcePosition /** Operations accessed from CheckCaptures */ trait SetupAPI: type DefRecheck = (tpd.ValOrDefDef, Symbol) => Context ?=> Type + + /** Setup procedure to run for each compilation unit + * @param tree the typed tree of the unit to check + * @param recheckDef the recheck method to run on completion of symbols with + * inferred (result-) types + */ def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit + + /** Symbol is a term member of a class that was not capture checked + * The info of these symbols is made fluid. + */ def isPreCC(sym: Symbol)(using Context): Boolean + + /** Check to do after the capture checking traversal */ def postCheck()(using Context): Unit object Setup: From 02cd565fa5bad80094bdd6768a0312d8534bc067 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 31 Oct 2024 11:50:51 +0100 Subject: [PATCH 033/202] Allow reach capabilities from within a nested closure --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 32 ++++++--- docs/_docs/internals/cc/use-design.md | 71 +++++++++++++++++++ .../captures/method-uses.scala | 30 ++++++++ 3 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 docs/_docs/internals/cc/use-design.md create mode 100644 tests/neg-custom-args/captures/method-uses.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 0cd795b018f2..844539f9a465 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -50,7 +50,8 @@ object CheckCaptures: owner: Symbol, kind: EnvKind, captured: CaptureSet, - outer0: Env | Null): + outer0: Env | Null, + nestedClosure: Symbol = NoSymbol): def outer = outer0.nn @@ -400,8 +401,10 @@ class CheckCaptures extends Recheck, SymTransformer: else !sym.isContainedIn(env.owner) - def checkUseDeclared(c: CaptureRef, env: Env) = - c.pathRoot match + def checkUseDeclared(c: CaptureRef, env: Env, lastEnv: Env | Null) = + if lastEnv != null && env.nestedClosure.exists && env.nestedClosure == lastEnv.owner then + () // access is from a nested closure, so it's OK + else c.pathRoot match case ref: NamedType if !ref.symbol.hasAnnotation(defn.UseAnnot) => val what = if ref.isType then "Capture set parameter" else "Local reach capability" report.error( @@ -409,7 +412,7 @@ class CheckCaptures extends Recheck, SymTransformer: |To allow this, the ${ref.symbol} should be declared with a @use annotation""", pos) case _ => - def recur(cs: CaptureSet, env: Env)(using Context): Unit = + def recur(cs: CaptureSet, env: Env, lastEnv: Env | Null)(using Context): Unit = if env.isOpen && !env.owner.isStaticOwner && !cs.isAlwaysEmpty then // Only captured references that are visible from the environment // should be included. @@ -423,7 +426,7 @@ class CheckCaptures extends Recheck, SymTransformer: c match case ReachCapability(c1) => if c1.isParamPath then - checkUseDeclared(c, env) + checkUseDeclared(c, env, lastEnv) else // When a reach capabilty x* where `x` is not a parameter goes out // of scope, we need to continue with `x`'s underlying deep capture set. @@ -438,16 +441,16 @@ class CheckCaptures extends Recheck, SymTransformer: capt.println(i"Widen reach $c to $underlying in ${env.owner}") underlying.disallowRootCapability: () => report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", pos) - recur(underlying, env) + recur(underlying, env, lastEnv) case c: TypeRef if c.isParamPath => - checkUseDeclared(c, env) + checkUseDeclared(c, env, lastEnv) case _ => isVisible checkSubset(included, env.captured, pos, provenance(env)) capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") if !isOfNestedMethod(env) then - recur(included, nextEnvToCharge(env, !_.owner.isStaticOwner)) - recur(cs, curEnv) + recur(included, nextEnvToCharge(env, !_.owner.isStaticOwner), env) + recur(cs, curEnv, null) end markFree /** Include references captured by the called method in the current environment stack */ @@ -843,10 +846,19 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Type = if Synthetics.isExcluded(sym) then sym.info else + // If rhs ends in a closure or anonymous class, the corresponding symbol + def nestedClosure(rhs: Tree)(using Context): Symbol = rhs match + case Closure(_, meth, _) => meth.symbol + case Apply(fn, _) if fn.symbol.isConstructor && fn.symbol.owner.isAnonymousClass => fn.symbol.owner + case Block(_, expr) => nestedClosure(expr) + case Inlined(_, _, expansion) => nestedClosure(expansion) + case Typed(expr, _) => nestedClosure(expr) + case _ => NoSymbol + val saved = curEnv val localSet = capturedVars(sym) if !localSet.isAlwaysEmpty then - curEnv = Env(sym, EnvKind.Regular, localSet, curEnv) + curEnv = Env(sym, EnvKind.Regular, localSet, curEnv, nestedClosure(tree.rhs)) // ctx with AssumedContains entries for each Contains parameter val bodyCtx = diff --git a/docs/_docs/internals/cc/use-design.md b/docs/_docs/internals/cc/use-design.md new file mode 100644 index 000000000000..4732f45a84ab --- /dev/null +++ b/docs/_docs/internals/cc/use-design.md @@ -0,0 +1,71 @@ + +Possible design: + + 1. Have @use annotation on type parameters and value parameters of regular methods + (not anonymous functions). + 2. In markFree, keep track whether a capture set variable or reach capability + is used directly in the method where it is defined, or in a nested context + (either unbound nested closure or unbound anonymous class). + 3. Disallow charging a reach capability `xs*` to the environment of the method where + `xs` is a parameter unless `xs` is declared `@use`. + 4. Analogously, disallow charging a capture set variable `C^` to the environment of the method where `C^` is a parameter unless `C^` is declared `@use`. + 5. When passing an argument to a `@use`d term parameter, charge the `dcs` of the argument type to the environments via markFree. + 6. When instantiating a `@use`d type parameter, charge the capture set of the argument + to the environments via markFree. + +It follows that we cannot refer to methods with @use term parameters as values. Indeed, +their eta expansion would produce an anonymous function that includes a reach capability of +its parameter in its use set, violating (3). + +Example: + +```scala + def runOps(@use ops: List[() => Unit]): Unit = ops.foreach(_()) +``` +Then `runOps` expands to +```scala +(xs: List[() => Unit]) => runOps(xs) +``` +Note that `xs` does not carry a `@use` since this is disallowed by (1) for anonymous functions. By (5), we charge the deep capture set of `xs`, which is `xs*` to the environment. By (3), this is actually disallowed. + +Now, if we express this with explicit capture set parameters we get: +```scala + def runOpsPoly[@use C^](ops: List[() ->{C^} Unit]): Unit = ops.foreach[C^](_()) +``` +Then `runOpsPoly` expands to `runOpsPoly[cs]` for some inferred capture set `cs`. And this expands to: +```scala +(xs: List[() ->{cs} Unit]) => runOpsPoly[cs](xs) +``` +Since `cs` is passed to the `@use` parameter of `runOpsPoly` it is charged +to the environment of the function body, so the type of the previous expression is +```scala +List[() ->{cs} Unit]) ->{cs} Unit +``` + +We can also use explicit capture set parameters to eta expand the first `runOps` manually: + +```scala +[C^] => (xs: List[() ->{C^} Unit]) => runOps(xs) + : [C^] -> List[() ->{C^} Unit] ->[C^] Unit +``` +Except that this currently runs afoul of the implementation restriction that polymorphic functions cannot wrap capturing functions. But that's a restriction we need to lift anyway. + +## `@use` inference + + - `@use` is implied for a term parameter `x` of a method if `x`'s type contains a boxed cap and `x` or `x*` is not referred to in the result type of the method. + + - `@use` is implied for a capture set parameter `C` of a method if `C` is not referred to in the result type of the method. + +If `@use` is implied, one can override to no use by giving an explicit use annotation +`@use(false)` instead. Example: +```scala + def f(@use(false) xs: List[() => Unit]): Int = xs.length +``` + +This works since `@use` is defined like this: +```scala +class use(cond: Boolean = true) extends StaticAnnotation +``` + + + diff --git a/tests/neg-custom-args/captures/method-uses.scala b/tests/neg-custom-args/captures/method-uses.scala new file mode 100644 index 000000000000..4dad7119a8e4 --- /dev/null +++ b/tests/neg-custom-args/captures/method-uses.scala @@ -0,0 +1,30 @@ +def test(xs: List[() => Unit]) = + xs.head // error + + def foo = + xs.head // ok + def bar() = + xs.head // ok + + class Foo: + println(xs.head) // error, but could be OK + + foo // error + bar() // error + Foo() // OK, but could be error + +def test2(xs: List[() => Unit]) = + def foo = xs.head // ok + () + +def test3(xs: List[() => Unit]): () ->{xs*} Unit = () => + println(xs.head) // ok + + def test4(xs: List[() => Unit]) = () => xs.head // ok + + def test5(xs: List[() => Unit]) = new: + println(xs.head) // ok + + def test6(xs: List[() => Unit]) = + val x= new { println(xs.head) } // error + x From e6b08de2d4b8a8fe7cb85cb92be39037d74ddc04 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 31 Oct 2024 14:50:07 +0100 Subject: [PATCH 034/202] Drop code supporting previous unsound scheme for curried lambdas --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 3 -- .../dotty/tools/dotc/cc/CheckCaptures.scala | 29 ++----------------- compiler/src/dotty/tools/dotc/cc/Setup.scala | 2 +- 3 files changed, 3 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index e4d80b7f6982..f9c33695e9fe 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -40,9 +40,6 @@ object ccConfig: */ inline val handleEtaExpansionsSpecially = false - /** If enabled we drop inner uses in outer arrows of a curried function */ - inline val DropOuterUsesInCurried = false - /** If true, use existential capture set variables */ def useExistentials(using Context) = Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.5`) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 844539f9a465..5e4afe542922 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -36,8 +36,6 @@ object CheckCaptures: case NestedInOwner // environment is a temporary one nested in the owner's environment, // and does not have a different actual owner symbol // (this happens when doing box adaptation). - case ClosureResult // environment is for the result of a closure, - // used only under ccConfig.DropOuterUsesInCurried case Boxed // environment is inside a box (in which case references are not counted) /** A class describing environments. @@ -185,11 +183,6 @@ object CheckCaptures: if ccConfig.useSealed then check.traverse(tp) end disallowRootCapabilitiesIn - /** Attachment key for bodies of closures, provided they are values. - * Used only under ccConfig.DropOuterUsesInCurried - */ - val ClosureBodyValue = Property.Key[Unit] - /** A prototype that indicates selection with an immutable value */ class PathSelectionProto(val sym: Symbol, val pt: Type)(using Context) extends WildcardSelectionProto @@ -339,20 +332,13 @@ class CheckCaptures extends Recheck, SymTransformer: /** The next environment enclosing `env` that needs to be charged * with free references. - * Skips environments directly enclosing environments of kind ClosureResult. * @param included Whether an environment is included in the range of * environments to charge. Once `included` is false, no * more environments need to be charged. */ def nextEnvToCharge(env: Env, included: Env => Boolean)(using Context): Env = - var nextEnv = env.outer - if env.owner.isConstructor then - if included(nextEnv) then nextEnv = nextEnv.outer - if env.kind == EnvKind.ClosureResult then - // skip this one - nextEnvToCharge(nextEnv, included) - else - nextEnv + if env.owner.isConstructor && included(env.outer) then env.outer.outer + else env.outer /** A description where this environment comes from */ private def provenance(env: Env)(using Context): String = @@ -746,15 +732,6 @@ class CheckCaptures extends Recheck, SymTransformer: .showing(i"rechecked closure $tree / $pt = $result", capt) override def recheckClosureBlock(mdef: DefDef, expr: Closure, pt: Type)(using Context): Type = - mdef.rhs match - case rhs @ closure(_, _, _) if ccConfig.DropOuterUsesInCurried => - // In a curried closure `x => y => e` don't leak capabilities retained by - // the second closure `y => e` into the first one. This is an approximation - // of the CC rule which says that a closure contributes captures to its - // environment only if a let-bound reference to the closure is used. - mdef.rhs.putAttachment(ClosureBodyValue, ()) - case _ => - openClosures = (mdef.symbol, pt) :: openClosures try // Constrain closure's parameters and result from the expected type before @@ -1002,8 +979,6 @@ class CheckCaptures extends Recheck, SymTransformer: tree match case _: RefTree | closureDef(_) if pt.isBoxedCapturing => curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv) - case _ if tree.hasAttachment(ClosureBodyValue) => - curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv) case _ => val res = try diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index f3ca15a8634d..56afaa1b33fb 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -314,7 +314,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => res ) val fntpe = defn.PolyFunctionOf(mt) - if !encl.isEmpty && (!ccConfig.DropOuterUsesInCurried || resDecomposed.isEmpty) then + if !encl.isEmpty then val cs = CaptureSet(encl.map(_.paramRefs.head)*) CapturingType(fntpe, cs, boxed = false) else fntpe From fd9e895e35c5d8ce5a4eba678b1bdae45ea9c747 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 31 Oct 2024 15:05:24 +0100 Subject: [PATCH 035/202] Re-enable handleEtaExpansionsSpecially setting This gives better error messages. Previously we thought this would make reach capabilities unsound, but I don't see an issue with the latest design. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 8 +----- .../src/dotty/tools/dotc/core/Types.scala | 28 ++++++++----------- .../captures/effect-swaps-explicit.check | 4 +-- tests/neg-custom-args/captures/i21614.check | 18 ++++++++---- tests/neg-custom-args/captures/i21614.scala | 2 +- tests/neg-custom-args/captures/levels.check | 7 +++-- .../captures/vars-simple.check | 8 ++++-- tests/neg-custom-args/captures/vars.check | 7 +++-- 8 files changed, 44 insertions(+), 38 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index f9c33695e9fe..31ddfcc93bbc 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -27,18 +27,12 @@ object ccConfig: */ inline val allowUnsoundMaps = false - /** If true, when computing the memberinfo of a refined type created - * by addCaptureRefinements take the refineInfo directly without intersecting - * with the parent info. - */ - inline val optimizedRefinements = false - /** If enabled, use a special path in recheckClosure for closures * that are eta expansions. This can improve some error messages but * currently leads to unsoundess for handling reach capabilities. * TODO: The unsoundness needs followin up. */ - inline val handleEtaExpansionsSpecially = false + inline val handleEtaExpansionsSpecially = true /** If true, use existential capture set variables */ def useExistentials(using Context) = diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 62bbd2be2773..87398000a461 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -863,23 +863,19 @@ object Types extends TypeUtils { } else val isRefinedMethod = rinfo.isInstanceOf[MethodOrPoly] - rinfo match - case CapturingType(_, refs: CaptureSet.RefiningVar) if ccConfig.optimizedRefinements => - pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, rinfo) + val joint = pdenot.meet( + new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId), pre, isRefinedMethod), + pre, + safeIntersection = ctx.base.pendingMemberSearches.contains(name)) + joint match + case joint: SingleDenotation + if isRefinedMethod + && (rinfo <:< joint.info + || name == nme.apply && defn.isFunctionType(tp.parent)) => + // use `rinfo` to keep the right parameter names for named args. See i8516.scala. + joint.derivedSingleDenotation(joint.symbol, rinfo, pre, isRefinedMethod) case _ => - val joint = pdenot.meet( - new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId), pre, isRefinedMethod), - pre, - safeIntersection = ctx.base.pendingMemberSearches.contains(name)) - joint match - case joint: SingleDenotation - if isRefinedMethod - && (rinfo <:< joint.info - || name == nme.apply && defn.isFunctionType(tp.parent)) => - // use `rinfo` to keep the right parameter names for named args. See i8516.scala. - joint.derivedSingleDenotation(joint.symbol, rinfo, pre, isRefinedMethod) - case _ => - joint + joint } def goApplied(tp: AppliedType, tycon: HKTypeLambda) = diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.check b/tests/neg-custom-args/captures/effect-swaps-explicit.check index 264dfa663d39..47559ab97568 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.check +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.check @@ -25,5 +25,5 @@ -- Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:68:15 --------------------------------------------- 68 | Result.make: //lbl ?=> // error, escaping label from Result | ^^^^^^^^^^^ - |local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9, contextual$9}, box E^?]]^): - | box Future[box T^?]^{fr, contextual$9, contextual$9} leaks into outer capture set of type parameter T of method make in object Result + |local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^): + | box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result diff --git a/tests/neg-custom-args/captures/i21614.check b/tests/neg-custom-args/captures/i21614.check index 14b468db4c8e..5e386fb3cb20 100644 --- a/tests/neg-custom-args/captures/i21614.check +++ b/tests/neg-custom-args/captures/i21614.check @@ -6,12 +6,20 @@ | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:12:12 --------------------------------------- -12 | files.map(new Logger(_)) // error, Q: can we improve the error message? +12 | files.map(new Logger(_)) // error, Q: can we improve the error message? // error | ^^^^^^^^^^^^^ - | Found: Logger{val f: (_$1 : File^{files*})}^ - | Required: Logger{val f: File^?}^? + | Found: (_$1: box File^{files*}) ->{files*} (ex$4: caps.Exists) -> Logger{val f: File^{_$1}}^{ex$4} + | Required: (_$1: box File^{files*}) -> box Logger{val f: File^?}^{ex$4, ex$4²} | - | Note that the universal capability `cap` - | cannot be included in capture set ? + | where: ex$4 is a reference to a value parameter + | ex$4² is a reference to a value parameter + | + | + | Note that the universal capability `cap` + | cannot be included in capture set ? | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/i21614.scala:12:8 ------------------------------------------------------------- +12 | files.map(new Logger(_)) // error, Q: can we improve the error message? // error + | ^^^^^^^^^ + | escaping local reference ex$4.type diff --git a/tests/neg-custom-args/captures/i21614.scala b/tests/neg-custom-args/captures/i21614.scala index 69aef446e9e2..6a1917656ebb 100644 --- a/tests/neg-custom-args/captures/i21614.scala +++ b/tests/neg-custom-args/captures/i21614.scala @@ -9,4 +9,4 @@ def mkLoggers1[F <: File^](@use files: List[F]): List[Logger^] = files.map((f: F) => new Logger(f)) // error, Q: can we make this pass (see #19076)? def mkLoggers2(@use files: List[File^]): List[Logger^] = - files.map(new Logger(_)) // error, Q: can we improve the error message? + files.map(new Logger(_)) // error, Q: can we improve the error message? // error diff --git a/tests/neg-custom-args/captures/levels.check b/tests/neg-custom-args/captures/levels.check index ddfa7c051211..2dae3ec3bbc6 100644 --- a/tests/neg-custom-args/captures/levels.check +++ b/tests/neg-custom-args/captures/levels.check @@ -5,10 +5,13 @@ | that type captures the root capability `cap`. | This is often caused by a local capability in an argument of constructor Ref | leaking as part of its result. --- Error: tests/neg-custom-args/captures/levels.scala:24:11 ------------------------------------------------------------ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:24:11 --------------------------------------- 24 | r.setV(g) // error | ^ - | reference (cap3 : CC^) is not included in the allowed capture set ? of value r + | Found: box (x: String) ->{cap3} String + | Required: box (x$0: String) ->? String | | Note that reference (cap3 : CC^), defined in method scope | cannot be included in outer capture set ? of value r + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/vars-simple.check b/tests/neg-custom-args/captures/vars-simple.check index e9671f775c22..2ef301b6ec1f 100644 --- a/tests/neg-custom-args/captures/vars-simple.check +++ b/tests/neg-custom-args/captures/vars-simple.check @@ -8,11 +8,13 @@ | since at least one of their capture sets contains the root capability `cap` | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/vars-simple.scala:16:8 -------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:16:8 ----------------------------------- 16 | a = g // error | ^ - | reference (cap3 : Cap) is not included in the allowed capture set {cap1, cap2} - | of an enclosing function literal with expected type box String ->{cap1, cap2} String + | Found: box (x: String) ->{cap3} String + | Required: box (x: String) ->{cap1, cap2} String + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:17:12 ---------------------------------- 17 | b = List(g) // error | ^^^^^^^ diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index 0d3c2e0f2e11..e4b1e71a2000 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -5,13 +5,16 @@ | | Note that reference (cap3 : Cap), defined in method scope | cannot be included in outer capture set {cap1} of variable a --- Error: tests/neg-custom-args/captures/vars.scala:25:8 --------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:25:8 ------------------------------------------ 25 | a = g // error | ^ - | reference (cap3 : Cap) is not included in the allowed capture set {cap1} of variable a + | Found: (x: String) ->{cap3} String + | Required: (x$0: String) ->{cap1} String | | Note that reference (cap3 : Cap), defined in method scope | cannot be included in outer capture set {cap1} of variable a + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:27:12 ----------------------------------------- 27 | b = List(g) // error | ^^^^^^^ From 4226a337ab2406989633385088faf0a78ab08627 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 31 Oct 2024 18:30:38 +0100 Subject: [PATCH 036/202] Disable reach capabilities in nested closures appearing without @use With our current @use scheme, this is unsound. We leave the possibility to re-enable as a Config option which is disabled by default and comes with a warning that enabling it would be unsound. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 10 ++++++--- .../dotty/tools/dotc/cc/CheckCaptures.scala | 16 ++++++++------ .../captures/method-uses.scala | 6 ++--- .../captures/unsound-reach-5.scala | 22 +++++++++++++++++++ 4 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 tests/neg-custom-args/captures/unsound-reach-5.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 31ddfcc93bbc..6f7c6df19e84 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -28,12 +28,16 @@ object ccConfig: inline val allowUnsoundMaps = false /** If enabled, use a special path in recheckClosure for closures - * that are eta expansions. This can improve some error messages but - * currently leads to unsoundess for handling reach capabilities. - * TODO: The unsoundness needs followin up. + * that are eta expansions. This can improve some error messages. */ inline val handleEtaExpansionsSpecially = true + /** Don't require @use for reach capabilities that are accessed + * only in a nested closure. This is unsound without additional + * mitigation measures, as shown by unsound-reach-5.scala. + */ + inline val deferredReaches = false + /** If true, use existential capture set variables */ def useExistentials(using Context) = Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.5`) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 5e4afe542922..05256ee515f2 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -824,13 +824,15 @@ class CheckCaptures extends Recheck, SymTransformer: if Synthetics.isExcluded(sym) then sym.info else // If rhs ends in a closure or anonymous class, the corresponding symbol - def nestedClosure(rhs: Tree)(using Context): Symbol = rhs match - case Closure(_, meth, _) => meth.symbol - case Apply(fn, _) if fn.symbol.isConstructor && fn.symbol.owner.isAnonymousClass => fn.symbol.owner - case Block(_, expr) => nestedClosure(expr) - case Inlined(_, _, expansion) => nestedClosure(expansion) - case Typed(expr, _) => nestedClosure(expr) - case _ => NoSymbol + def nestedClosure(rhs: Tree)(using Context): Symbol = + if !ccConfig.deferredReaches then NoSymbol + else rhs match + case Closure(_, meth, _) => meth.symbol + case Apply(fn, _) if fn.symbol.isConstructor && fn.symbol.owner.isAnonymousClass => fn.symbol.owner + case Block(_, expr) => nestedClosure(expr) + case Inlined(_, _, expansion) => nestedClosure(expansion) + case Typed(expr, _) => nestedClosure(expr) + case _ => NoSymbol val saved = curEnv val localSet = capturedVars(sym) diff --git a/tests/neg-custom-args/captures/method-uses.scala b/tests/neg-custom-args/captures/method-uses.scala index 4dad7119a8e4..69acef6a99a8 100644 --- a/tests/neg-custom-args/captures/method-uses.scala +++ b/tests/neg-custom-args/captures/method-uses.scala @@ -18,12 +18,12 @@ def test2(xs: List[() => Unit]) = () def test3(xs: List[() => Unit]): () ->{xs*} Unit = () => - println(xs.head) // ok + println(xs.head) // error, ok under deferredReaches - def test4(xs: List[() => Unit]) = () => xs.head // ok + def test4(xs: List[() => Unit]) = () => xs.head // error, ok under deferredReaches def test5(xs: List[() => Unit]) = new: - println(xs.head) // ok + println(xs.head) // error, ok under deferredReaches def test6(xs: List[() => Unit]) = val x= new { println(xs.head) } // error diff --git a/tests/neg-custom-args/captures/unsound-reach-5.scala b/tests/neg-custom-args/captures/unsound-reach-5.scala new file mode 100644 index 000000000000..806de5093ecd --- /dev/null +++ b/tests/neg-custom-args/captures/unsound-reach-5.scala @@ -0,0 +1,22 @@ +class IO + +def f(xs: List[() => Unit]): () ->{xs*} Unit = () => + println(xs.head) // error + +def test(io: IO^)(ys: List[() ->{io} Unit]) = + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! ys* gets lost + () + +def test(io: IO^) = + def ys: List[() ->{io} Unit] = ??? + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! io gets lost + () + + + From 8f8a15fae612a45e4dddf06704c79d39686f3154 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 1 Nov 2024 14:05:00 +0100 Subject: [PATCH 037/202] Go back to sealed checking Check that type parameters of methods and parent traits don't get instantiated with types containing a `cap` anywhere in covariant or invariant position. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 7 +-- .../dotty/tools/dotc/cc/CheckCaptures.scala | 58 ++++++++++++------- .../captures/box-adapt-cases.scala | 2 +- .../neg-custom-args/captures/capt-test.scala | 4 +- tests/neg-custom-args/captures/capt1.check | 20 +++---- tests/neg-custom-args/captures/capt1.scala | 4 +- .../captures/effect-swaps-explicit.scala | 4 +- .../captures/effect-swaps.check | 5 ++ .../captures/effect-swaps.scala | 2 +- tests/neg-custom-args/captures/filevar.scala | 4 +- tests/neg-custom-args/captures/hk-param.scala | 16 +++++ tests/neg-custom-args/captures/i15749.scala | 2 + tests/neg-custom-args/captures/i15772.check | 8 +-- tests/neg-custom-args/captures/i15922.scala | 4 +- .../captures/i15923-cases.scala | 7 --- tests/neg-custom-args/captures/i16114.check | 50 +++++++--------- tests/neg-custom-args/captures/i16114.scala | 4 +- .../captures/i19330-alt2.scala | 4 +- tests/neg-custom-args/captures/i19330.scala | 4 +- tests/neg-custom-args/captures/i21401.check | 35 +++++++---- tests/neg-custom-args/captures/i21401.scala | 8 +-- tests/neg-custom-args/captures/i21614.check | 18 ++---- tests/neg-custom-args/captures/i21614.scala | 2 +- tests/neg-custom-args/captures/i21646.scala | 2 +- .../captures/lazylists-exceptions.check | 5 +- tests/neg-custom-args/captures/levels.check | 16 +++-- tests/neg-custom-args/captures/levels.scala | 2 - .../neg-custom-args/captures/outer-var.check | 30 +++++++--- .../neg-custom-args/captures/outer-var.scala | 4 +- tests/neg-custom-args/captures/reaches.check | 31 +++++----- tests/neg-custom-args/captures/reaches.scala | 8 +-- tests/neg-custom-args/captures/real-try.check | 8 +-- tests/neg-custom-args/captures/real-try.scala | 4 +- tests/neg-custom-args/captures/try.check | 14 ++--- tests/neg-custom-args/captures/try.scala | 4 +- tests/neg-custom-args/captures/unbox.scala | 2 +- .../captures/unsound-reach-2.scala | 4 +- .../captures/unsound-reach-3.scala | 6 +- .../captures/unsound-reach-4.check | 5 ++ .../captures/unsound-reach-4.scala | 6 +- .../captures/unsound-reach.check | 23 ++++---- .../captures/unsound-reach.scala | 9 ++- .../captures/vars-simple.check | 9 +-- tests/neg-custom-args/captures/vars.scala | 4 +- .../captures/widen-reach.check | 12 ++-- .../captures/widen-reach.scala | 4 +- tests/pos-custom-args/captures/hk-param.scala | 2 + .../captures/i15923-cases.scala | 4 ++ tests/pos-custom-args/captures/levels.scala | 6 +- 49 files changed, 268 insertions(+), 228 deletions(-) create mode 100644 tests/neg-custom-args/captures/hk-param.scala delete mode 100644 tests/neg-custom-args/captures/i15923-cases.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 6f7c6df19e84..97c774b593a6 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -45,12 +45,11 @@ object ccConfig: /** If true, use "sealed" as encapsulation mechanism, meaning that we * check that type variable instantiations don't have `cap` in any of * their capture sets. This is an alternative of the original restriction - * that `cap` can't be boxed or unboxed. It is used in 3.3 and 3.4 but - * dropped again in 3.5. + * that `cap` can't be boxed or unboxed. It is dropped in 3.5 but used + * again in 3.6. */ def useSealed(using Context) = - Feature.sourceVersion.stable == SourceVersion.`3.3` - || Feature.sourceVersion.stable == SourceVersion.`3.4` + Feature.sourceVersion.stable != SourceVersion.`3.5` end ccConfig diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 05256ee515f2..cce5a7cd21dd 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -174,8 +174,7 @@ object CheckCaptures: def part = if t eq tp then "" else i"the part $t of " report.error( em"""$what cannot $have $tp since - |${part}that type captures the root capability `cap`. - |$addendum""", + |${part}that type captures the root capability `cap`.$addendum""", pos) traverse(parent) case t => @@ -686,23 +685,34 @@ class CheckCaptures extends Recheck, SymTransformer: else ownType end instantiate + def disallowCapInTypeArgs(fn: Tree, sym: Symbol, args: List[Tree])(using Context): Unit = + def isExempt = sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue + if ccConfig.useSealed && !isExempt then + val paramNames = atPhase(thisPhase.prev): + fn.tpe.widenDealias match + case tl: TypeLambda => tl.paramNames + case ref: AppliedType if ref.typeSymbol.isClass => ref.typeSymbol.typeParams.map(_.name) + case t => + println(i"parent type: $t") + args.map(_ => EmptyTypeName) + for case (arg: TypeTree, pname) <- args.lazyZip(paramNames) do + def where = if sym.exists then i" in an argument of $sym" else "" + val (addendum, pos) = + if arg.isInferred + then ("\nThis is often caused by a local capability$where\nleaking as part of its result.", fn.srcPos) + else if arg.span.exists then ("", arg.srcPos) + else ("", fn.srcPos) + disallowRootCapabilitiesIn(arg.knownType, NoSymbol, + i"Type variable $pname of $sym", "be instantiated to", addendum, pos) + end disallowCapInTypeArgs + override def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = - val meth = tree.symbol - if ccConfig.useSealed then - val TypeApply(fn, args) = tree - val polyType = atPhase(thisPhase.prev): - fn.tpe.widen.asInstanceOf[TypeLambda] - def isExempt(sym: Symbol) = - sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue - for case (arg: TypeTree, formal, pname) <- args.lazyZip(polyType.paramRefs).lazyZip((polyType.paramNames)) do - if !isExempt(meth) then - def where = if meth.exists then i" in an argument of $meth" else "" - disallowRootCapabilitiesIn(arg.knownType, NoSymbol, - i"Sealed type variable $pname", "be instantiated to", - i"This is often caused by a local capability$where\nleaking as part of its result.", - tree.srcPos) + val meth = tree.fun match + case fun @ Select(qual, nme.apply) => qual.symbol.orElse(fun.symbol) + case fun => fun.symbol + disallowCapInTypeArgs(tree.fun, meth, tree.args) val res = Existential.toCap(super.recheckTypeApply(tree, pt)) - includeCallCaptures(meth, res, tree.srcPos) + includeCallCaptures(tree.symbol, res, tree.srcPos) checkContains(tree) res end recheckTypeApply @@ -806,7 +816,7 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => "" s"an anonymous function$location" else encl.show - (NoSymbol, i"\nNote that $sym does not count as local since it is captured by $enclStr") + (NoSymbol, i"\n\nNote that $sym does not count as local since it is captured by $enclStr") case _ => (sym, "") disallowRootCapabilitiesIn( @@ -927,6 +937,11 @@ class CheckCaptures extends Recheck, SymTransformer: checkSubset(thisSet, CaptureSet.empty.withDescription(i"of pure base class $pureBase"), selfType.srcPos, cs1description = " captured by this self type") + for case tpt: TypeTree <- impl.parents do + tpt.tpe match + case AppliedType(fn, args) => + disallowCapInTypeArgs(tpt, fn.typeSymbol, args.map(TypeTree(_))) + case _ => inNestedLevelUnless(cls.is(Module)): super.recheckClassDef(tree, impl, cls) finally @@ -950,8 +965,8 @@ class CheckCaptures extends Recheck, SymTransformer: val tp = super.recheckTry(tree, pt) if ccConfig.useSealed && Feature.enabled(Feature.saferExceptions) then disallowRootCapabilitiesIn(tp, ctx.owner, - "result of `try`", "have type", - "This is often caused by a locally generated exception capability leaking as part of its result.", + "The result of `try`", "have type", + "\nThis is often caused by a locally generated exception capability leaking as part of its result.", tree.srcPos) tp @@ -1592,8 +1607,7 @@ class CheckCaptures extends Recheck, SymTransformer: && !arg.typeSymbol.name.is(WildcardParamName) then CheckCaptures.disallowRootCapabilitiesIn(arg, NoSymbol, - "Array", "have element type", - "Since arrays are mutable, they have to be treated like variables,\nso their element type must be sealed.", + "Array", "have element type", "", pos) traverseChildren(t) case defn.RefinedFunctionOf(rinfo: MethodType) => diff --git a/tests/neg-custom-args/captures/box-adapt-cases.scala b/tests/neg-custom-args/captures/box-adapt-cases.scala index 681d699842ed..d9ec0f80a548 100644 --- a/tests/neg-custom-args/captures/box-adapt-cases.scala +++ b/tests/neg-custom-args/captures/box-adapt-cases.scala @@ -4,7 +4,7 @@ def test1(): Unit = { type Id[X] = [T] -> (op: X => T) -> T val x: Id[Cap^] = ??? - x(cap => cap.use()) // error, OK under sealed + x(cap => cap.use()) } def test2(io: Cap^): Unit = { diff --git a/tests/neg-custom-args/captures/capt-test.scala b/tests/neg-custom-args/captures/capt-test.scala index b202a14d0940..80ee1aba84e1 100644 --- a/tests/neg-custom-args/captures/capt-test.scala +++ b/tests/neg-custom-args/captures/capt-test.scala @@ -20,8 +20,8 @@ def handle[E <: Exception, R <: Top](op: (CT[E] @retains(caps.cap)) => R)(handl catch case ex: E => handler(ex) def test: Unit = - val b = handle[Exception, () => Nothing] { + val b = handle[Exception, () => Nothing] { // error (x: CanThrow[Exception]) => () => raise(new Exception)(using x) - } { // error + } { (ex: Exception) => ??? } diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index 3d0ed538b2e5..94103799d698 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -33,22 +33,18 @@ 29 | def m() = if x == null then y else y | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/capt1.scala:34:12 ------------------------------------------------------------- +-- Error: tests/neg-custom-args/captures/capt1.scala:34:16 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error - | ^^^^^^^^^^^^ - | Sealed type variable X cannot be instantiated to () -> box C^ since - | the part box C^ of that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method h - | leaking as part of its result. + | ^^^^^^^^^ + | Type variable X of method h cannot be instantiated to () -> box C^ since + | the part box C^ of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/capt1.scala:34:30 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error | ^ | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> box C^ --- Error: tests/neg-custom-args/captures/capt1.scala:36:12 ------------------------------------------------------------- +-- Error: tests/neg-custom-args/captures/capt1.scala:36:13 ------------------------------------------------------------- 36 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Sealed type variable X cannot be instantiated to box () ->{x} Cap since - | the part Cap of that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method h - | leaking as part of its result. + | ^^^^^^^^^^^^^^^^^^^^^^^ + | Type variable X of method h cannot be instantiated to box () ->{x} Cap since + | the part Cap of that type captures the root capability `cap`. diff --git a/tests/neg-custom-args/captures/capt1.scala b/tests/neg-custom-args/captures/capt1.scala index cad0bad4ba56..8da7e633ca51 100644 --- a/tests/neg-custom-args/captures/capt1.scala +++ b/tests/neg-custom-args/captures/capt1.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import annotation.retains class C def f(x: C @retains(caps.cap), y: C): () -> C = diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.scala b/tests/neg-custom-args/captures/effect-swaps-explicit.scala index 7474e1711b34..e440271ccf88 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.scala +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + object boundary: final class Label[-T] // extends caps.Capability diff --git a/tests/neg-custom-args/captures/effect-swaps.check b/tests/neg-custom-args/captures/effect-swaps.check index ef5a95d333bf..b74c165fd6b6 100644 --- a/tests/neg-custom-args/captures/effect-swaps.check +++ b/tests/neg-custom-args/captures/effect-swaps.check @@ -22,3 +22,8 @@ 73 | fr.await.ok | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/effect-swaps.scala:66:15 ------------------------------------------------------ +66 | Result.make: // error: local reference leaks + | ^^^^^^^^^^^ + |local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^): + | box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result diff --git a/tests/neg-custom-args/captures/effect-swaps.scala b/tests/neg-custom-args/captures/effect-swaps.scala index 4bafd6421af3..99c781b963c5 100644 --- a/tests/neg-custom-args/captures/effect-swaps.scala +++ b/tests/neg-custom-args/captures/effect-swaps.scala @@ -63,7 +63,7 @@ def test[T, E](using Async) = fr.await.ok def fail4[T, E](fr: Future[Result[T, E]]^) = - Result.make: // should be errorm but inders Result[Any, Any] + Result.make: // error: local reference leaks Future: fut ?=> fr.await.ok diff --git a/tests/neg-custom-args/captures/filevar.scala b/tests/neg-custom-args/captures/filevar.scala index e54f161ef124..5b83f1d29380 100644 --- a/tests/neg-custom-args/captures/filevar.scala +++ b/tests/neg-custom-args/captures/filevar.scala @@ -5,8 +5,8 @@ class File: def write(x: String): Unit = ??? class Service: - var file: File^ = uninitialized // OK, was error under sealed - def log = file.write("log") // error, was OK under sealed + var file: File^ = uninitialized // error, was OK under unsealed + def log = file.write("log") // OK, was error under unsealed def withFile[T](op: (l: caps.Capability) ?-> (f: File^{l}) => T): T = op(using caps.cap)(new File) diff --git a/tests/neg-custom-args/captures/hk-param.scala b/tests/neg-custom-args/captures/hk-param.scala new file mode 100644 index 000000000000..bfd4f82a0697 --- /dev/null +++ b/tests/neg-custom-args/captures/hk-param.scala @@ -0,0 +1,16 @@ +/** Concrete collection type: View */ +trait View[+A] extends Itable[A], ILike[A, [X] =>> View[X]^]: // error + override def fromIterable[B](c: Itable[B]^): View[B]^{c} = ??? + +trait IPolyTransforms[+A, +C[A]] extends Any: + def fromIterable[B](coll: Itable[B]^): C[B] + +trait ILike[+A, +C[X] <: Itable[X]^] extends IPolyTransforms[A, C] + +/** Base trait for generic collections */ +trait Itable[+A] extends ItableOnce[A] with ILike[A, Itable^] // error + +/** Iterator can be used only once */ +trait ItableOnce[+A] { + def iterator: Iterator[A]^{this} +} diff --git a/tests/neg-custom-args/captures/i15749.scala b/tests/neg-custom-args/captures/i15749.scala index c5b59042085a..9a4e8b0b81ae 100644 --- a/tests/neg-custom-args/captures/i15749.scala +++ b/tests/neg-custom-args/captures/i15749.scala @@ -1,3 +1,5 @@ +//> using options -source 3.5 +// (to make sure we use the unsealed policy) class Unit object unit extends Unit diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 58582423b101..0f8f0bf6eac5 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -25,11 +25,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:34 --------------------------------------- 33 | val boxed2 : Observe[C]^ = box2(c) // error | ^ - | Found: C^ - | Required: box C{val arg: C^?}^ + | Found: box C^ + | Required: box C{val arg: C^?}^? | - | Note that C^ cannot be box-converted to box C{val arg: C^?}^ - | since at least one of their capture sets contains the root capability `cap` + | Note that the universal capability `cap` + | cannot be included in capture set ? | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ---------------------------------------- diff --git a/tests/neg-custom-args/captures/i15922.scala b/tests/neg-custom-args/captures/i15922.scala index 89bf91493fcd..848a22fe5341 100644 --- a/tests/neg-custom-args/captures/i15922.scala +++ b/tests/neg-custom-args/captures/i15922.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to force sealed encapsulation checking) + + trait Cap { def use(): Int } type Id[X] = [T] -> (op: X => T) -> T def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x) diff --git a/tests/neg-custom-args/captures/i15923-cases.scala b/tests/neg-custom-args/captures/i15923-cases.scala deleted file mode 100644 index 83cfa554e8b9..000000000000 --- a/tests/neg-custom-args/captures/i15923-cases.scala +++ /dev/null @@ -1,7 +0,0 @@ -trait Cap { def use(): Int } -type Id[X] = [T] -> (op: X => T) -> T -def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x) - -def foo(x: Id[Cap^]) = { - x(_.use()) // error, was OK under sealed policy -} diff --git a/tests/neg-custom-args/captures/i16114.check b/tests/neg-custom-args/captures/i16114.check index 6d81d8d63747..3b9fbd40493f 100644 --- a/tests/neg-custom-args/captures/i16114.check +++ b/tests/neg-custom-args/captures/i16114.check @@ -1,48 +1,38 @@ --- Error: tests/neg-custom-args/captures/i16114.scala:18:12 ------------------------------------------------------------ +-- Error: tests/neg-custom-args/captures/i16114.scala:18:13 ------------------------------------------------------------ 18 | expect[Cap^] { // error - | ^^^^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box Cap^ since - | that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method expect - | leaking as part of its result. + | ^^^^ + | Type variable T of method expect cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/i16114.scala:20:8 ------------------------------------------------------------- 20 | fs // error (limitation) | ^^ | (fs : Cap^) cannot be referenced here; it is not included in the allowed capture set {io} | of an enclosing function literal with expected type Unit ->{io} Unit --- Error: tests/neg-custom-args/captures/i16114.scala:24:12 ------------------------------------------------------------ +-- Error: tests/neg-custom-args/captures/i16114.scala:24:13 ------------------------------------------------------------ 24 | expect[Cap^] { // error - | ^^^^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box Cap^ since - | that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method expect - | leaking as part of its result. + | ^^^^ + | Type variable T of method expect cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/i16114.scala:26:8 ------------------------------------------------------------- 26 | io // error (limitation) | ^^ | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {fs} | of an enclosing function literal with expected type Unit ->{fs} Unit --- Error: tests/neg-custom-args/captures/i16114.scala:30:12 ------------------------------------------------------------ +-- Error: tests/neg-custom-args/captures/i16114.scala:30:13 ------------------------------------------------------------ 30 | expect[Cap^] { // error - | ^^^^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box Cap^ since - | that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method expect - | leaking as part of its result. --- Error: tests/neg-custom-args/captures/i16114.scala:36:12 ------------------------------------------------------------ + | ^^^^ + | Type variable T of method expect cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i16114.scala:36:13 ------------------------------------------------------------ 36 | expect[Cap^](io) // error - | ^^^^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box Cap^ since - | that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method expect - | leaking as part of its result. --- Error: tests/neg-custom-args/captures/i16114.scala:39:12 ------------------------------------------------------------ + | ^^^^ + | Type variable T of method expect cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i16114.scala:39:13 ------------------------------------------------------------ 39 | expect[Cap^] { // error - | ^^^^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box Cap^ since - | that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method expect - | leaking as part of its result. + | ^^^^ + | Type variable T of method expect cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/i16114.scala:40:8 ------------------------------------------------------------- 40 | io.use() // error | ^^ diff --git a/tests/neg-custom-args/captures/i16114.scala b/tests/neg-custom-args/captures/i16114.scala index ec04fe9c9827..801ea3b11a3d 100644 --- a/tests/neg-custom-args/captures/i16114.scala +++ b/tests/neg-custom-args/captures/i16114.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + trait Cap { def use(): Int; def close(): Unit } def mkCap(): Cap^ = ??? diff --git a/tests/neg-custom-args/captures/i19330-alt2.scala b/tests/neg-custom-args/captures/i19330-alt2.scala index 86634b45dbe3..3e52e3c65634 100644 --- a/tests/neg-custom-args/captures/i19330-alt2.scala +++ b/tests/neg-custom-args/captures/i19330-alt2.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import language.experimental.captureChecking trait Logger diff --git a/tests/neg-custom-args/captures/i19330.scala b/tests/neg-custom-args/captures/i19330.scala index 5fbdc00db311..715b670860cd 100644 --- a/tests/neg-custom-args/captures/i19330.scala +++ b/tests/neg-custom-args/captures/i19330.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to force sealed encapsulation checking) + + import language.experimental.captureChecking trait Logger diff --git a/tests/neg-custom-args/captures/i21401.check b/tests/neg-custom-args/captures/i21401.check index e204540358ce..679c451949bd 100644 --- a/tests/neg-custom-args/captures/i21401.check +++ b/tests/neg-custom-args/captures/i21401.check @@ -1,13 +1,28 @@ --- Error: tests/neg-custom-args/captures/i21401.scala:15:22 ------------------------------------------------------------ -15 | val a = usingIO[IO^](x => x) // error: The expression's type IO^ is not allowed to capture the root capability `cap` - | ^^^^^^^^^^^^^^^^^^^^ - | The expression's type box IO^ is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. --- Error: tests/neg-custom-args/captures/i21401.scala:16:70 ------------------------------------------------------------ -16 | val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error: The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^ - | ^^^^^^^^^^^^^^^^^^^ - | The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^. - | This usually means that a capability persists longer than its allowed lifetime. +-- Error: tests/neg-custom-args/captures/i21401.scala:13:14 ------------------------------------------------------------ +13 | op1(Boxed[IO^](x)) // error + | ^^^ + | Type variable T of object Boxed cannot be instantiated to box IO^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i21401.scala:15:18 ------------------------------------------------------------ +15 | val a = usingIO[IO^](x => x) // error + | ^^^ + | Type variable R of method usingIO cannot be instantiated to box IO^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i21401.scala:16:66 ------------------------------------------------------------ +16 | val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error + | ^^^ + | Type variable R of method usingIO cannot be instantiated to Res since + | the part box IO^ of that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i21401.scala:17:29 ------------------------------------------------------------ +17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error + | ^^^^^^^^^^ + | Type variable R of value leaked cannot be instantiated to Boxed[box IO^] since + | the part box IO^ of that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i21401.scala:17:52 ------------------------------------------------------------ +17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error + | ^^^^^^^^^^^^^^^^^^^^^^^^ + |Type variable X of value leaked cannot be instantiated to Boxed[box IO^] -> (ex$18: caps.Exists) -> Boxed[box IO^{ex$18}] since + |the part box IO^{ex$18} of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/i21401.scala:18:21 ------------------------------------------------------------ 18 | val y: IO^{x*} = x.unbox // error | ^^^^^^^ diff --git a/tests/neg-custom-args/captures/i21401.scala b/tests/neg-custom-args/captures/i21401.scala index 8284c601cd5f..0b5479376a0a 100644 --- a/tests/neg-custom-args/captures/i21401.scala +++ b/tests/neg-custom-args/captures/i21401.scala @@ -10,10 +10,10 @@ type Res = [R, X <: Boxed[IO^] -> R] -> (op: X) -> R def mkRes(x: IO^): Res = [R, X <: Boxed[IO^] -> R] => (op: X) => val op1: Boxed[IO^] -> R = op - op1(Boxed[IO^](x)) + op1(Boxed[IO^](x)) // error def test2() = - val a = usingIO[IO^](x => x) // error: The expression's type IO^ is not allowed to capture the root capability `cap` - val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error: The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^ - val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) + val a = usingIO[IO^](x => x) // error + val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error + val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error val y: IO^{x*} = x.unbox // error y.println("boom") diff --git a/tests/neg-custom-args/captures/i21614.check b/tests/neg-custom-args/captures/i21614.check index 5e386fb3cb20..ced3ab7fd59a 100644 --- a/tests/neg-custom-args/captures/i21614.check +++ b/tests/neg-custom-args/captures/i21614.check @@ -6,20 +6,12 @@ | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:12:12 --------------------------------------- -12 | files.map(new Logger(_)) // error, Q: can we improve the error message? // error +12 | files.map(new Logger(_)) // error, Q: can we improve the error message? | ^^^^^^^^^^^^^ - | Found: (_$1: box File^{files*}) ->{files*} (ex$4: caps.Exists) -> Logger{val f: File^{_$1}}^{ex$4} - | Required: (_$1: box File^{files*}) -> box Logger{val f: File^?}^{ex$4, ex$4²} + | Found: (_$1: box File^{files*}) ->{files*} (ex$13: caps.Exists) -> box Logger{val f: File^{_$1}}^{ex$13} + | Required: (_$1: box File^{files*}) -> box Logger{val f: File^?}^? | - | where: ex$4 is a reference to a value parameter - | ex$4² is a reference to a value parameter - | - | - | Note that the universal capability `cap` - | cannot be included in capture set ? + | Note that the universal capability `cap` + | cannot be included in capture set ? | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/i21614.scala:12:8 ------------------------------------------------------------- -12 | files.map(new Logger(_)) // error, Q: can we improve the error message? // error - | ^^^^^^^^^ - | escaping local reference ex$4.type diff --git a/tests/neg-custom-args/captures/i21614.scala b/tests/neg-custom-args/captures/i21614.scala index 6a1917656ebb..69aef446e9e2 100644 --- a/tests/neg-custom-args/captures/i21614.scala +++ b/tests/neg-custom-args/captures/i21614.scala @@ -9,4 +9,4 @@ def mkLoggers1[F <: File^](@use files: List[F]): List[Logger^] = files.map((f: F) => new Logger(f)) // error, Q: can we make this pass (see #19076)? def mkLoggers2(@use files: List[File^]): List[Logger^] = - files.map(new Logger(_)) // error, Q: can we improve the error message? // error + files.map(new Logger(_)) // error, Q: can we improve the error message? diff --git a/tests/neg-custom-args/captures/i21646.scala b/tests/neg-custom-args/captures/i21646.scala index 42c493a9ea80..cf451ded3120 100644 --- a/tests/neg-custom-args/captures/i21646.scala +++ b/tests/neg-custom-args/captures/i21646.scala @@ -5,7 +5,7 @@ trait File extends Capability class Resource[T <: Capability](gen: T): def use[U](f: T => U): U = - f(gen) // error + f(gen) // OK, was error under unsealed @main def run = val myFile: File = ??? diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.check b/tests/neg-custom-args/captures/lazylists-exceptions.check index 4a8738118609..111719a81f07 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.check +++ b/tests/neg-custom-args/captures/lazylists-exceptions.check @@ -1,8 +1,9 @@ -- Error: tests/neg-custom-args/captures/lazylists-exceptions.scala:36:2 ----------------------------------------------- 36 | try // error | ^ - | The expression's type LazyList[Int]^ is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. + | The result of `try` cannot have type LazyList[Int]^ since + | that type captures the root capability `cap`. + | This is often caused by a locally generated exception capability leaking as part of its result. 37 | tabulate(10) { i => 38 | if i > 9 then throw Ex1() 39 | i * i diff --git a/tests/neg-custom-args/captures/levels.check b/tests/neg-custom-args/captures/levels.check index 2dae3ec3bbc6..b99adefd4b2f 100644 --- a/tests/neg-custom-args/captures/levels.check +++ b/tests/neg-custom-args/captures/levels.check @@ -1,12 +1,10 @@ --- Error: tests/neg-custom-args/captures/levels.scala:19:13 ------------------------------------------------------------ -19 | val _ = Ref[String => String]((x: String) => x) // error - | ^^^^^^^^^^^^^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box String => String since - | that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of constructor Ref - | leaking as part of its result. --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:24:11 --------------------------------------- -24 | r.setV(g) // error +-- Error: tests/neg-custom-args/captures/levels.scala:17:21 ------------------------------------------------------------ +17 | val _ = Ref[String => String]((x: String) => x) // error + | ^^^^^^^^^^^^^^^^ + | Type variable T of constructor Ref cannot be instantiated to box String => String since + | that type captures the root capability `cap`. +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:22:11 --------------------------------------- +22 | r.setV(g) // error | ^ | Found: box (x: String) ->{cap3} String | Required: box (x$0: String) ->? String diff --git a/tests/neg-custom-args/captures/levels.scala b/tests/neg-custom-args/captures/levels.scala index 4709fd80d9b8..b28e87f03ef7 100644 --- a/tests/neg-custom-args/captures/levels.scala +++ b/tests/neg-custom-args/captures/levels.scala @@ -1,5 +1,3 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) class CC def test1(cap1: CC^) = diff --git a/tests/neg-custom-args/captures/outer-var.check b/tests/neg-custom-args/captures/outer-var.check index 32351a179eab..72af842728a1 100644 --- a/tests/neg-custom-args/captures/outer-var.check +++ b/tests/neg-custom-args/captures/outer-var.check @@ -1,8 +1,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:11:8 ------------------------------------- 11 | x = q // error | ^ - | Found: box () ->{q} Unit - | Required: box () ->{p, q²} Unit + | Found: (q : Proc) + | Required: () ->{p, q²} Unit | | where: q is a parameter in method inner | q² is a parameter in method test @@ -12,19 +12,31 @@ 12 | x = (q: Proc) // error | ^^^^^^^ | Found: Proc - | Required: box () ->{p, q} Unit - | - | Note that () => Unit cannot be box-converted to box () ->{p, q} Unit - | since at least one of their capture sets contains the root capability `cap` + | Required: () ->{p, q} Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:13:9 ------------------------------------- 13 | y = (q: Proc) // error | ^^^^^^^ | Found: Proc - | Required: box () => Unit + | Required: () ->{p} Unit + | + | Note that the universal capability `cap` + | cannot be included in capture set {p} of variable y + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:14:8 ------------------------------------- +14 | y = q // error, was OK under unsealed + | ^ + | Found: (q : Proc) + | Required: () ->{p} Unit | - | Note that () => Unit cannot be box-converted to box () => Unit - | since at least one of their capture sets contains the root capability `cap` + | Note that reference (q : Proc), defined in method inner + | cannot be included in outer capture set {p} of variable y | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/outer-var.scala:16:57 --------------------------------------------------------- +16 | var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error, was OK under unsealed + | ^^^^^^^^^^ + | Type variable A of object ListBuffer cannot be instantiated to box () => Unit since + | that type captures the root capability `cap`. diff --git a/tests/neg-custom-args/captures/outer-var.scala b/tests/neg-custom-args/captures/outer-var.scala index e26cd631602a..f869bfbfc387 100644 --- a/tests/neg-custom-args/captures/outer-var.scala +++ b/tests/neg-custom-args/captures/outer-var.scala @@ -11,8 +11,8 @@ def test(p: Proc, q: () => Unit) = x = q // error x = (q: Proc) // error y = (q: Proc) // error - y = q // OK, was error under sealed + y = q // error, was OK under unsealed - var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // OK, was error under sealed + var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error, was OK under unsealed diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index 0121f0cf7a55..7c00fa7299fe 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -15,16 +15,21 @@ | cannot be included in outer capture set {xs*} of value cur | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/reaches.scala:38:31 ----------------------------------------------------------- -38 | val next: () => Unit = cur.head // error - | ^^^^^^^^ - | The expression's type box () => Unit is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. --- Error: tests/neg-custom-args/captures/reaches.scala:45:35 ----------------------------------------------------------- -45 | val next: () => Unit = cur.get.head // error - | ^^^^^^^^^^^^ - | The expression's type box () => Unit is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. +-- Error: tests/neg-custom-args/captures/reaches.scala:36:6 ------------------------------------------------------------ +36 | var cur: List[Proc] = xs // error + | ^ + | Mutable variable cur cannot have type List[box () => Unit] since + | the part box () => Unit of that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/reaches.scala:43:16 ----------------------------------------------------------- +43 | val cur = Ref[List[Proc]](xs) // error + | ^^^^^^^^^^ + | Type variable T of constructor Ref cannot be instantiated to List[box () => Unit] since + | the part box () => Unit of that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/reaches.scala:53:51 ----------------------------------------------------------- +53 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error + | ^ + | Type variable A of constructor Id cannot be instantiated to box () => Unit since + | that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/reaches.scala:55:6 ------------------------------------------------------------ 55 | id(() => f.write()) // error | ^^^^^^^^^^^^^^^^^^^ @@ -46,12 +51,6 @@ | ^ | Local reach capability ps* leaks into capture scope of method mapCompose. | To allow this, the parameter ps should be declared with a @use annotation --- [E057] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:53:51 -------------------------------------- -53 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error - | ^ - | Type argument () -> Unit does not conform to lower bound () => Unit - | - | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/reaches.scala:61:31 ----------------------------------------------------------- 61 | val leaked = usingFile[File^{id*}]: f => // error | ^^^ diff --git a/tests/neg-custom-args/captures/reaches.scala b/tests/neg-custom-args/captures/reaches.scala index 066c40031553..a9773b76f445 100644 --- a/tests/neg-custom-args/captures/reaches.scala +++ b/tests/neg-custom-args/captures/reaches.scala @@ -33,16 +33,16 @@ def runAll1(@use xs: List[Proc]): Unit = (() => f.write()) :: Nil // error def runAll2(xs: List[Proc]): Unit = - var cur: List[Proc] = xs + var cur: List[Proc] = xs // error while cur.nonEmpty do - val next: () => Unit = cur.head // error + val next: () => Unit = cur.head next() cur = cur.tail def runAll3(xs: List[Proc]): Unit = - val cur = Ref[List[Proc]](xs) + val cur = Ref[List[Proc]](xs) // error while cur.get.nonEmpty do - val next: () => Unit = cur.get.head // error + val next: () => Unit = cur.get.head next() cur.set(cur.get.tail: List[Proc]) diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index 6df092885384..a6f514addc95 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -7,7 +7,7 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:14:2 ----------------------------------------------------------- 14 | try // error | ^ - | result of `try` cannot have type () => Unit since + | The result of `try` cannot have type () => Unit since | that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. 15 | () => foo(1) @@ -17,7 +17,7 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:20:10 ---------------------------------------------------------- 20 | val x = try // error | ^ - | result of `try` cannot have type () => Unit since + | The result of `try` cannot have type () => Unit since | that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. 21 | () => foo(1) @@ -27,7 +27,7 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:26:10 ---------------------------------------------------------- 26 | val y = try // error | ^ - | result of `try` cannot have type () => Cell[Unit]^? since + | The result of `try` cannot have type () => (ex$8: caps.Exists) -> Cell[Unit]^? since | that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. 27 | () => Cell(foo(1)) @@ -37,7 +37,7 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:32:10 ---------------------------------------------------------- 32 | val b = try // error | ^ - | result of `try` cannot have type Cell[box () => Unit]^? since + | The result of `try` cannot have type Cell[box () => Unit]^? since | the part box () => Unit of that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. 33 | Cell(() => foo(1)) diff --git a/tests/neg-custom-args/captures/real-try.scala b/tests/neg-custom-args/captures/real-try.scala index 8e60d4fe7326..32e976f654a9 100644 --- a/tests/neg-custom-args/captures/real-try.scala +++ b/tests/neg-custom-args/captures/real-try.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import language.experimental.saferExceptions class Ex1 extends Exception("Ex1") diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 77a5fc06e05a..9c49c444c82f 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,12 +1,8 @@ --- Error: tests/neg-custom-args/captures/try.scala:25:3 ---------------------------------------------------------------- -23 | val a = handle[Exception, CanThrow[Exception]] { -24 | (x: CanThrow[Exception]) => x -25 | }{ // error (but could be better) - | ^ - | The expression's type box CT[Exception]^ is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. -26 | (ex: Exception) => ??? -27 | } +-- Error: tests/neg-custom-args/captures/try.scala:23:28 --------------------------------------------------------------- +23 | val a = handle[Exception, CanThrow[Exception]] { // error + | ^^^^^^^^^^^^^^^^^^^ + | Type variable R of method handle cannot be instantiated to box CT[Exception]^ since + | that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/try.scala:30:65 --------------------------------------------------------------- 30 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error | ^ diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index 45a1b346a512..a85a18f69caa 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -20,9 +20,9 @@ def handle[E <: Exception, R <: Top](op: CT[E]^ => R)(handler: E => R): R = catch case ex: E => handler(ex) def test = - val a = handle[Exception, CanThrow[Exception]] { + val a = handle[Exception, CanThrow[Exception]] { // error (x: CanThrow[Exception]) => x - }{ // error (but could be better) + }{ (ex: Exception) => ??? } diff --git a/tests/neg-custom-args/captures/unbox.scala b/tests/neg-custom-args/captures/unbox.scala index 33702a954068..28feb5f89aff 100644 --- a/tests/neg-custom-args/captures/unbox.scala +++ b/tests/neg-custom-args/captures/unbox.scala @@ -1,4 +1,4 @@ -import language.`3.2` +import language.`3.5` type Proc = () => Unit val xs: List[Proc] = ??? diff --git a/tests/neg-custom-args/captures/unsound-reach-2.scala b/tests/neg-custom-args/captures/unsound-reach-2.scala index 5bea18bdccba..c7dfa117a2fe 100644 --- a/tests/neg-custom-args/captures/unsound-reach-2.scala +++ b/tests/neg-custom-args/captures/unsound-reach-2.scala @@ -1,5 +1,3 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) import language.experimental.captureChecking trait Consumer[-T]: def apply(x: T): Unit @@ -11,7 +9,7 @@ def withFile[R](path: String)(op: Consumer[File]): R = ??? trait Foo[+X]: def use(x: File^)(op: Consumer[X]): Unit -class Bar extends Foo[File^]: +class Bar extends Foo[File^]: // error def use(x: File^)(op: Consumer[File^]): Unit = op.apply(x) def bad(): Unit = diff --git a/tests/neg-custom-args/captures/unsound-reach-3.scala b/tests/neg-custom-args/captures/unsound-reach-3.scala index 985beb7ae55d..c5cdfca9d87a 100644 --- a/tests/neg-custom-args/captures/unsound-reach-3.scala +++ b/tests/neg-custom-args/captures/unsound-reach-3.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import language.experimental.captureChecking trait File: def close(): Unit @@ -8,7 +8,7 @@ def withFile[R](path: String)(op: File^ => R): R = ??? trait Foo[+X]: def use(x: File^): X -class Bar extends Foo[File^]: +class Bar extends Foo[File^]: // error def use(x: File^): File^ = x def bad(): Unit = diff --git a/tests/neg-custom-args/captures/unsound-reach-4.check b/tests/neg-custom-args/captures/unsound-reach-4.check index ed8e29cdf511..ca95bf42ba59 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.check +++ b/tests/neg-custom-args/captures/unsound-reach-4.check @@ -1,3 +1,8 @@ +-- Error: tests/neg-custom-args/captures/unsound-reach-4.scala:13:18 --------------------------------------------------- +13 |class Bar extends Foo[File^]: // error + | ^^^^^^^^^^ + | Type variable X of trait Foo cannot be instantiated to File^ since + | that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/unsound-reach-4.scala:22:22 --------------------------------------------------- 22 | escaped = boom.use(f) // error | ^^^^^^^^^^^ diff --git a/tests/neg-custom-args/captures/unsound-reach-4.scala b/tests/neg-custom-args/captures/unsound-reach-4.scala index 14050b4afff2..88fbc2f5c1de 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.scala +++ b/tests/neg-custom-args/captures/unsound-reach-4.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import language.experimental.captureChecking trait File: def close(): Unit @@ -10,7 +10,7 @@ type F = File^ trait Foo[+X]: def use(x: F): X -class Bar extends Foo[File^]: +class Bar extends Foo[File^]: // error def use(x: F): File^ = x def bad(): Unit = diff --git a/tests/neg-custom-args/captures/unsound-reach.check b/tests/neg-custom-args/captures/unsound-reach.check index 4a6793d204c5..69794f569edb 100644 --- a/tests/neg-custom-args/captures/unsound-reach.check +++ b/tests/neg-custom-args/captures/unsound-reach.check @@ -1,12 +1,15 @@ --- Error: tests/neg-custom-args/captures/unsound-reach.scala:18:21 ----------------------------------------------------- -18 | boom.use(f): (f1: File^{backdoor*}) => // error +-- Error: tests/neg-custom-args/captures/unsound-reach.scala:9:18 ------------------------------------------------------ +9 |class Bar extends Foo[File^]: // error + | ^^^^^^^^^^ + | Type variable X of trait Foo cannot be instantiated to File^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/unsound-reach.scala:14:19 ----------------------------------------------------- +14 |class Bar2 extends Foo2[File^]: // error + | ^ + | Type variable X of constructor Foo2 cannot be instantiated to box File^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/unsound-reach.scala:23:21 ----------------------------------------------------- +23 | boom.use(f): (f1: File^{backdoor*}) => // error | ^ | Local reach capability backdoor* leaks into capture scope of method bad -19 | escaped = f1 --- [E164] Declaration Error: tests/neg-custom-args/captures/unsound-reach.scala:10:8 ----------------------------------- -10 | def use(x: File^)(op: File^ => Unit): Unit = op(x) // error, was OK using sealed checking - | ^ - | error overriding method use in trait Foo of type (x: File^)(op: box File^ => Unit): Unit; - | method use of type (x: File^)(op: File^ => Unit): Unit has incompatible type - | - | longer explanation available when compiling with `-explain` +24 | escaped = f1 diff --git a/tests/neg-custom-args/captures/unsound-reach.scala b/tests/neg-custom-args/captures/unsound-reach.scala index c3c31a7f32ff..3fb666c7c1fc 100644 --- a/tests/neg-custom-args/captures/unsound-reach.scala +++ b/tests/neg-custom-args/captures/unsound-reach.scala @@ -6,8 +6,13 @@ def withFile[R](path: String)(op: File^ => R): R = ??? trait Foo[+X]: def use(x: File^)(op: X => Unit): Unit -class Bar extends Foo[File^]: - def use(x: File^)(op: File^ => Unit): Unit = op(x) // error, was OK using sealed checking +class Bar extends Foo[File^]: // error + def use(x: File^)(op: File^ => Unit): Unit = op(x) // OK using sealed checking + +abstract class Foo2[+X](): + def use(x: File^)(op: X => Unit): Unit +class Bar2 extends Foo2[File^]: // error + def use(x: File^)(op: File^ => Unit): Unit = op(x) // OK using sealed checking def bad(): Unit = val backdoor: Foo[File^] = new Bar diff --git a/tests/neg-custom-args/captures/vars-simple.check b/tests/neg-custom-args/captures/vars-simple.check index 2ef301b6ec1f..2bc014e9a4e7 100644 --- a/tests/neg-custom-args/captures/vars-simple.check +++ b/tests/neg-custom-args/captures/vars-simple.check @@ -2,17 +2,14 @@ 15 | a = (g: String => String) // error | ^^^^^^^^^^^^^^^^^^^ | Found: String => String - | Required: box String ->{cap1, cap2} String - | - | Note that String => String cannot be box-converted to box String ->{cap1, cap2} String - | since at least one of their capture sets contains the root capability `cap` + | Required: String ->{cap1, cap2} String | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:16:8 ----------------------------------- 16 | a = g // error | ^ - | Found: box (x: String) ->{cap3} String - | Required: box (x: String) ->{cap1, cap2} String + | Found: (x: String) ->{cap3} String + | Required: (x: String) ->{cap1, cap2} String | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:17:12 ---------------------------------- diff --git a/tests/neg-custom-args/captures/vars.scala b/tests/neg-custom-args/captures/vars.scala index 5eb1e3fedda9..eb9719cd2adf 100644 --- a/tests/neg-custom-args/captures/vars.scala +++ b/tests/neg-custom-args/captures/vars.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + class CC type Cap = CC^ diff --git a/tests/neg-custom-args/captures/widen-reach.check b/tests/neg-custom-args/captures/widen-reach.check index abc065a1e2bc..9fe1f2bd5de6 100644 --- a/tests/neg-custom-args/captures/widen-reach.check +++ b/tests/neg-custom-args/captures/widen-reach.check @@ -1,3 +1,8 @@ +-- Error: tests/neg-custom-args/captures/widen-reach.scala:8:18 -------------------------------------------------------- +8 |trait Bar extends Foo[IO^]: // error + | ^^^^^^^^ + | Type variable T of trait Foo cannot be instantiated to IO^ since + | that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/widen-reach.scala:13:26 ------------------------------------------------------- 13 | val y2: IO^ -> IO^ = y1.foo // error | ^^^^^^ @@ -8,10 +13,3 @@ | ^^^^^^ | Local reach capability x* leaks into capture scope of method test. | To allow this, the parameter x should be declared with a @use annotation --- [E164] Declaration Error: tests/neg-custom-args/captures/widen-reach.scala:9:6 -------------------------------------- -9 | val foo: IO^ -> IO^ = x => x // error - | ^ - | error overriding value foo in trait Foo of type IO^ -> box IO^; - | value foo of type IO^ -> (ex$3: caps.Exists) -> IO^{ex$3} has incompatible type - | - | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/widen-reach.scala b/tests/neg-custom-args/captures/widen-reach.scala index fa5eee1232df..9a9305640473 100644 --- a/tests/neg-custom-args/captures/widen-reach.scala +++ b/tests/neg-custom-args/captures/widen-reach.scala @@ -5,8 +5,8 @@ trait IO trait Foo[+T]: val foo: IO^ -> T -trait Bar extends Foo[IO^]: - val foo: IO^ -> IO^ = x => x // error +trait Bar extends Foo[IO^]: // error + val foo: IO^ -> IO^ = x => x def test(x: Foo[IO^]): Unit = val y1: Foo[IO^{x*}] = x diff --git a/tests/pos-custom-args/captures/hk-param.scala b/tests/pos-custom-args/captures/hk-param.scala index df4335069bbb..325a2b55a480 100644 --- a/tests/pos-custom-args/captures/hk-param.scala +++ b/tests/pos-custom-args/captures/hk-param.scala @@ -1,3 +1,5 @@ +//> using options -source 3.5 +// (to make sure we use the unsealed policy) /** Concrete collection type: View */ trait View[+A] extends Itable[A], ILike[A, [X] =>> View[X]^]: override def fromIterable[B](c: Itable[B]^): View[B]^{c} = ??? diff --git a/tests/pos-custom-args/captures/i15923-cases.scala b/tests/pos-custom-args/captures/i15923-cases.scala index 4b5a36f208ec..c48ca430c440 100644 --- a/tests/pos-custom-args/captures/i15923-cases.scala +++ b/tests/pos-custom-args/captures/i15923-cases.scala @@ -2,6 +2,10 @@ trait Cap { def use(): Int } type Id[X] = [T] -> (op: X => T) -> T def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x) +def foo(x: Id[Cap^]) = { + x(_.use()) // OK under sealed policy +} + def bar(io: Cap^, x: Id[Cap^{io}]) = { x(_.use()) } diff --git a/tests/pos-custom-args/captures/levels.scala b/tests/pos-custom-args/captures/levels.scala index cabd537442a5..4d9d759e86db 100644 --- a/tests/pos-custom-args/captures/levels.scala +++ b/tests/pos-custom-args/captures/levels.scala @@ -1,3 +1,5 @@ +//> using options -source 3.5 +// (to make sure we use the unsealed policy) class CC def test1(cap1: CC^) = @@ -14,10 +16,10 @@ def test2(cap1: CC^) = def setV(x: T): Unit = v = x def getV: T = v - val _ = Ref[String => String]((x: String) => x) // ok + val _ = Ref[String => String]((x: String) => x) val r = Ref((x: String) => x) def scope(cap3: CC^) = def g(x: String): String = if cap3 == cap3 then "" else "a" - r.setV(g) // error + r.setV(g) () From 62b2f1c250fc6934f5929b1ba3123b9b60bb5a72 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 2 Nov 2024 12:04:06 +0100 Subject: [PATCH 038/202] Follow upper bounds of type variables when computing dcs --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 4 ++++ .../dotty/tools/dotc/cc/CheckCaptures.scala | 2 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 4 ++++ tests/neg-custom-args/captures/dcs-tvar.check | 10 +++++++++ tests/neg-custom-args/captures/dcs-tvar.scala | 9 ++++++++ tests/neg-custom-args/captures/i21646.scala | 2 +- .../captures/unsound-reach-6.scala | 22 +++++++++++++++++++ .../captures/use-override.scala | 15 +++++++++++++ tests/pos-custom-args/captures/i21646.scala | 13 +++++++++++ 9 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 tests/neg-custom-args/captures/dcs-tvar.check create mode 100644 tests/neg-custom-args/captures/dcs-tvar.scala create mode 100644 tests/neg-custom-args/captures/unsound-reach-6.scala create mode 100644 tests/neg-custom-args/captures/use-override.scala create mode 100644 tests/pos-custom-args/captures/i21646.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 835e413463bd..cde0431e3e86 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1120,6 +1120,7 @@ object CaptureSet: */ def ofTypeDeeply(tp: Type)(using Context): CaptureSet = val collect = new TypeAccumulator[CaptureSet]: + val seen = util.HashSet[Symbol]() def apply(cs: CaptureSet, t: Type) = if variance <= 0 then cs else t.dealias match @@ -1127,6 +1128,9 @@ object CaptureSet: this(cs, p) ++ cs1 case t @ AnnotatedType(parent, ann) => this(cs, parent) + case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) => + seen += t.symbol + this(cs, t.info.bounds.hi) case t @ FunctionOrMethod(args, res @ Existential(_, _)) if args.forall(_.isAlwaysPure) => this(cs, Existential.toCap(res)) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index cce5a7cd21dd..7a5c8b884ffc 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -660,7 +660,7 @@ class CheckCaptures extends Recheck, SymTransformer: for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol if !getter.is(Private) && getter.hasTrackedParts then - refined = RefinedType(refined, getterName, argType) + refined = RefinedType(refined, getterName, argType.unboxed) allCaptures ++= argType.captureSet (refined, allCaptures) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 56afaa1b33fb..dc78761b76e8 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -81,11 +81,15 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: private def newFlagsFor(symd: SymDenotation)(using Context): FlagSet = object containsCovarRetains extends TypeAccumulator[Boolean]: + val seen = util.HashSet[Symbol]() def apply(x: Boolean, tp: Type): Boolean = if x then true else if tp.derivesFromCapability && variance >= 0 then true else tp match case AnnotatedType(_, ann) if ann.symbol.isRetains && variance >= 0 => true + case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) => + seen += t.symbol + apply(x, t.info.bounds.hi) case _ => foldOver(x, tp) def apply(tp: Type): Boolean = apply(false, tp) diff --git a/tests/neg-custom-args/captures/dcs-tvar.check b/tests/neg-custom-args/captures/dcs-tvar.check new file mode 100644 index 000000000000..d3caa720e88a --- /dev/null +++ b/tests/neg-custom-args/captures/dcs-tvar.check @@ -0,0 +1,10 @@ +-- Error: tests/neg-custom-args/captures/dcs-tvar.scala:6:15 ----------------------------------------------------------- +6 | () => runOps(xs) // error + | ^^ + | reference xs* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit +-- Error: tests/neg-custom-args/captures/dcs-tvar.scala:9:15 ----------------------------------------------------------- +9 | () => runOps(xs) // error + | ^^ + | reference xs* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit diff --git a/tests/neg-custom-args/captures/dcs-tvar.scala b/tests/neg-custom-args/captures/dcs-tvar.scala new file mode 100644 index 000000000000..381c08b4d351 --- /dev/null +++ b/tests/neg-custom-args/captures/dcs-tvar.scala @@ -0,0 +1,9 @@ +import caps.use + +def runOps(@use xs: List[() => Unit]): Unit = ??? + +def f[T <: List[() => Unit]](xs: T): () -> Unit = + () => runOps(xs) // error + +def g[T <: List[U], U <: () => Unit](xs: T): () -> Unit = + () => runOps(xs) // error diff --git a/tests/neg-custom-args/captures/i21646.scala b/tests/neg-custom-args/captures/i21646.scala index cf451ded3120..92aba9fda5d1 100644 --- a/tests/neg-custom-args/captures/i21646.scala +++ b/tests/neg-custom-args/captures/i21646.scala @@ -9,5 +9,5 @@ class Resource[T <: Capability](gen: T): @main def run = val myFile: File = ??? - val r = Resource(myFile) // error + val r = Resource(myFile) // now ok, was error () diff --git a/tests/neg-custom-args/captures/unsound-reach-6.scala b/tests/neg-custom-args/captures/unsound-reach-6.scala new file mode 100644 index 000000000000..b7306dca4190 --- /dev/null +++ b/tests/neg-custom-args/captures/unsound-reach-6.scala @@ -0,0 +1,22 @@ +class IO + +def f(xs: List[() => Unit]): () => Unit = () => + println(xs.head) // error + +def test(io: IO^)(ys: List[() ->{io} Unit]) = + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! ys* gets lost + () + +def test(io: IO^) = + def ys: List[() ->{io} Unit] = ??? + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! io gets lost + () + + + diff --git a/tests/neg-custom-args/captures/use-override.scala b/tests/neg-custom-args/captures/use-override.scala new file mode 100644 index 000000000000..febb59ca4208 --- /dev/null +++ b/tests/neg-custom-args/captures/use-override.scala @@ -0,0 +1,15 @@ +import caps.use + +def test(io: Object^, async: Object^) = + + trait A: + def f(@use x: List[() ->{io} Unit]): Unit + + class B extends A: + def f(@use x: List[() => Unit]): Unit = // error, would be unsound if allowed + x.foreach(_()) + + class C extends A: + def f(@use x: List[() ->{io, async} Unit]): Unit = // error, this one could be soundly allowed actually + x.foreach(_()) + diff --git a/tests/pos-custom-args/captures/i21646.scala b/tests/pos-custom-args/captures/i21646.scala new file mode 100644 index 000000000000..cf451ded3120 --- /dev/null +++ b/tests/pos-custom-args/captures/i21646.scala @@ -0,0 +1,13 @@ +import language.experimental.captureChecking +import caps.Capability + +trait File extends Capability + +class Resource[T <: Capability](gen: T): + def use[U](f: T => U): U = + f(gen) // OK, was error under unsealed + +@main def run = + val myFile: File = ??? + val r = Resource(myFile) // error + () From 06764503f01b45fac467557c4399b84b967fa77d Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 2 Nov 2024 12:19:08 +0100 Subject: [PATCH 039/202] Check that @use annotations only appear for method and class parameters --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 11 +++++++++++ tests/neg-custom-args/captures/bad-uses-2.scala | 6 ++++++ tests/neg-custom-args/captures/bad-uses.scala | 3 +++ tests/neg-custom-args/captures/i21646.scala | 13 ------------- tests/pending/pos/function-contravariance.scala | 10 ++++++++++ tests/pos-custom-args/captures/i21646.scala | 2 +- 6 files changed, 31 insertions(+), 14 deletions(-) create mode 100644 tests/neg-custom-args/captures/bad-uses-2.scala create mode 100644 tests/neg-custom-args/captures/bad-uses.scala delete mode 100644 tests/neg-custom-args/captures/i21646.scala create mode 100644 tests/pending/pos/function-contravariance.scala diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index dc78761b76e8..647e5a2cfd51 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -495,6 +495,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => traverseChildren(tree) postProcess(tree) + checkProperUse(tree) end traverse def postProcess(tree: Tree)(using Context): Unit = tree match @@ -637,6 +638,16 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => case _ => end postProcess + + def checkProperUse(tree: Tree)(using Context): Unit = tree match + case tree: MemberDef => + def useAllowed(sym: Symbol) = + (sym.is(Param) || sym.is(ParamAccessor)) && !sym.owner.isAnonymousFunction + for ann <- tree.symbol.annotations do + if ann.symbol == defn.UseAnnot && !useAllowed(tree.symbol) then + report.error(i"Only parameters of methods can have @use annotations", tree.srcPos) + case _ => + end checkProperUse end setupTraverser /** Checks whether an abstract type could be impure. See also: [[needsVariable]]. */ diff --git a/tests/neg-custom-args/captures/bad-uses-2.scala b/tests/neg-custom-args/captures/bad-uses-2.scala new file mode 100644 index 000000000000..00eec3e5a2e9 --- /dev/null +++ b/tests/neg-custom-args/captures/bad-uses-2.scala @@ -0,0 +1,6 @@ +import caps.use +class Test: + @use def F = ??? // error + @use val x = ??? // error + @use type T // error + def foo(@use c: Test): Unit = ??? // OK diff --git a/tests/neg-custom-args/captures/bad-uses.scala b/tests/neg-custom-args/captures/bad-uses.scala new file mode 100644 index 000000000000..c21976ebb3cf --- /dev/null +++ b/tests/neg-custom-args/captures/bad-uses.scala @@ -0,0 +1,3 @@ +import caps.use +class Test: + val bar = (@use c: Test) => () // error diff --git a/tests/neg-custom-args/captures/i21646.scala b/tests/neg-custom-args/captures/i21646.scala deleted file mode 100644 index 92aba9fda5d1..000000000000 --- a/tests/neg-custom-args/captures/i21646.scala +++ /dev/null @@ -1,13 +0,0 @@ -import language.experimental.captureChecking -import caps.Capability - -trait File extends Capability - -class Resource[T <: Capability](gen: T): - def use[U](f: T => U): U = - f(gen) // OK, was error under unsealed - -@main def run = - val myFile: File = ??? - val r = Resource(myFile) // now ok, was error - () diff --git a/tests/pending/pos/function-contravariance.scala b/tests/pending/pos/function-contravariance.scala new file mode 100644 index 000000000000..4df88f890bed --- /dev/null +++ b/tests/pending/pos/function-contravariance.scala @@ -0,0 +1,10 @@ +import language.experimental.namedTuples + +class A: + type T + +class B extends A + +val f: (x: A) => x.T = ??? +val g: (x: B) => x.T = f // OK +val h: (x: A) => x.T = g // error diff --git a/tests/pos-custom-args/captures/i21646.scala b/tests/pos-custom-args/captures/i21646.scala index cf451ded3120..92aba9fda5d1 100644 --- a/tests/pos-custom-args/captures/i21646.scala +++ b/tests/pos-custom-args/captures/i21646.scala @@ -9,5 +9,5 @@ class Resource[T <: Capability](gen: T): @main def run = val myFile: File = ??? - val r = Resource(myFile) // error + val r = Resource(myFile) // now ok, was error () From 96d1610fe8ba71b3e2d6e8d84744e7d982a9c9bc Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 2 Nov 2024 15:50:10 +0100 Subject: [PATCH 040/202] Refactorings and drop useExistential Config option --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 27 +-- .../dotty/tools/dotc/cc/CheckCaptures.scala | 165 ++++++++---------- .../src/dotty/tools/dotc/cc/Existential.scala | 6 +- 3 files changed, 76 insertions(+), 122 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 97c774b593a6..2dc2bbf77abe 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -38,10 +38,6 @@ object ccConfig: */ inline val deferredReaches = false - /** If true, use existential capture set variables */ - def useExistentials(using Context) = - Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.5`) - /** If true, use "sealed" as encapsulation mechanism, meaning that we * check that type variable instantiations don't have `cap` in any of * their capture sets. This is an alternative of the original restriction @@ -479,17 +475,6 @@ extension (tp: Type) * occurrences of cap are allowed in instance types of type variables. */ def withReachCaptures(ref: Type)(using Context): Type = - class CheckContraCaps extends TypeTraverser: - var ok = true - def traverse(t: Type): Unit = - if ok then - t.dealias match - case CapturingType(_, cs) if cs.isUniversal && variance <= 0 => - ok = false - case _ => - traverseChildren(t) - end CheckContraCaps - object narrowCaps extends TypeMap: def apply(t: Type) = if variance <= 0 then t @@ -512,15 +497,9 @@ extension (tp: Type) ref match case ref: CaptureRef if ref.isTrackableRef => - val checker = new CheckContraCaps - if !ccConfig.useExistentials then checker.traverse(tp) - if checker.ok then - val tp1 = narrowCaps(tp) - if tp1 ne tp then capt.println(i"narrow $tp of $ref to $tp1") - tp1 - else - capt.println(i"cannot narrow $tp of $ref") - tp + val tp1 = narrowCaps(tp) + if tp1 ne tp then capt.println(i"narrow $tp of $ref to $tp1") + tp1 case _ => tp diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 7a5c8b884ffc..8a7231866742 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -121,6 +121,9 @@ object CheckCaptures: def inverse = thisMap end SubstParamsBiMap + /** A prototype that indicates selection with an immutable value */ + class PathSelectionProto(val sym: Symbol, val pt: Type)(using Context) extends WildcardSelectionProto + /** Check that a @retains annotation only mentions references that can be tracked. * This check is performed at Typer. */ @@ -144,9 +147,9 @@ object CheckCaptures: case ReachCapabilityApply(arg) => check(arg, elem.srcPos) case _ => check(elem, elem.srcPos) - /** Report an error if some part of `tp` contains the root capability in its capture set - * or if it refers to an unsealed type parameter that could possibly be instantiated with - * cap in a way that's visible at the type. + /** Under the sealed policy, report an error if some part of `tp` contains the + * root capability in its capture set or if it refers to a type parameter that + * could possibly be instantiated with cap in a way that's visible at the type. */ private def disallowRootCapabilitiesIn(tp: Type, carrier: Symbol, what: String, have: String, addendum: String, pos: SrcPos)(using Context) = val check = new TypeTraverser: @@ -182,8 +185,66 @@ object CheckCaptures: if ccConfig.useSealed then check.traverse(tp) end disallowRootCapabilitiesIn - /** A prototype that indicates selection with an immutable value */ - class PathSelectionProto(val sym: Symbol, val pt: Type)(using Context) extends WildcardSelectionProto + /** Under the sealed policy, disallow the root capability in type arguments. + * Type arguments come either from a TypeApply node or from an AppliedType + * which represents a trait parent in a template. + * @param fn the type application, of type TypeApply or TypeTree + * @param sym the constructor symbol (could be a method or a val or a class) + * @param args the type arguments + */ + private def disallowCapInTypeArgs(fn: Tree, sym: Symbol, args: List[Tree], thisPhase: Phase)(using Context): Unit = + def isExempt = sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue + if ccConfig.useSealed && !isExempt then + val paramNames = atPhase(thisPhase.prev): + fn.tpe.widenDealias match + case tl: TypeLambda => tl.paramNames + case ref: AppliedType if ref.typeSymbol.isClass => ref.typeSymbol.typeParams.map(_.name) + case t => + println(i"parent type: $t") + args.map(_ => EmptyTypeName) + for case (arg: TypeTree, pname) <- args.lazyZip(paramNames) do + def where = if sym.exists then i" in an argument of $sym" else "" + val (addendum, pos) = + if arg.isInferred + then ("\nThis is often caused by a local capability$where\nleaking as part of its result.", fn.srcPos) + else if arg.span.exists then ("", arg.srcPos) + else ("", fn.srcPos) + disallowRootCapabilitiesIn(arg.knownType, NoSymbol, + i"Type variable $pname of $sym", "be instantiated to", addendum, pos) + end disallowCapInTypeArgs + + /** If we are not under the sealed policy, and a tree is an application that unboxes + * its result or is a try, check that the tree's type does not have covariant universal + * capabilities. + */ + private def checkNotUniversalInUnboxedResult(tpe: Type, tree: Tree)(using Context): Unit = + def needsUniversalCheck = tree match + case _: RefTree | _: Apply | _: TypeApply => tree.symbol.unboxesResult + case _: Try => true + case _ => false + + object checkNotUniversal extends TypeTraverser: + def traverse(tp: Type) = + tp.dealias match + case wtp @ CapturingType(parent, refs) => + if variance > 0 then + refs.disallowRootCapability: () => + def part = if wtp eq tpe.widen then "" else i" in its part $wtp" + report.error( + em"""The expression's type ${tpe.widen} is not allowed to capture the root capability `cap`$part. + |This usually means that a capability persists longer than its allowed lifetime.""", + tree.srcPos) + if !wtp.isBoxed then traverse(parent) + case tp => + traverseChildren(tp) + + if !ccConfig.useSealed + && !tpe.hasAnnotation(defn.UncheckedCapturesAnnot) + && needsUniversalCheck + && tpe.widen.isValueType + then + checkNotUniversal.traverse(tpe.widen) + end checkNotUniversalInUnboxedResult class CheckCaptures extends Recheck, SymTransformer: thisPhase => @@ -269,41 +330,6 @@ class CheckCaptures extends Recheck, SymTransformer: def showRef(ref: CaptureRef)(using Context): String = ctx.printer.toTextCaptureRef(ref).show - // Uses 4-space indent as a trial - private def checkReachCapsIsolated(tpe: Type, pos: SrcPos)(using Context): Unit = - - object checker extends TypeTraverser: - var refVariances: Map[Boolean, Int] = Map.empty - var seenReach: CaptureRef | Null = null - def traverse(tp: Type) = - tp.dealias match - case CapturingType(parent, refs) => - traverse(parent) - for ref <- refs.elems do - if ref.isReach && !ref.stripReach.isInstanceOf[TermParamRef] - || ref.isRootCapability - then - val isReach = ref.isReach - def register() = - refVariances = refVariances.updated(isReach, variance) - seenReach = ref - refVariances.get(isReach) match - case None => register() - case Some(v) => if v != 0 && variance == 0 then register() - case _ => - traverseChildren(tp) - - checker.traverse(tpe) - if checker.refVariances.size == 2 - && checker.refVariances(true) >= 0 - && checker.refVariances(false) <= 0 - then - report.error( - em"""Reach capability ${showRef(checker.seenReach.nn)} and universal capability cap cannot both - |appear in the type $tpe of this expression""", - pos) - end checkReachCapsIsolated - /** The current environment */ private val rootEnv: Env = inContext(ictx): Env(defn.RootClass, EnvKind.Regular, CaptureSet.empty, null) @@ -685,32 +711,11 @@ class CheckCaptures extends Recheck, SymTransformer: else ownType end instantiate - def disallowCapInTypeArgs(fn: Tree, sym: Symbol, args: List[Tree])(using Context): Unit = - def isExempt = sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue - if ccConfig.useSealed && !isExempt then - val paramNames = atPhase(thisPhase.prev): - fn.tpe.widenDealias match - case tl: TypeLambda => tl.paramNames - case ref: AppliedType if ref.typeSymbol.isClass => ref.typeSymbol.typeParams.map(_.name) - case t => - println(i"parent type: $t") - args.map(_ => EmptyTypeName) - for case (arg: TypeTree, pname) <- args.lazyZip(paramNames) do - def where = if sym.exists then i" in an argument of $sym" else "" - val (addendum, pos) = - if arg.isInferred - then ("\nThis is often caused by a local capability$where\nleaking as part of its result.", fn.srcPos) - else if arg.span.exists then ("", arg.srcPos) - else ("", fn.srcPos) - disallowRootCapabilitiesIn(arg.knownType, NoSymbol, - i"Type variable $pname of $sym", "be instantiated to", addendum, pos) - end disallowCapInTypeArgs - override def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = val meth = tree.fun match case fun @ Select(qual, nme.apply) => qual.symbol.orElse(fun.symbol) case fun => fun.symbol - disallowCapInTypeArgs(tree.fun, meth, tree.args) + disallowCapInTypeArgs(tree.fun, meth, tree.args, thisPhase) val res = Existential.toCap(super.recheckTypeApply(tree, pt)) includeCallCaptures(tree.symbol, res, tree.srcPos) checkContains(tree) @@ -940,7 +945,7 @@ class CheckCaptures extends Recheck, SymTransformer: for case tpt: TypeTree <- impl.parents do tpt.tpe match case AppliedType(fn, args) => - disallowCapInTypeArgs(tpt, fn.typeSymbol, args.map(TypeTree(_))) + disallowCapInTypeArgs(tpt, fn.typeSymbol, args.map(TypeTree(_)), thisPhase) case _ => inNestedLevelUnless(cls.is(Module)): super.recheckClassDef(tree, impl, cls) @@ -1008,40 +1013,12 @@ class CheckCaptures extends Recheck, SymTransformer: report.error(ex.getMessage.nn) tree.tpe finally curEnv = saved - if tree.isTerm then - if !ccConfig.useExistentials then - checkReachCapsIsolated(res.widen, tree.srcPos) - if !pt.isBoxedCapturing && pt != LhsProto then - markFree(res.boxedCaptureSet, tree.srcPos) + if tree.isTerm && !pt.isBoxedCapturing && pt != LhsProto then + markFree(res.boxedCaptureSet, tree.srcPos) res override def recheckFinish(tpe: Type, tree: Tree, pt: Type)(using Context): Type = - def needsUniversalCheck = tree match - case _: RefTree | _: Apply | _: TypeApply => tree.symbol.unboxesResult - case _: Try => true - case _ => false - - object checkNotUniversal extends TypeTraverser: - def traverse(tp: Type) = - tp.dealias match - case wtp @ CapturingType(parent, refs) => - if variance > 0 then - refs.disallowRootCapability: () => - def part = if wtp eq tpe.widen then "" else i" in its part $wtp" - report.error( - em"""The expression's type ${tpe.widen} is not allowed to capture the root capability `cap`$part. - |This usually means that a capability persists longer than its allowed lifetime.""", - tree.srcPos) - if !wtp.isBoxed then traverse(parent) - case tp => - traverseChildren(tp) - - if !ccConfig.useSealed - && !tpe.hasAnnotation(defn.UncheckedCapturesAnnot) - && needsUniversalCheck - && tpe.widen.isValueType - then - checkNotUniversal.traverse(tpe.widen) + checkNotUniversalInUnboxedResult(tpe, tree) super.recheckFinish(tpe, tree, pt) end recheckFinish diff --git a/compiler/src/dotty/tools/dotc/cc/Existential.scala b/compiler/src/dotty/tools/dotc/cc/Existential.scala index 732510789e28..d51ce1b08dbd 100644 --- a/compiler/src/dotty/tools/dotc/cc/Existential.scala +++ b/compiler/src/dotty/tools/dotc/cc/Existential.scala @@ -333,10 +333,8 @@ object Existential: override def toString = "Wrap.inverse" end Wrap - if ccConfig.useExistentials then - val wrapped = apply(Wrap(_)(tp)) - if needsWrap then wrapped else tp - else tp + val wrapped = apply(Wrap(_)(tp)) + if needsWrap then wrapped else tp end mapCap def mapCapInResults(fail: Message => Unit)(using Context): TypeMap = new: From adf6a257e44935016ded5a73a7df8949b1627624 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 2 Nov 2024 21:44:40 +0100 Subject: [PATCH 041/202] Refactoring: Use common code for all markFree variants --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 1 + .../dotty/tools/dotc/cc/CheckCaptures.scala | 28 +++++++++++++------ tests/neg-custom-args/captures/capt1.check | 8 +++--- tests/neg-custom-args/captures/cc-this5.check | 2 +- tests/neg-custom-args/captures/eta.check | 2 +- .../captures/exception-definitions.check | 2 +- tests/neg-custom-args/captures/i15772.check | 4 +-- tests/neg-custom-args/captures/i16114.check | 8 +++--- .../neg-custom-args/captures/lazylists2.check | 4 +-- .../captures/leaked-curried.check | 4 +-- tests/neg-custom-args/captures/try.check | 4 +-- 11 files changed, 39 insertions(+), 28 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 2dc2bbf77abe..8e684e212f3b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -290,6 +290,7 @@ extension (tp: Type) case _: ThisType | NoPrefix => tp1.symbol.is(Param) || tp1.symbol.is(ParamAccessor) case prefix => prefix.isParamPath + case _: ParamRef => true case _ => false /** If this is a unboxed capturing type with nonempty capture set, its boxed version. diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 8a7231866742..d35cbc3700e2 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -390,14 +390,7 @@ class CheckCaptures extends Recheck, SymTransformer: markFree(sym, sym.termRef, pos) def markFree(sym: Symbol, ref: TermRef, pos: SrcPos)(using Context): Unit = - if sym.exists && ref.isTracked then - def recur(env: Env): Unit = - if env.isOpen && env.owner != sym.enclosure then - capt.println(i"Mark $sym with cs ${ref.captureSet} free in ${env.owner}") - checkElem(ref, env.captured, pos, provenance(env)) - if !isOfNestedMethod(env) then - recur(nextEnvToCharge(env, _.owner != sym.enclosure)) - recur(curEnv) + if sym.exists && ref.isTracked then markFree(ref.captureSet, pos) /** Make sure (projected) `cs` is a subset of the capture sets of all enclosing * environments. At each stage, only include references from `cs` that are outside @@ -434,7 +427,24 @@ class CheckCaptures extends Recheck, SymTransformer: case ref => false if !isVisible then - c match + //println(i"out of scope: $c") + if ccConfig.deferredReaches then // avoid all locally bound capabilities + if c.isParamPath then + c match + case ReachCapability(_) | _: TypeRef => + checkUseDeclared(c, env, lastEnv) + case _ => + else + val underlying = c match + case ReachCapability(c1) => + CaptureSet.ofTypeDeeply(c1.widen) + case _ => + CaptureSet.ofType(c.widen, followResult = false) + capt.println(i"Widen reach $c to $underlying in ${env.owner}") + underlying.disallowRootCapability: () => + report.error(em"Local capability $c in ${env.ownerString} cannot have `cap` as underlying capture set", pos) + recur(underlying, env, lastEnv) + else c match // avoid only reach capabilities and capture sets case ReachCapability(c1) => if c1.isParamPath then checkUseDeclared(c, env, lastEnv) diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index 94103799d698..f63c55ca48c4 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -1,12 +1,12 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:6:11 -------------------------------------------------------------- 6 | () => if x == null then y else y // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (x : C^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> C -- Error: tests/neg-custom-args/captures/capt1.scala:9:11 -------------------------------------------------------------- 9 | () => if x == null then y else y // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (x : C^) is not included in the allowed capture set {} | of an enclosing function literal with expected type Matchable -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:16:2 ----------------------------------------- 16 | def f(y: Int) = if x == null then y else y // error @@ -41,8 +41,8 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:34:30 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> box C^ + | reference (x : C^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> box C^ -- Error: tests/neg-custom-args/captures/capt1.scala:36:13 ------------------------------------------------------------- 36 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/neg-custom-args/captures/cc-this5.check b/tests/neg-custom-args/captures/cc-this5.check index 8affe7005e2e..21b5b36e0574 100644 --- a/tests/neg-custom-args/captures/cc-this5.check +++ b/tests/neg-custom-args/captures/cc-this5.check @@ -1,7 +1,7 @@ -- Error: tests/neg-custom-args/captures/cc-this5.scala:16:20 ---------------------------------------------------------- 16 | def f = println(c) // error | ^ - | (c : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (c : Cap^) is not included in the allowed capture set {} | of the enclosing class A -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-this5.scala:21:15 ------------------------------------- 21 | val x: A = this // error diff --git a/tests/neg-custom-args/captures/eta.check b/tests/neg-custom-args/captures/eta.check index 9850e54a7fdf..b7669e9b68ea 100644 --- a/tests/neg-custom-args/captures/eta.check +++ b/tests/neg-custom-args/captures/eta.check @@ -8,5 +8,5 @@ -- Error: tests/neg-custom-args/captures/eta.scala:6:20 ---------------------------------------------------------------- 6 | bar( () => f ) // error | ^ - | (f : Proc^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (f : Proc^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> box () ->? Unit diff --git a/tests/neg-custom-args/captures/exception-definitions.check b/tests/neg-custom-args/captures/exception-definitions.check index 7f915ebd9833..3f2b15f312b9 100644 --- a/tests/neg-custom-args/captures/exception-definitions.check +++ b/tests/neg-custom-args/captures/exception-definitions.check @@ -5,7 +5,7 @@ -- Error: tests/neg-custom-args/captures/exception-definitions.scala:7:12 ---------------------------------------------- 7 | val x = c // error | ^ - |(c : Any^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Err2 + | reference (c : Any^) is not included in the allowed capture set {} of the self type of class Err2 -- Error: tests/neg-custom-args/captures/exception-definitions.scala:8:13 ---------------------------------------------- 8 | class Err3(c: Any^) extends Exception // error | ^ diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 0f8f0bf6eac5..67685d5663b8 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -1,7 +1,7 @@ -- Error: tests/neg-custom-args/captures/i15772.scala:19:26 ------------------------------------------------------------ 19 | val c : C^{x} = new C(x) // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (x : C^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> Int -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:20:46 --------------------------------------- 20 | val boxed1 : ((C^) => Unit) -> Unit = box1(c) // error @@ -13,7 +13,7 @@ -- Error: tests/neg-custom-args/captures/i15772.scala:26:26 ------------------------------------------------------------ 26 | val c : C^{x} = new C(x) // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (x : C^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> Int -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:27:35 --------------------------------------- 27 | val boxed2 : Observe[C^] = box2(c) // error diff --git a/tests/neg-custom-args/captures/i16114.check b/tests/neg-custom-args/captures/i16114.check index 3b9fbd40493f..745ccea1f905 100644 --- a/tests/neg-custom-args/captures/i16114.check +++ b/tests/neg-custom-args/captures/i16114.check @@ -6,7 +6,7 @@ -- Error: tests/neg-custom-args/captures/i16114.scala:20:8 ------------------------------------------------------------- 20 | fs // error (limitation) | ^^ - | (fs : Cap^) cannot be referenced here; it is not included in the allowed capture set {io} + | reference (fs : Cap^) is not included in the allowed capture set {io} | of an enclosing function literal with expected type Unit ->{io} Unit -- Error: tests/neg-custom-args/captures/i16114.scala:24:13 ------------------------------------------------------------ 24 | expect[Cap^] { // error @@ -16,7 +16,7 @@ -- Error: tests/neg-custom-args/captures/i16114.scala:26:8 ------------------------------------------------------------- 26 | io // error (limitation) | ^^ - | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {fs} + | reference (io : Cap^) is not included in the allowed capture set {fs} | of an enclosing function literal with expected type Unit ->{fs} Unit -- Error: tests/neg-custom-args/captures/i16114.scala:30:13 ------------------------------------------------------------ 30 | expect[Cap^] { // error @@ -36,10 +36,10 @@ -- Error: tests/neg-custom-args/captures/i16114.scala:40:8 ------------------------------------------------------------- 40 | io.use() // error | ^^ - | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (io : Cap^) is not included in the allowed capture set {} | of an enclosing function literal with expected type Unit -> Unit -- Error: tests/neg-custom-args/captures/i16114.scala:41:8 ------------------------------------------------------------- 41 | io // error | ^^ - | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (io : Cap^) is not included in the allowed capture set {} | of an enclosing function literal with expected type Unit -> Unit diff --git a/tests/neg-custom-args/captures/lazylists2.check b/tests/neg-custom-args/captures/lazylists2.check index 13b1da6eaf1c..d1883cc54718 100644 --- a/tests/neg-custom-args/captures/lazylists2.check +++ b/tests/neg-custom-args/captures/lazylists2.check @@ -25,11 +25,11 @@ -- Error: tests/neg-custom-args/captures/lazylists2.scala:40:20 -------------------------------------------------------- 40 | def head: B = f(xs.head) // error | ^ - |(f : A => B) cannot be referenced here; it is not included in the allowed capture set {xs} of the self type of class Mapped + | reference (f : A => B) is not included in the allowed capture set {xs} of the self type of class Mapped -- Error: tests/neg-custom-args/captures/lazylists2.scala:41:48 -------------------------------------------------------- 41 | def tail: LazyList[B]^{this}= xs.tail.map(f) // error | ^ - |(f : A => B) cannot be referenced here; it is not included in the allowed capture set {xs} of the self type of class Mapped + | reference (f : A => B) is not included in the allowed capture set {xs} of the self type of class Mapped -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:45:4 ------------------------------------ 45 | final class Mapped extends LazyList[B]: // error | ^ diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index 63359e7bb8b8..be11aedd74ae 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,10 +1,10 @@ -- Error: tests/neg-custom-args/captures/leaked-curried.scala:14:20 ---------------------------------------------------- 14 | () => () => io // error | ^^ - | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (io : Cap^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> () ->{io} (ex$7: caps.Exists) -> Cap^{ex$7} -- Error: tests/neg-custom-args/captures/leaked-curried.scala:17:20 ---------------------------------------------------- 17 | () => () => io // error | ^^ - | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (io : Cap^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> () ->{io} (ex$15: caps.Exists) -> Cap^{ex$15} diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 9c49c444c82f..72604451472c 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -6,8 +6,8 @@ -- Error: tests/neg-custom-args/captures/try.scala:30:65 --------------------------------------------------------------- 30 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error | ^ - | (x : CanThrow[Exception]) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () ->? Nothing + | reference (x : CanThrow[Exception]) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () ->? Nothing -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:52:2 ------------------------------------------- 47 |val global: () -> Int = handle { 48 | (x: CanThrow[Exception]) => From 496c866106bf93cc12e1e375c38a37e2e2284903 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 3 Nov 2024 12:25:20 +0100 Subject: [PATCH 042/202] Fix dcs for invariant type parameters Also: add test that reach capabilities are contained inside boxes --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 6 ++++-- tests/pos-custom-args/captures/boxed-use.scala | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 tests/pos-custom-args/captures/boxed-use.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index cde0431e3e86..406d9b661fb3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1116,13 +1116,15 @@ object CaptureSet: /** The deep capture set of a type is the union of all covariant occurrences of * capture sets. Nested existential sets are approximated with `cap`. * NOTE: The traversal logic needs to be in sync with narrowCaps in CaptureOps, which - * replaces caps with reach capabilties. + * replaces caps with reach capabilties. The one exception to this is invariant + * arguments. This have to be included to be conservative in dcs but must be + * excluded in narrowCaps. */ def ofTypeDeeply(tp: Type)(using Context): CaptureSet = val collect = new TypeAccumulator[CaptureSet]: val seen = util.HashSet[Symbol]() def apply(cs: CaptureSet, t: Type) = - if variance <= 0 then cs + if variance < 0 then cs else t.dealias match case t @ CapturingType(p, cs1) => this(cs, p) ++ cs1 diff --git a/tests/pos-custom-args/captures/boxed-use.scala b/tests/pos-custom-args/captures/boxed-use.scala new file mode 100644 index 000000000000..5dbdab7a6935 --- /dev/null +++ b/tests/pos-custom-args/captures/boxed-use.scala @@ -0,0 +1,17 @@ +class Box[A](val elem: A) +class CoBox[+A](val elem: A) + +def applyAll[A](fs: Box[A => Unit], x: A): Box[() ->{fs*} Unit] = + Box(() => fs.elem(x)) + +def applyAllCo[A](fs: CoBox[A => Unit], x: A): CoBox[() ->{fs*} Unit] = + CoBox(() => fs.elem(x)) + +// Same with inferred result types +def test = + def applyAll[A](fs: Box[A => Unit], x: A) = + Box(() => fs.elem(x)) + + def applyAllCo[A](fs: CoBox[A => Unit], x: A) = + CoBox(() => fs.elem(x)) + From f4c09e3891005c5d404ef68dfb01afe965c68ac1 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 3 Nov 2024 23:54:44 +0100 Subject: [PATCH 043/202] Fix toFunctionType for methods containing reach capabilities in the result type --- .../src/dotty/tools/dotc/core/Types.scala | 7 +++++-- .../captures/depfun-reach.check | 14 +++++++++++++ .../captures/depfun-reach.scala | 20 +++++++++++++++++++ .../captures/gears-problem.scala | 7 ++++++- 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 tests/neg-custom-args/captures/depfun-reach.check create mode 100644 tests/neg-custom-args/captures/depfun-reach.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 87398000a461..95c3b025b3ce 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -38,8 +38,7 @@ import config.Printers.{core, typr, matchTypes} import reporting.{trace, Message} import java.lang.ref.WeakReference import compiletime.uninitialized -import cc.{CapturingType, CaptureRef, CaptureSet, SingletonCaptureRef, isTrackableRef, - derivedCapturingType, isBoxedCapturing, isCaptureChecking, isRetains, isRetainsLike} +import cc.* import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -4070,6 +4069,10 @@ object Types extends TypeUtils { range(defn.NothingType, atVariance(1)(apply(tp.underlying))) case CapturingType(_, _) => mapOver(tp) + case ReachCapability(tp1) => + apply(tp1) match + case tp1a: CaptureRef if tp1a.isTrackableRef => tp1a.reach + case _ => defn.captureRoot.termRef case AnnotatedType(parent, ann) if ann.refersToParamOf(thisLambdaType) => val parent1 = mapOver(parent) if ann.symbol.isRetainsLike then diff --git a/tests/neg-custom-args/captures/depfun-reach.check b/tests/neg-custom-args/captures/depfun-reach.check new file mode 100644 index 000000000000..c1d7d05dc8d6 --- /dev/null +++ b/tests/neg-custom-args/captures/depfun-reach.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:13:4 ---------------------------------- +13 | op // error + | ^^ + | Found: (xs: List[(X, box () ->{io} Unit)]) ->{op} List[box () ->{xs*} Unit] + | Required: (xs: List[(X, box () ->{io} Unit)]) => List[() -> Unit] + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:20:60 --------------------------------- +20 | val b: (xs: List[() ->{io} Unit]) => List[() ->{} Unit] = a // error + | ^ + | Found: (xs: List[box () ->{io} Unit]) ->{a} List[box () ->{xs*} Unit] + | Required: (xs: List[box () ->{io} Unit]) => List[() -> Unit] + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/depfun-reach.scala b/tests/neg-custom-args/captures/depfun-reach.scala new file mode 100644 index 000000000000..94b10f7dbcdb --- /dev/null +++ b/tests/neg-custom-args/captures/depfun-reach.scala @@ -0,0 +1,20 @@ +import language.experimental.captureChecking +import caps.cap + +def test(io: Object^, async: Object^) = + def compose(op: List[(() ->{cap} Unit, () ->{cap} Unit)]): List[() ->{op*} Unit] = + List(() => op.foreach((f,g) => { f(); g() })) + + def compose1(op: List[(() ->{async} Unit, () ->{io} Unit)]): List[() ->{op*} Unit] = + compose(op) + + def foo[X](op: (xs: List[(X, () ->{io} Unit)]) => List[() ->{xs*} Unit]) + : (xs: List[(X, () ->{io} Unit)]) => List[() ->{} Unit] = + op // error + + def boom(op: List[(() ->{async} Unit, () ->{io} Unit)]): List[() ->{} Unit] = + foo(compose1)(op) + +def test2(io: Object^) = + val a: (xs: List[() ->{io} Unit]) => List[() ->{xs*} Unit] = ??? + val b: (xs: List[() ->{io} Unit]) => List[() ->{} Unit] = a // error diff --git a/tests/pos-custom-args/captures/gears-problem.scala b/tests/pos-custom-args/captures/gears-problem.scala index c6ef72bc421e..531c59d9fe7d 100644 --- a/tests/pos-custom-args/captures/gears-problem.scala +++ b/tests/pos-custom-args/captures/gears-problem.scala @@ -16,4 +16,9 @@ extension [T](@use fs: Seq[Future[T]^]) val collector: Collector[T]{val futures: Seq[Future[T]^{fs*}]} = Collector(fs) // val ch = collector.results // also errors - val fut: Future[T]^{fs*} = collector.results.read().right.get // found ...^{caps.cap} \ No newline at end of file + val fut: Future[T]^{fs*} = collector.results.read().right.get // found ...^{caps.cap} + + val ch = collector.results + val item = ch.read() + val r = item.right + val fut2: Future[T]^{fs*} = r.get \ No newline at end of file From c4c69b0d7749c84411b622001e835aeb727031db Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 5 Nov 2024 19:28:18 +0100 Subject: [PATCH 044/202] Expand criterion when to not check inferred types a bit --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index d35cbc3700e2..628217e27164 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -894,6 +894,7 @@ class CheckCaptures extends Recheck, SymTransformer: def canUseInferred = // If canUseInferred is false, all capturing types in the type of `sym` need to be given explicitly sym.is(Private) // private symbols can always have inferred types + || sym.privateWithin == sym.effectiveOwner || sym.name.is(DefaultGetterName) // default getters are exempted since otherwise it would be // too annoying. This is a hole since a defualt getter's result type // might leak into a type variable. From 91da9fbf135ad07193395e97a950125ef058c637 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 5 Nov 2024 19:29:34 +0100 Subject: [PATCH 045/202] Be more careful computing underlying types of reach capabilities We can use the dcs only if there are no type variables. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 23 ++++-- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 11 ++- .../dotty/tools/dotc/cc/CheckCaptures.scala | 74 ++++++++++--------- .../src/dotty/tools/dotc/core/SymUtils.scala | 3 + .../captures/gears-problem-1.scala} | 2 +- .../captures/gears-problem.check | 15 ++++ .../captures/gears-problem.scala | 4 +- .../captures/unsound-reach-7.scala | 15 ++++ .../neg-custom-args/captures/use-capset.check | 19 +++++ .../neg-custom-args/captures/use-capset.scala | 16 ++++ .../captures/gears-problem-poly.scala | 29 ++++++++ 11 files changed, 165 insertions(+), 46 deletions(-) rename tests/{pos/gears-probem-1.scala => neg-custom-args/captures/gears-problem-1.scala} (87%) create mode 100644 tests/neg-custom-args/captures/gears-problem.check rename tests/{pos-custom-args => neg-custom-args}/captures/gears-problem.scala (90%) create mode 100644 tests/neg-custom-args/captures/unsound-reach-7.scala create mode 100644 tests/neg-custom-args/captures/use-capset.check create mode 100644 tests/neg-custom-args/captures/use-capset.scala create mode 100644 tests/pos-custom-args/captures/gears-problem-poly.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 8e684e212f3b..5508a5a3d0f6 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -220,8 +220,8 @@ extension (tp: Type) * a singleton capability `x` or a reach capability `x*`, the deep capture * set can be narrowed to`{x*}`. */ - def deepCaptureSet(using Context): CaptureSet = - val dcs = CaptureSet.ofTypeDeeply(tp.widen.stripCapturing) + def deepCaptureSet(includeTypevars: Boolean)(using Context): CaptureSet = + val dcs = CaptureSet.ofTypeDeeply(tp.widen.stripCapturing, includeTypevars) if dcs.isAlwaysEmpty then tp.captureSet else tp match case tp @ ReachCapability(_) => @@ -231,6 +231,9 @@ extension (tp: Type) case _ => tp.captureSet ++ dcs + def deepCaptureSet(using Context): CaptureSet = + deepCaptureSet(includeTypevars = false) + /** A type capturing `ref` */ def capturing(ref: CaptureRef)(using Context): Type = if tp.captureSet.accountsFor(ref) then tp @@ -593,9 +596,7 @@ extension (sym: Symbol) def isRefiningParamAccessor(using Context): Boolean = sym.is(ParamAccessor) && { - val param = sym.owner.primaryConstructor.paramSymss - .nestedFind(_.name == sym.name) - .getOrElse(NoSymbol) + val param = sym.owner.primaryConstructor.paramNamed(sym.name) !param.hasAnnotation(defn.ConstructorOnlyAnnot) && !param.hasAnnotation(defn.UntrackedCapturesAnnot) } @@ -603,6 +604,18 @@ extension (sym: Symbol) def hasTrackedParts(using Context): Boolean = !CaptureSet.ofTypeDeeply(sym.info).isAlwaysEmpty + /** `sym` is annotated @use or it is a type parameter with a matching + * @use-annotated term parameter that contains `sym` in its deep capture set. + */ + def isUseParam(using Context): Boolean = + sym.hasAnnotation(defn.UseAnnot) + || sym.is(TypeParam) + && sym.owner.rawParamss.nestedExists: param => + param.is(TermParam) && param.hasAnnotation(defn.UseAnnot) + && param.info.deepCaptureSet.elems.exists: + case c: TypeRef => c.symbol == sym + case _ => false + extension (tp: AnnotatedType) /** Is this a boxed capturing type? */ def isBoxed(using Context): Boolean = tp.annot match diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 406d9b661fb3..899045843cbf 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1064,8 +1064,9 @@ object CaptureSet: case ref: (TermRef | TermParamRef) if ref.isMaxCapability => if ref.isTrackableRef then ref.singletonCaptureSet else CaptureSet.universal - case ReachCapability(ref1) => ref1.widen.deepCaptureSet - .showing(i"Deep capture set of $ref: ${ref1.widen} = $result", capt) + case ReachCapability(ref1) => + ref1.widen.deepCaptureSet(includeTypevars = true) + .showing(i"Deep capture set of $ref: ${ref1.widen} = ${result}", capt) case _ => ofType(ref.underlying, followResult = true) /** Capture set of a type */ @@ -1120,7 +1121,7 @@ object CaptureSet: * arguments. This have to be included to be conservative in dcs but must be * excluded in narrowCaps. */ - def ofTypeDeeply(tp: Type)(using Context): CaptureSet = + def ofTypeDeeply(tp: Type, includeTypevars: Boolean = false)(using Context): CaptureSet = val collect = new TypeAccumulator[CaptureSet]: val seen = util.HashSet[Symbol]() def apply(cs: CaptureSet, t: Type) = @@ -1132,7 +1133,9 @@ object CaptureSet: this(cs, parent) case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) => seen += t.symbol - this(cs, t.info.bounds.hi) + val upper = t.info.bounds.hi + if includeTypevars && upper.isExactlyAny then CaptureSet.universal + else this(cs, t.info.bounds.hi) case t @ FunctionOrMethod(args, res @ Existential(_, _)) if args.forall(_.isAlwaysPure) => this(cs, Existential.toCap(res)) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 628217e27164..32271ecacc41 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -185,34 +185,6 @@ object CheckCaptures: if ccConfig.useSealed then check.traverse(tp) end disallowRootCapabilitiesIn - /** Under the sealed policy, disallow the root capability in type arguments. - * Type arguments come either from a TypeApply node or from an AppliedType - * which represents a trait parent in a template. - * @param fn the type application, of type TypeApply or TypeTree - * @param sym the constructor symbol (could be a method or a val or a class) - * @param args the type arguments - */ - private def disallowCapInTypeArgs(fn: Tree, sym: Symbol, args: List[Tree], thisPhase: Phase)(using Context): Unit = - def isExempt = sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue - if ccConfig.useSealed && !isExempt then - val paramNames = atPhase(thisPhase.prev): - fn.tpe.widenDealias match - case tl: TypeLambda => tl.paramNames - case ref: AppliedType if ref.typeSymbol.isClass => ref.typeSymbol.typeParams.map(_.name) - case t => - println(i"parent type: $t") - args.map(_ => EmptyTypeName) - for case (arg: TypeTree, pname) <- args.lazyZip(paramNames) do - def where = if sym.exists then i" in an argument of $sym" else "" - val (addendum, pos) = - if arg.isInferred - then ("\nThis is often caused by a local capability$where\nleaking as part of its result.", fn.srcPos) - else if arg.span.exists then ("", arg.srcPos) - else ("", fn.srcPos) - disallowRootCapabilitiesIn(arg.knownType, NoSymbol, - i"Type variable $pname of $sym", "be instantiated to", addendum, pos) - end disallowCapInTypeArgs - /** If we are not under the sealed policy, and a tree is an application that unboxes * its result or is a try, check that the tree's type does not have covariant universal * capabilities. @@ -409,14 +381,14 @@ class CheckCaptures extends Recheck, SymTransformer: if lastEnv != null && env.nestedClosure.exists && env.nestedClosure == lastEnv.owner then () // access is from a nested closure, so it's OK else c.pathRoot match - case ref: NamedType if !ref.symbol.hasAnnotation(defn.UseAnnot) => + case ref: NamedType if !ref.symbol.isUseParam => val what = if ref.isType then "Capture set parameter" else "Local reach capability" report.error( em"""$what $c leaks into capture scope of ${env.ownerString}. |To allow this, the ${ref.symbol} should be declared with a @use annotation""", pos) case _ => - def recur(cs: CaptureSet, env: Env, lastEnv: Env | Null)(using Context): Unit = + def recur(cs: CaptureSet, env: Env, lastEnv: Env | Null): Unit = if env.isOpen && !env.owner.isStaticOwner && !cs.isAlwaysEmpty then // Only captured references that are visible from the environment // should be included. @@ -480,6 +452,40 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos) + /** Under the sealed policy, disallow the root capability in type arguments. + * Type arguments come either from a TypeApply node or from an AppliedType + * which represents a trait parent in a template. Also, if a corresponding + * formal type parameter is declared or implied @use, charge the deep capture + * set of the argument to the environent. + * @param fn the type application, of type TypeApply or TypeTree + * @param sym the constructor symbol (could be a method or a val or a class) + * @param args the type arguments + */ + def disallowCapInTypeArgs(fn: Tree, sym: Symbol, args: List[Tree])(using Context): Unit = + def isExempt = sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue + if ccConfig.useSealed && !isExempt then + val paramNames = atPhase(thisPhase.prev): + fn.tpe.widenDealias match + case tl: TypeLambda => tl.paramNames + case ref: AppliedType if ref.typeSymbol.isClass => ref.typeSymbol.typeParams.map(_.name) + case t => + println(i"parent type: $t") + args.map(_ => EmptyTypeName) + + for case (arg: TypeTree, pname) <- args.lazyZip(paramNames) do + def where = if sym.exists then i" in an argument of $sym" else "" + val (addendum, pos) = + if arg.isInferred + then ("\nThis is often caused by a local capability$where\nleaking as part of its result.", fn.srcPos) + else if arg.span.exists then ("", arg.srcPos) + else ("", fn.srcPos) + disallowRootCapabilitiesIn(arg.knownType, NoSymbol, + i"Type variable $pname of $sym", "be instantiated to", addendum, pos) + + val param = fn.symbol.paramNamed(pname) + if param.isUseParam then markFree(arg.knownType.deepCaptureSet, pos) + end disallowCapInTypeArgs + override def recheckIdent(tree: Ident, pt: Type)(using Context): Type = val sym = tree.symbol if sym.is(Method) then @@ -558,8 +564,8 @@ class CheckCaptures extends Recheck, SymTransformer: */ override def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = val paramInfosWithUses = funtpe.paramInfos.zipWithConserve(funtpe.paramNames): (formal, pname) => - val paramOpt = meth.rawParamss.nestedFind(_.name == pname) - paramOpt.flatMap(_.getAnnotation(defn.UseAnnot)) match + val param = meth.paramNamed(pname) + param.getAnnotation(defn.UseAnnot) match case Some(ann) => AnnotatedType(formal, ann) case _ => formal funtpe.derivedLambdaType(paramInfos = paramInfosWithUses) @@ -725,7 +731,7 @@ class CheckCaptures extends Recheck, SymTransformer: val meth = tree.fun match case fun @ Select(qual, nme.apply) => qual.symbol.orElse(fun.symbol) case fun => fun.symbol - disallowCapInTypeArgs(tree.fun, meth, tree.args, thisPhase) + disallowCapInTypeArgs(tree.fun, meth, tree.args) val res = Existential.toCap(super.recheckTypeApply(tree, pt)) includeCallCaptures(tree.symbol, res, tree.srcPos) checkContains(tree) @@ -956,7 +962,7 @@ class CheckCaptures extends Recheck, SymTransformer: for case tpt: TypeTree <- impl.parents do tpt.tpe match case AppliedType(fn, args) => - disallowCapInTypeArgs(tpt, fn.typeSymbol, args.map(TypeTree(_)), thisPhase) + disallowCapInTypeArgs(tpt, fn.typeSymbol, args.map(TypeTree(_))) case _ => inNestedLevelUnless(cls.is(Module)): super.recheckClassDef(tree, impl, cls) diff --git a/compiler/src/dotty/tools/dotc/core/SymUtils.scala b/compiler/src/dotty/tools/dotc/core/SymUtils.scala index 3a97a0053dbd..1a762737d52f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/SymUtils.scala @@ -271,6 +271,9 @@ class SymUtils: self.owner.info.decl(fieldName).suchThat(!_.is(Method)).symbol } + def paramNamed(name: Name)(using Context): Symbol = + self.rawParamss.nestedFind(_.name == name).getOrElse(NoSymbol) + /** Is this symbol a constant expression final val? * * This is the case if all of the following are true: diff --git a/tests/pos/gears-probem-1.scala b/tests/neg-custom-args/captures/gears-problem-1.scala similarity index 87% rename from tests/pos/gears-probem-1.scala rename to tests/neg-custom-args/captures/gears-problem-1.scala index d363515afece..515aecd468f6 100644 --- a/tests/pos/gears-probem-1.scala +++ b/tests/neg-custom-args/captures/gears-problem-1.scala @@ -22,4 +22,4 @@ extension [T](@use fs: Seq[Future[T]^]) val collector//: Collector[T]{val futures: Seq[Future[T]^{fs*}]} = Collector(fs) // val ch = collector.results // also errors - val fut: Future[T]^{fs*} = collector.results.read().get // found ...^{caps.cap} \ No newline at end of file + val fut: Future[T]^{fs*} = collector.results.read().get // error diff --git a/tests/neg-custom-args/captures/gears-problem.check b/tests/neg-custom-args/captures/gears-problem.check new file mode 100644 index 000000000000..eb37feb6d568 --- /dev/null +++ b/tests/neg-custom-args/captures/gears-problem.check @@ -0,0 +1,15 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/gears-problem.scala:19:62 -------------------------------- +19 | val fut: Future[T]^{fs*} = collector.results.read().right.get // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: Future[T]^{collector.futures*} + | Required: Future[T]^{fs*} + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/gears-problem.scala:24:34 -------------------------------- +24 | val fut2: Future[T]^{fs*} = r.get // error + | ^^^^^ + | Found: Future[box T^?]^{collector.futures*} + | Required: Future[T]^{fs*} + | + | longer explanation available when compiling with `-explain` +there were 4 deprecation warnings; re-run with -deprecation for details diff --git a/tests/pos-custom-args/captures/gears-problem.scala b/tests/neg-custom-args/captures/gears-problem.scala similarity index 90% rename from tests/pos-custom-args/captures/gears-problem.scala rename to tests/neg-custom-args/captures/gears-problem.scala index 531c59d9fe7d..8dcdaabb2100 100644 --- a/tests/pos-custom-args/captures/gears-problem.scala +++ b/tests/neg-custom-args/captures/gears-problem.scala @@ -16,9 +16,9 @@ extension [T](@use fs: Seq[Future[T]^]) val collector: Collector[T]{val futures: Seq[Future[T]^{fs*}]} = Collector(fs) // val ch = collector.results // also errors - val fut: Future[T]^{fs*} = collector.results.read().right.get // found ...^{caps.cap} + val fut: Future[T]^{fs*} = collector.results.read().right.get // error val ch = collector.results val item = ch.read() val r = item.right - val fut2: Future[T]^{fs*} = r.get \ No newline at end of file + val fut2: Future[T]^{fs*} = r.get // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/unsound-reach-7.scala b/tests/neg-custom-args/captures/unsound-reach-7.scala new file mode 100644 index 000000000000..df345cdf7f6d --- /dev/null +++ b/tests/neg-custom-args/captures/unsound-reach-7.scala @@ -0,0 +1,15 @@ +import language.experimental.captureChecking +import caps.{cap, use} + +trait IO +trait Async + +def main(io: IO^, async: Async^) = + def bad[X](ops: List[(X, () ->{io} Unit)])(f: () ->{ops*} Unit): () ->{io} Unit = f // error + def runOps(@use ops: List[(() => Unit, () => Unit)]): () ->{ops*} Unit = + () => ops.foreach((f1, f2) => { f1(); f2() }) + def delayOps(@use ops: List[(() ->{async} Unit, () ->{io} Unit)]): () ->{io} Unit = + val runner: () ->{ops*} Unit = runOps(ops) + val badRunner: () ->{io} Unit = bad[() ->{async} Unit](ops)(runner) + // it uses both async and io, but we losed track of async. + badRunner \ No newline at end of file diff --git a/tests/neg-custom-args/captures/use-capset.check b/tests/neg-custom-args/captures/use-capset.check new file mode 100644 index 000000000000..d94ff90cddb8 --- /dev/null +++ b/tests/neg-custom-args/captures/use-capset.check @@ -0,0 +1,19 @@ +-- Error: tests/neg-custom-args/captures/use-capset.scala:7:50 --------------------------------------------------------- +7 |private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error + | ^^^^^^^ + | Capture set parameter C leaks into capture scope of method g. + | To allow this, the type C should be declared with a @use annotation +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:13:22 ----------------------------------- +13 | val _: () -> Unit = h // error: should be ->{io} + | ^ + | Found: (h : () ->{io} Unit) + | Required: () -> Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:15:50 ----------------------------------- +15 | val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} + | ^^ + | Found: () ->? (x$0: List[box Object^{io}]^{}) ->{io} (ex$13: caps.Exists) -> Object^{io} + | Required: () -> List[box Object^{io}] -> Object^{io} + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/use-capset.scala b/tests/neg-custom-args/captures/use-capset.scala new file mode 100644 index 000000000000..6010e955f867 --- /dev/null +++ b/tests/neg-custom-args/captures/use-capset.scala @@ -0,0 +1,16 @@ +import caps.{use, CapSet} + + + +def f[C^](@use xs: List[Object^{C^}]): Unit = ??? + +private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error + +private def g2[@use C^] = (xs: List[Object^{C^}]) => xs.head // ok + +def test(io: Object^)(@use xs: List[Object^{io}]): Unit = + val h = () => f(xs) + val _: () -> Unit = h // error: should be ->{io} + val h2 = () => g[CapSet^{io}] + val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} + diff --git a/tests/pos-custom-args/captures/gears-problem-poly.scala b/tests/pos-custom-args/captures/gears-problem-poly.scala new file mode 100644 index 000000000000..fdbcf37a35a6 --- /dev/null +++ b/tests/pos-custom-args/captures/gears-problem-poly.scala @@ -0,0 +1,29 @@ +import language.experimental.captureChecking +import caps.{use, CapSet} + +trait Future[+T]: + def await: T + +trait Channel[+T]: + def read(): Ok[T] + +class Collector[T, C^](val futures: Seq[Future[T]^{C^}]): + val results: Channel[Future[T]^{C^}] = ??? +end Collector + +class Result[+T, +E]: + def get: T = ??? + +case class Err[+E](e: E) extends Result[Nothing, E] +case class Ok[+T](x: T) extends Result[T, Nothing] + +extension [T, C^](@use fs: Seq[Future[T]^{C^}]) + def awaitAllPoly = + val collector = Collector(fs) + val fut: Future[T]^{C^} = collector.results.read().get + +extension [T](@use fs: Seq[Future[T]^]) + def awaitAll = fs.awaitAllPoly + +def awaitExplicit[T](@use fs: Seq[Future[T]^]): Unit = + awaitAllPoly[T, CapSet^{fs*}](fs) From 34e947ac91d11fba07464487b0be9cee83eeadf3 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 5 Nov 2024 21:42:51 +0100 Subject: [PATCH 046/202] Polish code of markFree --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 88 +++++++++++-------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 32271ecacc41..f1d4a55711e3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -388,6 +388,52 @@ class CheckCaptures extends Recheck, SymTransformer: |To allow this, the ${ref.symbol} should be declared with a @use annotation""", pos) case _ => + /** Avoid locally defined capability by charging the underlying type + * (which may not be cap). This scheme applies only under the deferredReaches setting. + */ + def avoidLocalCapability(c: CaptureRef, env: Env, lastEnv: Env | Null): Unit = + if c.isParamPath then + c match + case ReachCapability(_) | _: TypeRef => + checkUseDeclared(c, env, lastEnv) + case _ => + else + val underlying = c match + case ReachCapability(c1) => + CaptureSet.ofTypeDeeply(c1.widen) + case _ => + CaptureSet.ofType(c.widen, followResult = false) + capt.println(i"Widen reach $c to $underlying in ${env.owner}") + underlying.disallowRootCapability: () => + report.error(em"Local capability $c in ${env.ownerString} cannot have `cap` as underlying capture set", pos) + recur(underlying, env, lastEnv) + + /** Avoid locally defined capability if it is a reach capability or capture set + * parameter. This is the default. + */ + def avoidLocalReachCapability(c: CaptureRef, env: Env): Unit = c match + case ReachCapability(c1) => + if c1.isParamPath then + checkUseDeclared(c, env, null) + else + // When a reach capabilty x* where `x` is not a parameter goes out + // of scope, we need to continue with `x`'s underlying deep capture set. + // It is an error if that set contains cap. + // The same is not an issue for normal capabilities since in a local + // definition `val x = e`, the capabilities of `e` have already been charged. + // Note: It's not true that the underlying capture set of a reach capability + // is always cap. Reach capabilities over paths depend on the prefix, which + // might turn a cap into something else. + // The path-use.scala neg test contains an example. + val underlying = CaptureSet.ofTypeDeeply(c1.widen) + capt.println(i"Widen reach $c to $underlying in ${env.owner}") + underlying.disallowRootCapability: () => + report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", pos) + recur(underlying, env, null) + case c: TypeRef if c.isParamPath => + checkUseDeclared(c, env, null) + case _ => + def recur(cs: CaptureSet, env: Env, lastEnv: Env | Null): Unit = if env.isOpen && !env.owner.isStaticOwner && !cs.isAlwaysEmpty then // Only captured references that are visible from the environment @@ -399,45 +445,9 @@ class CheckCaptures extends Recheck, SymTransformer: case ref => false if !isVisible then - //println(i"out of scope: $c") - if ccConfig.deferredReaches then // avoid all locally bound capabilities - if c.isParamPath then - c match - case ReachCapability(_) | _: TypeRef => - checkUseDeclared(c, env, lastEnv) - case _ => - else - val underlying = c match - case ReachCapability(c1) => - CaptureSet.ofTypeDeeply(c1.widen) - case _ => - CaptureSet.ofType(c.widen, followResult = false) - capt.println(i"Widen reach $c to $underlying in ${env.owner}") - underlying.disallowRootCapability: () => - report.error(em"Local capability $c in ${env.ownerString} cannot have `cap` as underlying capture set", pos) - recur(underlying, env, lastEnv) - else c match // avoid only reach capabilities and capture sets - case ReachCapability(c1) => - if c1.isParamPath then - checkUseDeclared(c, env, lastEnv) - else - // When a reach capabilty x* where `x` is not a parameter goes out - // of scope, we need to continue with `x`'s underlying deep capture set. - // It is an error if that set contains cap. - // The same is not an issue for normal capabilities since in a local - // definition `val x = e`, the capabilities of `e` have already been charged. - // Note: It's not true that the underlying capture set of a reach capability - // is always cap. Reach capabilities over paths depend on the prefix, which - // might turn a cap into something else. - // The path-use.scala neg test contains an example. - val underlying = CaptureSet.ofTypeDeeply(c1.widen) - capt.println(i"Widen reach $c to $underlying in ${env.owner}") - underlying.disallowRootCapability: () => - report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", pos) - recur(underlying, env, lastEnv) - case c: TypeRef if c.isParamPath => - checkUseDeclared(c, env, lastEnv) - case _ => + if ccConfig.deferredReaches + then avoidLocalCapability(c, env, lastEnv) + else avoidLocalReachCapability(c, env) isVisible checkSubset(included, env.captured, pos, provenance(env)) capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") From eb0577f8e955489775c887743854cf131a90e3ba Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 8 Nov 2024 15:41:40 +0100 Subject: [PATCH 047/202] Revert change to Iterator The additional purity in the asInstanceOf target is not needed --- scala2-library-cc/src/scala/collection/Iterator.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scala2-library-cc/src/scala/collection/Iterator.scala b/scala2-library-cc/src/scala/collection/Iterator.scala index 22632d738199..7d5af9f021dc 100644 --- a/scala2-library-cc/src/scala/collection/Iterator.scala +++ b/scala2-library-cc/src/scala/collection/Iterator.scala @@ -1159,8 +1159,7 @@ object Iterator extends IterableFactory[Iterator] { // If we advanced the current iterator to a ConcatIterator, merge it into this one @tailrec def merge(): Unit = if (current.isInstanceOf[ConcatIterator[_]]) { - val c: ConcatIterator[A] { val from: Iterator[A] } - = current.asInstanceOf + val c: ConcatIterator[A] = current.asInstanceOf current = c.current.asInstanceOf // !!! CC unsafe op currentHasNextChecked = c.currentHasNextChecked if (c.tail != null) { From 2b51ee98241c07153f3a6fddbc4ca2174b94332b Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 8 Nov 2024 17:01:43 +0100 Subject: [PATCH 048/202] Document and polish CheckCaptures --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 4 - .../src/dotty/tools/dotc/cc/CaptureSet.scala | 4 +- .../dotty/tools/dotc/cc/CheckCaptures.scala | 532 +++++++++++------- tests/pos-custom-args/captures/vars1.scala | 2 +- 4 files changed, 326 insertions(+), 216 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 5508a5a3d0f6..eef1b5caacd1 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -127,10 +127,6 @@ end CCState def ccState(using Context) = Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState1 -class NoCommonRoot(rs: Symbol*)(using Context) extends Exception( - i"No common capture root nested in ${rs.mkString(" and ")}" -) - extension (tree: Tree) /** Map tree with CaptureRef type to its type, diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 899045843cbf..675b00a09ff5 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -185,10 +185,12 @@ sealed abstract class CaptureSet extends Showable: /** A more optimistic version of subCaptures used to choose one of two typing rules * for selections and applications. `cs1 mightSubcapture cs2` if `cs2` might account for - * every element currently known to be in `cs1`. + * every element currently known to be in `cs1`, and the same is not true in reverse + * when we compare elements of cs2 vs cs1. */ def mightSubcapture(that: CaptureSet)(using Context): Boolean = elems.forall(that.mightAccountFor) + && !that.elems.forall(this.mightAccountFor) /** The subcapturing test. * @param frozen if true, no new variables or dependent sets are allowed to diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index f1d4a55711e3..41a35cb52d39 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -39,10 +39,12 @@ object CheckCaptures: case Boxed // environment is inside a box (in which case references are not counted) /** A class describing environments. - * @param owner the current owner - * @param kind the environment's kind - * @param captured the capture set containing all references to tracked free variables outside of boxes - * @param outer0 the next enclosing environment + * @param owner the current owner + * @param kind the environment's kind + * @param captured the capture set containing all references to tracked free variables outside of boxes + * @param outer0 the next enclosing environment + * @param nestedClosure under deferredReaches: If this is an env of a method with an anonymous function or + * anonymous class as RHS, the symbol of that function or class. NoSymbol in all other cases. */ case class Env( owner: Symbol, @@ -88,6 +90,9 @@ object CheckCaptures: mapOver(tp) end SubstParamsMap + /** Used for substituting parameters in a special case: when all actual arguments + * are mutually distinct capabilities. + */ final class SubstParamsBiMap(from: LambdaType, to: List[Type])(using Context) extends BiTypeMap: thisMap => @@ -240,6 +245,22 @@ class CheckCaptures extends Recheck, SymTransformer: class CaptureChecker(ictx: Context) extends Rechecker(ictx): + /** The current environment */ + private val rootEnv: Env = inContext(ictx): + Env(defn.RootClass, EnvKind.Regular, CaptureSet.empty, null) + private var curEnv = rootEnv + + /** Currently checked closures and their expected types, used for error reporting */ + private var openClosures: List[(Symbol, Type)] = Nil + + private val myCapturedVars: util.EqHashMap[Symbol, CaptureSet] = EqHashMap() + + /** A list of actions to perform at postCheck. The reason to defer these actions + * is that it is sometimes better for type inference to not constrain too early + * with a checkConformsExpr. + */ + private var todoAtPostCheck = new mutable.ListBuffer[() => Unit] + override def keepType(tree: Tree) = super.keepType(tree) || tree.isInstanceOf[Try] // type of `try` needs tp be checked for * escapes @@ -271,10 +292,11 @@ class CheckCaptures extends Recheck, SymTransformer: report.warning(msg, tpt.srcPos) ccState.approxWarnings.clear() - /** Assert subcapturing `cs1 <: cs2` */ + /** Assert subcapturing `cs1 <: cs2` (available for debugging, otherwise unused) */ def assertSub(cs1: CaptureSet, cs2: CaptureSet)(using Context) = assert(cs1.subCaptures(cs2, frozen = false).isOK, i"$cs1 is not a subset of $cs2") + /** If `res` is not CompareResult.OK, report an error */ def checkOK(res: CompareResult, prefix: => String, pos: SrcPos, provenance: => String = "")(using Context): Unit = if !res.isOK then def toAdd: String = CaptureSet.levelErrors.toAdd.mkString @@ -299,25 +321,6 @@ class CheckCaptures extends Recheck, SymTransformer: else i"references $cs1$cs1description are not all", pos, provenance) - def showRef(ref: CaptureRef)(using Context): String = - ctx.printer.toTextCaptureRef(ref).show - - /** The current environment */ - private val rootEnv: Env = inContext(ictx): - Env(defn.RootClass, EnvKind.Regular, CaptureSet.empty, null) - private var curEnv = rootEnv - - /** Currently checked closures and their expected types, used for error reporting */ - private var openClosures: List[(Symbol, Type)] = Nil - - private val myCapturedVars: util.EqHashMap[Symbol, CaptureSet] = EqHashMap() - - /** A list of actions to perform at postCheck. The reason to defer these actions - * is that it is sometimes better for type inference to not constrain too early - * with a checkConformsExpr. - */ - private var todoAtPostCheck = new mutable.ListBuffer[() => Unit] - /** If `sym` is a class or method nested inside a term, a capture set variable representing * the captured variables of the environment associated with `sym`. */ @@ -327,6 +330,8 @@ class CheckCaptures extends Recheck, SymTransformer: then CaptureSet.Var(sym.owner, level = sym.ccLevel) else CaptureSet.empty) +// ---- Record Uses with MarkFree ---------------------------------------------------- + /** The next environment enclosing `env` that needs to be charged * with free references. * @param included Whether an environment is included in the range of @@ -349,6 +354,9 @@ class CheckCaptures extends Recheck, SymTransformer: else i"\nof the enclosing ${owner.showLocated}" + /** Does the given environment belong to a method that is (a) nested in a term + * and (b) not the method of an anonympus function? + */ def isOfNestedMethod(env: Env | Null)(using Context) = env != null && env.owner.is(Method) @@ -364,7 +372,7 @@ class CheckCaptures extends Recheck, SymTransformer: def markFree(sym: Symbol, ref: TermRef, pos: SrcPos)(using Context): Unit = if sym.exists && ref.isTracked then markFree(ref.captureSet, pos) - /** Make sure (projected) `cs` is a subset of the capture sets of all enclosing + /** Make sure the (projected) `cs` is a subset of the capture sets of all enclosing * environments. At each stage, only include references from `cs` that are outside * the environment's owner */ @@ -377,9 +385,12 @@ class CheckCaptures extends Recheck, SymTransformer: else !sym.isContainedIn(env.owner) + /** If captureRef `c` refers to a parameter that is not @use declared, report an error. + * Exception under deferredReaches: If use comes from a nested closure, accept it. + */ def checkUseDeclared(c: CaptureRef, env: Env, lastEnv: Env | Null) = if lastEnv != null && env.nestedClosure.exists && env.nestedClosure == lastEnv.owner then - () // access is from a nested closure, so it's OK + assert(ccConfig.deferredReaches) // access is from a nested closure under deferredReaches, so it's OK else c.pathRoot match case ref: NamedType if !ref.symbol.isUseParam => val what = if ref.isType then "Capture set parameter" else "Local reach capability" @@ -453,6 +464,9 @@ class CheckCaptures extends Recheck, SymTransformer: capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") if !isOfNestedMethod(env) then recur(included, nextEnvToCharge(env, !_.owner.isStaticOwner), env) + // Don't propagate out of methods inside terms. The use set of these methods + // will be charged when that method is called. + recur(cs, curEnv, null) end markFree @@ -464,9 +478,9 @@ class CheckCaptures extends Recheck, SymTransformer: /** Under the sealed policy, disallow the root capability in type arguments. * Type arguments come either from a TypeApply node or from an AppliedType - * which represents a trait parent in a template. Also, if a corresponding - * formal type parameter is declared or implied @use, charge the deep capture - * set of the argument to the environent. + * which represents a trait parent in a template. + * Also, if a corresponding formal type parameter is declared or implied @use, + * charge the deep capture set of the argument to the environent. * @param fn the type application, of type TypeApply or TypeTree * @param sym the constructor symbol (could be a method or a val or a class) * @param args the type arguments @@ -499,22 +513,26 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheckIdent(tree: Ident, pt: Type)(using Context): Type = val sym = tree.symbol if sym.is(Method) then + // If ident refers to a parameterless method, charge its cv to the environment includeCallCaptures(sym, sym.info, tree.srcPos) else if !sym.isStatic then - //debugShowEnvs() + // Otherwise charge its symbol, but add all selections implied by the e + // expected type `pt`. + // Example: If we have `x` and the expected type says we select that with `.a.b`, + // we charge `x.a.b` instead of `x`. def addSelects(ref: TermRef, pt: Type): TermRef = pt match case pt: PathSelectionProto if ref.isTracked => // if `ref` is not tracked then the selection could not give anything new // class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters. addSelects(ref.select(pt.sym).asInstanceOf[TermRef], pt.pt) case _ => ref - val ref = sym.termRef - val pathRef = addSelects(ref, pt) - //if pathRef ne ref then - // println(i"add selects $ref --> $pathRef") - markFree(sym, if false then ref else pathRef, tree.srcPos) + val pathRef = addSelects(sym.termRef, pt) + markFree(sym, pathRef, tree.srcPos) super.recheckIdent(tree, pt) + /** The expected type for the qualifier of a selection. If the selection + * could be part of a capabaility path, we return a PathSelectionProto. + */ override def selectionProto(tree: Select, pt: Type)(using Context): Type = val sym = tree.symbol if !sym.isOneOf(UnstableValueFlags) && !sym.isStatic then PathSelectionProto(sym, pt) @@ -527,8 +545,8 @@ class CheckCaptures extends Recheck, SymTransformer: * E |- f.m: R^C * * The implementation picks as `C` one of `{f}` or `Cr`, depending on the - * outcome of a `mightSubcapture` test. It picks `{f}` if this might subcapture Cr - * and Cr otherwise. + * outcome of a `mightSubcapture` test. It picks `{f}` if it might subcapture Cr + * and picks Cr otherwise. */ override def recheckSelection(tree: Select, qualType: Type, name: Name, pt: Type)(using Context) = { def disambiguate(denot: Denotation): Denotation = denot match @@ -548,10 +566,14 @@ class CheckCaptures extends Recheck, SymTransformer: val selType = recheckSelection(tree, qualType, name, disambiguate) val selWiden = selType.widen + // Don't apply the rule + // - on the LHS of assignments, or + // - if the qualifier or selection type is boxed, or + // - the selection is either a trackable capture ref or a pure type if pt == LhsProto || qualType.isBoxedCapturing - || selType.isTrackableRef || selWiden.isBoxedCapturing + || selType.isTrackableRef || selWiden.captureSet.isAlwaysEmpty then selType @@ -561,7 +583,7 @@ class CheckCaptures extends Recheck, SymTransformer: capt.println(i"pick one of $qualType, ${selType.widen}, $qualCs, $selCs ${selWiden.captureSet} in $tree") if qualCs.mightSubcapture(selCs) - && !selCs.mightSubcapture(qualCs) + //&& !selCs.mightSubcapture(qualCs) && !pt.stripCapturing.isInstanceOf[SingletonType] then selWiden.stripCapturing.capturing(qualCs) @@ -570,7 +592,8 @@ class CheckCaptures extends Recheck, SymTransformer: selType }//.showing(i"recheck sel $tree, $qualType = $result") - /** Copy all @use annotations on method parameter symbols to the corresponding paramInfo types. + /** Hook for massaging a function before it is applied. Copies all @use annotations + * on method parameter symbols to the corresponding paramInfo types. */ override def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = val paramInfosWithUses = funtpe.paramInfos.zipWithConserve(funtpe.paramNames): (formal, pname) => @@ -580,16 +603,18 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => formal funtpe.derivedLambdaType(paramInfos = paramInfosWithUses) - override def recheckApply(tree: Apply, pt: Type)(using Context): Type = - val meth = tree.fun.symbol + /** If this is an application of a caps.unsafe method, handle it specially + * otherwise return NoType. + */ + private def recheckUnsafeApply(tree: Apply, pt: Type)(using Context): Type = - // Unsafe box/unbox handling, only for versions < 3.3 def mapArgUsing(f: Type => Type) = val arg :: Nil = tree.args: @unchecked val argType0 = f(recheckStart(arg, pt)) val argType = super.recheckFinish(argType0, arg, pt) super.recheckFinish(argType, tree, pt) + val meth = tree.fun.symbol if meth == defn.Caps_unsafeAssumePure then val arg :: Nil = tree.args: @unchecked val argType0 = recheck(arg, pt.capturing(CaptureSet.universal)) @@ -613,23 +638,33 @@ class CheckCaptures extends Recheck, SymTransformer: case tp @ CapturingType(parent, refs) => tp.derivedCapturingType(forceBox(parent), refs) mapArgUsing(forceBox) - else + else NoType + end recheckUnsafeApply + + /** Recheck applications. More work is done in `recheckApplication`, + * `recheckArg` and `instantiate` below. + */ + override def recheckApply(tree: Apply, pt: Type)(using Context): Type = + recheckUnsafeApply(tree, pt).orElse: val res = super.recheckApply(tree, pt) - includeCallCaptures(meth, res, tree.srcPos) + includeCallCaptures(tree.symbol, res, tree.srcPos) res - end recheckApply - protected override - def recheckArg(arg: Tree, formal: Type)(using Context): Type = + /** Recheck argument, and, if formal parameter carries a `@use`, + * charge the deep capture set of the actual argument to the environment. + */ + protected override def recheckArg(arg: Tree, formal: Type)(using Context): Type = val argType = recheck(arg, formal) formal match case AnnotatedType(formal1, ann) if ann.symbol == defn.UseAnnot => + // The UseAnnot is added to `formal` by `prepareFunction` capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}") markFree(argType.deepCaptureSet, arg.srcPos) case _ => argType - /** A specialized implementation of the apply rule. + /** Map existential captures in result to `cap` and implement the following + * rele: * * E |- q: Tq^Cq * E |- q.f: Ta^Ca ->Cf Tr^Cr @@ -646,7 +681,7 @@ class CheckCaptures extends Recheck, SymTransformer: * If the function `f` does have an `@use` parameter, then it could in addition * unbox reach capabilities over its formal parameter. Therefore, the approximation * would be `Cq \union dcs(Ca)` instead. - * If the approximation is known to subcapture the declared result Cr, we pick it for C + * If the approximation might subcapture the declared result Cr, we pick it for C * otherwise we pick Cr. */ protected override @@ -675,12 +710,9 @@ class CheckCaptures extends Recheck, SymTransformer: case Nil => true /** Handle an application of method `sym` with type `mt` to arguments of types `argTypes`. - * This means: + * This means * - Instantiate result type with actual arguments - * - If call is to a constructor: - * - remember types of arguments corresponding to tracked - * parameters in refinements. - * - add capture set of instantiated class to capture set of result type. + * - if `sym` is a constructor, refine its type with `refineInstanceType` * If all argument types are mutually different trackable capture references, use a BiTypeMap, * since that is more precise. Otherwise use a normal idempotent map, which might lose information * in the case where the result type contains captureset variables that are further @@ -694,49 +726,64 @@ class CheckCaptures extends Recheck, SymTransformer: SubstParamsBiMap(mt, argTypes)(mt.resType) else SubstParamsMap(mt, argTypes)(mt.resType) - - if sym.isConstructor then - val cls = sym.owner.asClass - - /** First half of result pair: - * Refine the type of a constructor call `new C(t_1, ..., t_n)` - * to C{val x_1: T_1, ..., x_m: T_m} where x_1, ..., x_m are the tracked - * parameters of C and T_1, ..., T_m are the types of the corresponding arguments. - * - * Second half: union of all capture sets of arguments to tracked parameters. - */ - def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) = - var refined: Type = core - var allCaptures: CaptureSet = - if core.derivesFromCapability then defn.universalCSImpliedByCapability else initCs - for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do - val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol - if !getter.is(Private) && getter.hasTrackedParts then - refined = RefinedType(refined, getterName, argType.unboxed) - allCaptures ++= argType.captureSet - (refined, allCaptures) - - /** Augment result type of constructor with refinements and captures. - * @param core The result type of the constructor - * @param initCs The initial capture set to add, not yet counting capture sets from arguments - */ - def augmentConstructorType(core: Type, initCs: CaptureSet): Type = core match - case core: MethodType => - // more parameters to follow; augment result type - core.derivedLambdaType(resType = augmentConstructorType(core.resType, initCs)) - case CapturingType(parent, refs) => - // can happen for curried constructors if instantiate of a previous step - // added capture set to result. - augmentConstructorType(parent, initCs ++ refs) - case _ => - val (refined, cs) = addParamArgRefinements(core, initCs) - refined.capturing(cs) - - augmentConstructorType(ownType, capturedVars(cls) ++ capturedVars(sym)) - .showing(i"constr type $mt with $argTypes%, % in $cls = $result", capt) + if sym.isConstructor then refineConstructorInstance(ownType, mt, argTypes, sym) else ownType - end instantiate + /** Refine the type returned from a constructor as follows: + * - remember types of arguments corresponding to tracked parameters in refinements. + * - add capture set of instantiated class and capture set of constructor to capture set of result type. + * Note: This scheme does not distinguish whether a capture is made by the constructor + * only or by a method in the class. Both captures go into the result type. We + * could be more precise by distinguishing the two capture sets. + */ + private def refineConstructorInstance(resType: Type, mt: MethodType, argTypes: List[Type], constr: Symbol)(using Context): Type = + val cls = constr.owner.asClass + + /** First half of result pair: + * Refine the type of a constructor call `new C(t_1, ..., t_n)` + * to C{val x_1: T_1, ..., x_m: T_m} where x_1, ..., x_m are the tracked + * parameters of C and T_1, ..., T_m are the types of the corresponding arguments. + * + * Second half: union of initial capture set and all capture sets of arguments + * to tracked parameters. + */ + def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) = + var refined: Type = core + var allCaptures: CaptureSet = + if core.derivesFromCapability then defn.universalCSImpliedByCapability else initCs + for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do + val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol + if !getter.is(Private) && getter.hasTrackedParts then + refined = RefinedType(refined, getterName, argType.unboxed) + allCaptures ++= argType.captureSet + (refined, allCaptures) + + /** Augment result type of constructor with refinements and captures. + * @param core The result type of the constructor + * @param initCs The initial capture set to add, not yet counting capture sets from arguments + */ + def augmentConstructorType(core: Type, initCs: CaptureSet): Type = core match + case core: MethodType => + // more parameters to follow; augment result type + core.derivedLambdaType(resType = augmentConstructorType(core.resType, initCs)) + case CapturingType(parent, refs) => + // can happen for curried constructors if instantiate of a previous step + // added capture set to result. + augmentConstructorType(parent, initCs ++ refs) + case _ => + val (refined, cs) = addParamArgRefinements(core, initCs) + refined.capturing(cs) + + augmentConstructorType(resType, capturedVars(cls) ++ capturedVars(constr)) + .showing(i"constr type $mt with $argTypes%, % in $constr = $result", capt) + end refineConstructorInstance + + /** Recheck type applications: + * - Map existential captures in result to `cap` + * - include captures of called methods in environment + * - don't allow cap to appear covariantly in type arguments + * - special handling of `contains[A, B]` calls + */ override def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = val meth = tree.fun match case fun @ Select(qual, nme.apply) => qual.symbol.orElse(fun.symbol) @@ -766,12 +813,19 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheckBlock(tree: Block, pt: Type)(using Context): Type = inNestedLevel(super.recheckBlock(tree, pt)) + /** Recheck Closure node: add the captured vars of the anonymoys function + * to the result type. See also `recheckClosureBlock` which rechecks the + * block containing the anonymous function and the Closure node. + */ override def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean)(using Context): Type = val cs = capturedVars(tree.meth.symbol) capt.println(i"typing closure $tree with cvs $cs") super.recheckClosure(tree, pt, forceDependent).capturing(cs) .showing(i"rechecked closure $tree / $pt = $result", capt) + /** Recheck a lambda of the form + * { def $anonfun(...) = ...; closure($anonfun, ...)} + */ override def recheckClosureBlock(mdef: DefDef, expr: Closure, pt: Type)(using Context): Type = openClosures = (mdef.symbol, pt) :: openClosures try @@ -779,12 +833,14 @@ class CheckCaptures extends Recheck, SymTransformer: // rechecking the body. val res = recheckClosure(expr, pt, forceDependent = true) if !(isEtaExpansion(mdef) && ccConfig.handleEtaExpansionsSpecially) then - // If closure is an eta expanded method reference it's better to not constrain + // Check whether the closure's results conforms to the expected type + // This constrains parameter types of the closure which can give better + // error messages. + // But if the closure is an eta expanded method reference it's better to not constrain // its internals early since that would give error messages in generated code - // which are less intelligible. - // Example is the line `a = x` in neg-custom-args/captures/vars.scala. - // For all other closures, early constraints are preferred since they - // give more localized error messages. + // which are less intelligible. An example is the line `a = x` in + // neg-custom-args/captures/vars.scala. That's why this code is conditioned. + // to apply only to closures that are not eta expansions. val res1 = Existential.toCapDeeply(res) val pt1 = Existential.toCapDeeply(pt) // We need to open existentials here in order not to get vars mixed up in them @@ -797,42 +853,18 @@ class CheckCaptures extends Recheck, SymTransformer: openClosures = openClosures.tail end recheckClosureBlock + /** Elements of a SeqLiteral instantiate a Seq or Array parameter, so they + * should be boxed. + */ override def seqLiteralElemProto(tree: SeqLiteral, pt: Type, declared: Type)(using Context) = super.seqLiteralElemProto(tree, pt, declared).boxed - /** Maps mutable variables to the symbols that capture them (in the - * CheckCaptures sense, i.e. symbol is referred to from a different method - * than the one it is defined in). - */ - private val capturedBy = util.HashMap[Symbol, Symbol]() - - /** Maps anonymous functions appearing as function arguments to - * the function that is called. + /** Recheck val and var definitions: + * - disallow cap in the type of mutable vars. + * - for externally visible definitions: check that their inferred type + * does not refine what was known before capture checking. + * - Interpolate contravariant capture set variables in result type. */ - private val anonFunCallee = util.HashMap[Symbol, Symbol]() - - /** Populates `capturedBy` and `anonFunCallee`. Called by `checkUnit`. - */ - private def collectCapturedMutVars(using Context) = new TreeTraverser: - def traverse(tree: Tree)(using Context) = tree match - case id: Ident => - val sym = id.symbol - if sym.is(Mutable, butNot = Method) && sym.owner.isTerm then - val enclMeth = ctx.owner.enclosingMethod - if sym.enclosingMethod != enclMeth then - capturedBy(sym) = enclMeth - case Apply(fn, args) => - for case closureDef(mdef) <- args do - anonFunCallee(mdef.symbol) = fn.symbol - traverseChildren(tree) - case Inlined(_, bindings, expansion) => - traverse(bindings) - traverse(expansion) - case mdef: DefDef => - if !mdef.symbol.isInlineMethod then traverseChildren(tree) - case _ => - traverseChildren(tree) - override def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Type = try if sym.is(Module) then sym.info // Modules are checked by checking the module class @@ -861,10 +893,20 @@ class CheckCaptures extends Recheck, SymTransformer: // function is compiled since we do not propagate expected types into blocks. interpolateVarsIn(tree.tpt) + /** Recheck method definitions: + * - check body in a nested environment that tracks uses, in a nested level, + * and in a nested context that knows abaout Contains parameters so that we + * can assume they are true. + * - for externally visible definitions: check that their inferred type + * does not refine what was known before capture checking. + * - Interpolate contravariant capture set variables in result type unless + * def is anonymous. + */ override def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Type = if Synthetics.isExcluded(sym) then sym.info else - // If rhs ends in a closure or anonymous class, the corresponding symbol + // Under the deferredReaches setting: If rhs ends in a closure or + // anonymous class, the corresponding symbol def nestedClosure(rhs: Tree)(using Context): Symbol = if !ccConfig.deferredReaches then NoSymbol else rhs match @@ -889,7 +931,7 @@ class CheckCaptures extends Recheck, SymTransformer: if ac.isEmpty then ctx else ctx.withProperty(CaptureSet.AssumedContains, Some(ac)) - inNestedLevel: // TODO: needed here? + inNestedLevel: // TODO: nestedLevel needed here? try checkInferredResult(super.recheckDefDef(tree, sym)(using bodyCtx), tree) finally if !sym.isAnonymousFunction then @@ -909,14 +951,14 @@ class CheckCaptures extends Recheck, SymTransformer: val sym = tree.symbol def canUseInferred = // If canUseInferred is false, all capturing types in the type of `sym` need to be given explicitly - sym.is(Private) // private symbols can always have inferred types - || sym.privateWithin == sym.effectiveOwner - || sym.name.is(DefaultGetterName) // default getters are exempted since otherwise it would be + sym.isLocalToCompilationUnit // Symbols that can't be seen outside the compilation unit can always have inferred types + || sym.privateWithin == defn.EmptyPackageClass + // We make an exception for private symbols in a toplevel file in the empty package + // these could theoretically be accessed from other files in the empty package, but + // it would be too annoying to require explicit types. + || sym.name.is(DefaultGetterName) // Default getters are exempted since otherwise it would be // too annoying. This is a hole since a defualt getter's result type // might leak into a type variable. - || // non-local symbols cannot have inferred types since external capture types are not inferred - sym.isLocalToCompilationUnit // local symbols still need explicit types if - && !sym.owner.is(Trait) // they are defined in a trait, since we do OverridingPairs checking before capture inference def addenda(expected: Type) = new Addenda: override def toAdd(using Context) = @@ -933,23 +975,60 @@ class CheckCaptures extends Recheck, SymTransformer: case tpt: InferredTypeTree if !canUseInferred => val expected = tpt.tpe.dropAllRetains todoAtPostCheck += (() => checkConformsExpr(tp, expected, tree.rhs, addenda(expected))) + // The check that inferred <: expected is done after recheck so that it + // does not interfere with normal rechecking by constraining capture set variables. case _ => tp end checkInferredResult - /** Class-specific capture set relations: + /** The set of symbols that were rechecked via a completer */ + private val completed = new mutable.HashSet[Symbol] + + /** The normal rechecking if `sym` was already completed before */ + override def skipRecheck(sym: Symbol)(using Context): Boolean = + completed.contains(sym) + + /** Check a ValDef or DefDef as an action performed in a completer. Since + * these checks can appear out of order, we need to first create the correct + * environment for checking the definition. + */ + def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type = + val saved = curEnv + try + // Setup environment to reflect the new owner. + val envForOwner: Map[Symbol, Env] = curEnv.outersIterator + .takeWhile(e => !capturedVars(e.owner).isAlwaysEmpty) // no refs can leak beyond this point + .map(e => (e.owner, e)) + .toMap + def restoreEnvFor(sym: Symbol): Env = + val localSet = capturedVars(sym) + if localSet.isAlwaysEmpty then rootEnv + else envForOwner.get(sym) match + case Some(e) => e + case None => Env(sym, EnvKind.Regular, localSet, restoreEnvFor(sym.owner)) + curEnv = restoreEnvFor(sym.owner) + capt.println(i"Complete $sym in ${curEnv.outersIterator.toList.map(_.owner)}") + try recheckDef(tree, sym) + finally completed += sym + finally + curEnv = saved + + /** Recheck classDef by enforcing the following class-specific capture set relations: * 1. The capture set of a class includes the capture sets of its parents. * 2. The capture set of the self type of a class includes the capture set of the class. * 3. The capture set of the self type of a class includes the capture set of every class parameter, - * unless the parameter is marked @constructorOnly. + * unless the parameter is marked @constructorOnly or @untrackedCaptures. * 4. If the class extends a pure base class, the capture set of the self type must be empty. + * Also, check that trait parents represented as applied types don't have cap in their + * type arguments. Other generic parents are represented as TypeApplys, where the same check + * is already done in the TypeApply. */ override def recheckClassDef(tree: TypeDef, impl: Template, cls: ClassSymbol)(using Context): Type = - val saved = curEnv val localSet = capturedVars(cls) for parent <- impl.parents do // (1) checkSubset(capturedVars(parent.tpe.classSymbol), localSet, parent.srcPos, i"\nof the references allowed to be captured by $cls") + val saved = curEnv if !localSet.isAlwaysEmpty then curEnv = Env(cls, EnvKind.Regular, localSet, curEnv) try @@ -960,15 +1039,15 @@ class CheckCaptures extends Recheck, SymTransformer: && !param.hasAnnotation(defn.UntrackedCapturesAnnot) then checkSubset(param.termRef.captureSet, thisSet, param.srcPos) // (3) for pureBase <- cls.pureBaseClass do // (4) - def selfType = impl.body + def selfTypeTree = impl.body .collect: case TypeDef(tpnme.SELF, rhs) => rhs .headOption - .getOrElse(tree) - .orElse(tree) + .getOrElse(tree) // Use class tree if self type tree is missing ... + .orElse(tree) // ... or empty. checkSubset(thisSet, CaptureSet.empty.withDescription(i"of pure base class $pureBase"), - selfType.srcPos, cs1description = " captured by this self type") + selfTypeTree.srcPos, cs1description = " captured by this self type") for case tpt: TypeTree <- impl.parents do tpt.tpe match case AppliedType(fn, args) => @@ -993,6 +1072,9 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => super.recheckTyped(tree) + /** Under the sealed policy and with saferExceptions, disallow cap in the + * result type of a try + */ override def recheckTry(tree: Try, pt: Type)(using Context): Type = val tp = super.recheckTry(tree, pt) if ccConfig.useSealed && Feature.enabled(Feature.saferExceptions) then @@ -1018,10 +1100,12 @@ class CheckCaptures extends Recheck, SymTransformer: recheckFinish(result, arg, pt) */ - /** If expected type `pt` is boxed and the tree is a function or a reference, - * don't propagate free variables. - * Otherwise, if the result type is boxed, simulate an unboxing by - * adding all references in the boxed capture set to the current environment. + /** The main recheck method does some box adapation for all nodes: + * - If expected type `pt` is boxed and the tree is a lambda or a reference, + * don't propagate free variables. + * - If the expected type is not boxed but the result type is boxed, + * simulate an unboxing by adding all references in the boxed capture set + * of the result type to the current environment. */ override def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type = val saved = curEnv @@ -1036,14 +1120,12 @@ class CheckCaptures extends Recheck, SymTransformer: else trace.force(i"rechecking $tree with pt = $pt", recheckr, show = true): super.recheck(tree, pt) - catch case ex: NoCommonRoot => - report.error(ex.getMessage.nn) - tree.tpe finally curEnv = saved if tree.isTerm && !pt.isBoxedCapturing && pt != LhsProto then markFree(res.boxedCaptureSet, tree.srcPos) res + /** Under the old unsealed policy: check that cap is ot unboxed */ override def recheckFinish(tpe: Type, tree: Tree, pt: Type)(using Context): Type = checkNotUniversalInUnboxedResult(tpe, tree) super.recheckFinish(tpe, tree, pt) @@ -1073,6 +1155,10 @@ class CheckCaptures extends Recheck, SymTransformer: | |Note that ${msg.toString}""" + /** Addendas for error messages that show where we have under-approximated by + * mapping a a capture ref in contravariant position to the empty set because + * the original result type of the map was not itself a capture ref. + */ private def addApproxAddenda(using Context) = new TypeAccumulator[Addenda]: def apply(add: Addenda, t: Type) = t match @@ -1253,6 +1339,7 @@ class CheckCaptures extends Recheck, SymTransformer: def adaptStr = i"adapting $actual ${if covariant then "~~>" else "<~~"} $expected" + // Get existentials and wildcards out of the way actual match case actual @ Existential(_, actualUnpacked) => return Existential.derivedExistentialType(actual): @@ -1286,11 +1373,10 @@ class CheckCaptures extends Recheck, SymTransformer: else if !leaked.subCaptures(cs, frozen = false).isOK then report.error( - em"""$expected cannot be box-converted to $actual + em"""$expected cannot be box-converted to ${actual.capturing(leaked)} |since the additional capture set $leaked resulted from box conversion is not allowed in $actual""", pos) cs - // Compute the adapted type def adaptedType(resultBoxed: Boolean) = if (adaptedShape eq actualShape) && leaked.isAlwaysEmpty && actualIsBoxed == resultBoxed then actual @@ -1299,35 +1385,43 @@ class CheckCaptures extends Recheck, SymTransformer: .forceBoxStatus(resultBoxed) if needsAdaptation then - val criticalSet = // the set which is not allowed to have `cap` - if covariant then captures // can't box with `cap` - else expected.captureSet // can't unbox with `cap` + val criticalSet = // the set with which we box or unbox + if covariant then captures // covariant: we box with captures of actual type plus captures leaked by inner adapation + else expected.captureSet // contravarant: we unbox with captures of epected type def msg = em"""$actual cannot be box-converted to $expected |since at least one of their capture sets contains the root capability `cap`""" def allowUniversalInBoxed = ccConfig.useSealed || expected.hasAnnotation(defn.UncheckedCapturesAnnot) || actual.widen.hasAnnotation(defn.UncheckedCapturesAnnot) - if criticalSet.isUnboxable && expected.isValueType && !allowUniversalInBoxed then - // We can't box/unbox the universal capability. Leave `actual` as it is - // so we get an error in checkConforms. Add the error message generated - // from boxing as an addendum. This tends to give better error - // messages than disallowing the root capability in `criticalSet`. - if boxErrors != null then boxErrors += msg - if ctx.settings.YccDebug.value then - println(i"cannot box/unbox $actual vs $expected") - actual - else - if !allowUniversalInBoxed then - // Disallow future addition of `cap` to `criticalSet`. - criticalSet.disallowRootCapability: () => - report.error(msg, pos) - if !insertBox then // unboxing - //debugShowEnvs() - markFree(criticalSet, pos) - adaptedType(!actualIsBoxed) - else - adaptedType(actualIsBoxed) + if !allowUniversalInBoxed then + if criticalSet.isUnboxable && expected.isValueType then + // We can't box/unbox the universal capability. Leave `actual` as it is + // so we get an error in checkConforms. Add the error message generated + // from boxing as an addendum. This tends to give better error + // messages than disallowing the root capability in `criticalSet`. + if boxErrors != null then boxErrors += msg + if ctx.settings.YccDebug.value then + println(i"cannot box/unbox $actual vs $expected") + return actual + // Disallow future addition of `cap` to `criticalSet`. + criticalSet.disallowRootCapability: () => + report.error(msg, pos) + + if !insertBox then // we are unboxing + //debugShowEnvs() + markFree(criticalSet, pos) + end if + + // Compute the adapted type. + // The result is boxed if actual is boxed and we don't need to adapt, + // or if actual is unboxed and we do need to adapt. + val resultIsBoxed = actualIsBoxed != needsAdaptation + if (adaptedShape eq actualShape) && leaked.isAlwaysEmpty && actualIsBoxed == resultIsBoxed + then actual + else adaptedShape + .capturing(if alwaysConst then CaptureSet(captures.elems) else captures) + .forceBoxStatus(resultIsBoxed) } end recur @@ -1352,9 +1446,10 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => widened case _ => widened - /** Adapt `actual` type to `expected` type by inserting boxing and unboxing conversions - * - * @param alwaysConst always make capture set variables constant after adaptation + /** Adapt `actual` type to `expected` type. This involves: + * - narrow toplevel captures of `x`'s underlying type to `{x}` according to CC's VAR rule + * - narrow nested captures of `x`'s underlying type to `{x*}` + * - do box adaptation */ def adapt(actual: Type, expected: Type, pos: SrcPos, boxErrors: BoxErrors)(using Context): Type = if expected == LhsProto || expected.isSingleton && actual.isSingleton then @@ -1368,6 +1463,8 @@ class CheckCaptures extends Recheck, SymTransformer: else adapted.showing(i"adapt boxed $actual vs $expected = $adapted", capt) end adapt +// ---- Unit-level rechecking ------------------------------------------- + /** Check overrides again, taking capture sets into account. * TODO: Can we avoid doing overrides checks twice? * We need to do them here since only at this phase CaptureTypes are relevant @@ -1398,6 +1495,7 @@ class CheckCaptures extends Recheck, SymTransformer: finally curEnv = saved actual1 frozen_<:< expected1 + /** Omit the check if one of {overriding,overridden} was nnot capture checked */ override def needsCheck(overriding: Symbol, overridden: Symbol)(using Context): Boolean = !setup.isPreCC(overriding) && !setup.isPreCC(overridden) @@ -1425,36 +1523,43 @@ class CheckCaptures extends Recheck, SymTransformer: checkAllOverrides(ctx.owner.asClass, OverridingPairsCheckerCC(_, _, t)) case _ => traverseChildren(t) + end checkOverrides - private val completed = new mutable.HashSet[Symbol] + /** Used for error reporting: + * Maps mutable variables to the symbols that capture them (in the + * CheckCaptures sense, i.e. symbol is referred to from a different method + * than the one it is defined in). + */ + private val capturedBy = util.HashMap[Symbol, Symbol]() - override def skipRecheck(sym: Symbol)(using Context): Boolean = - completed.contains(sym) + /** Used for error reporting: + * Maps anonymous functions appearing as function arguments to + * the function that is called. + */ + private val anonFunCallee = util.HashMap[Symbol, Symbol]() - /** Check a ValDef or DefDef as an action performed in a completer. Since - * these checks can appear out of order, we need to firsty create the correct - * environment for checking the definition. + /** Used for error reporting: + * Populates `capturedBy` and `anonFunCallee`. Called by `checkUnit`. */ - def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type = - val saved = curEnv - try - // Setup environment to reflect the new owner. - val envForOwner: Map[Symbol, Env] = curEnv.outersIterator - .takeWhile(e => !capturedVars(e.owner).isAlwaysEmpty) // no refs can leak beyind this point - .map(e => (e.owner, e)) - .toMap - def restoreEnvFor(sym: Symbol): Env = - val localSet = capturedVars(sym) - if localSet.isAlwaysEmpty then rootEnv - else envForOwner.get(sym) match - case Some(e) => e - case None => Env(sym, EnvKind.Regular, localSet, restoreEnvFor(sym.owner)) - curEnv = restoreEnvFor(sym.owner) - capt.println(i"Complete $sym in ${curEnv.outersIterator.toList.map(_.owner)}") - try recheckDef(tree, sym) - finally completed += sym - finally - curEnv = saved + private def collectCapturedMutVars(using Context) = new TreeTraverser: + def traverse(tree: Tree)(using Context) = tree match + case id: Ident => + val sym = id.symbol + if sym.is(Mutable, butNot = Method) && sym.owner.isTerm then + val enclMeth = ctx.owner.enclosingMethod + if sym.enclosingMethod != enclMeth then + capturedBy(sym) = enclMeth + case Apply(fn, args) => + for case closureDef(mdef) <- args do + anonFunCallee(mdef.symbol) = fn.symbol + traverseChildren(tree) + case Inlined(_, bindings, expansion) => + traverse(bindings) + traverse(expansion) + case mdef: DefDef => + if !mdef.symbol.isInlineMethod then traverseChildren(tree) + case _ => + traverseChildren(tree) private val setup: SetupAPI = thisPhase.prev.asInstanceOf[Setup] @@ -1475,6 +1580,9 @@ class CheckCaptures extends Recheck, SymTransformer: postCheckWF(unit.tpdTree) if ctx.settings.YccDebug.value then show(unit.tpdTree) // this does not print tree, but makes its variables visible for dependency printing + end checkUnit + +// ----- Checks to do after the rechecking traversal -------------------------- /** Check that self types of subclasses conform to self types of super classes. * (See comment below how this is achieved). The check assumes that classes @@ -1602,6 +1710,10 @@ class CheckCaptures extends Recheck, SymTransformer: checker.traverse(tree.knownType) end healTypeParam + /** Under the unsealed policy: Arrays are like vars, check that their element types + * do not contains `cap` (in fact it would work also to check on array creation + * like we do under sealed). + */ def checkArraysAreSealedIn(tp: Type, pos: SrcPos)(using Context): Unit = val check = new TypeTraverser: def traverse(t: Type): Unit = @@ -1643,7 +1755,7 @@ class CheckCaptures extends Recheck, SymTransformer: checkBounds(normArgs, tl) args.lazyZip(tl.paramNames).foreach(healTypeParam(_, _, fun.symbol)) case _ => - case tree: TypeTree => + case tree: TypeTree if !ccConfig.useSealed => checkArraysAreSealedIn(tree.tpe, tree.srcPos) case _ => end check diff --git a/tests/pos-custom-args/captures/vars1.scala b/tests/pos-custom-args/captures/vars1.scala index 451b8988364f..c8d887e38463 100644 --- a/tests/pos-custom-args/captures/vars1.scala +++ b/tests/pos-custom-args/captures/vars1.scala @@ -8,7 +8,7 @@ object Test: var defaultIncompleteHandler: ErrorHandler = ??? @uncheckedCaptures var incompleteHandler: ErrorHandler = defaultIncompleteHandler - private val x = incompleteHandler.unsafeUnbox + private val x = incompleteHandler val _ : ErrorHandler = x val _ = x(1, "a") From b9d485a125844c1f9a567f674e5d441587d15987 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 8 Nov 2024 19:21:16 +0100 Subject: [PATCH 049/202] Drop legacy caps.unsafe operations Retain only caps.unsafe.unsafeAssumePure --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 2 - .../dotty/tools/dotc/cc/CheckCaptures.scala | 39 +++++++------------ .../dotty/tools/dotc/core/Definitions.scala | 3 -- library/src/scala/caps.scala | 16 -------- 4 files changed, 13 insertions(+), 47 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index eef1b5caacd1..6eb56fd69ab2 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -584,8 +584,6 @@ extension (sym: Symbol) case _ => false containsEnclTypeParam(sym.info.finalResultType) && !sym.allowsRootCapture - && sym != defn.Caps_unsafeBox - && sym != defn.Caps_unsafeUnbox && !defn.isPolymorphicAfterErasure(sym) && !defn.isTypeTestOrCast(sym) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 41a35cb52d39..8b811bf61809 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -607,13 +607,6 @@ class CheckCaptures extends Recheck, SymTransformer: * otherwise return NoType. */ private def recheckUnsafeApply(tree: Apply, pt: Type)(using Context): Type = - - def mapArgUsing(f: Type => Type) = - val arg :: Nil = tree.args: @unchecked - val argType0 = f(recheckStart(arg, pt)) - val argType = super.recheckFinish(argType0, arg, pt) - super.recheckFinish(argType, tree, pt) - val meth = tree.fun.symbol if meth == defn.Caps_unsafeAssumePure then val arg :: Nil = tree.args: @unchecked @@ -623,31 +616,25 @@ class CheckCaptures extends Recheck, SymTransformer: else argType0.widen.stripCapturing capt.println(i"rechecking $arg with $pt: $argType") super.recheckFinish(argType, tree, pt) - else if meth == defn.Caps_unsafeBox then - mapArgUsing(_.forceBoxStatus(true)) - else if meth == defn.Caps_unsafeUnbox then - mapArgUsing(_.forceBoxStatus(false)) - else if meth == defn.Caps_unsafeBoxFunArg then - def forceBox(tp: Type): Type = tp.strippedDealias match - case defn.FunctionOf(paramtpe :: Nil, restpe, isContextual) => - defn.FunctionOf(paramtpe.forceBoxStatus(true) :: Nil, restpe, isContextual) - case tp @ RefinedType(parent, rname, rinfo: MethodType) => - tp.derivedRefinedType(parent, rname, - rinfo.derivedLambdaType( - paramInfos = rinfo.paramInfos.map(_.forceBoxStatus(true)))) - case tp @ CapturingType(parent, refs) => - tp.derivedCapturingType(forceBox(parent), refs) - mapArgUsing(forceBox) else NoType end recheckUnsafeApply - /** Recheck applications. More work is done in `recheckApplication`, - * `recheckArg` and `instantiate` below. + /** Recheck applications, with special handling of unsafeAssumePure. + * More work is done in `recheckApplication`, `recheckArg` and `instantiate` below. */ override def recheckApply(tree: Apply, pt: Type)(using Context): Type = - recheckUnsafeApply(tree, pt).orElse: + val meth = tree.fun.symbol + if meth == defn.Caps_unsafeAssumePure then + val arg :: Nil = tree.args: @unchecked + val argType0 = recheck(arg, pt.capturing(CaptureSet.universal)) + val argType = + if argType0.captureSet.isAlwaysEmpty then argType0 + else argType0.widen.stripCapturing + capt.println(i"rechecking $arg with $pt: $argType") + super.recheckFinish(argType, tree, pt) + else val res = super.recheckApply(tree, pt) - includeCallCaptures(tree.symbol, res, tree.srcPos) + includeCallCaptures(meth, res, tree.srcPos) res /** Recheck argument, and, if formal parameter carries a `@use`, diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 1e36cb6f22db..2890bdf306be 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -999,9 +999,6 @@ class Definitions { @tu lazy val Caps_Exists: ClassSymbol = requiredClass("scala.caps.Exists") @tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe") @tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure") - @tu lazy val Caps_unsafeBox: Symbol = CapsUnsafeModule.requiredMethod("unsafeBox") - @tu lazy val Caps_unsafeUnbox: Symbol = CapsUnsafeModule.requiredMethod("unsafeUnbox") - @tu lazy val Caps_unsafeBoxFunArg: Symbol = CapsUnsafeModule.requiredMethod("unsafeBoxFunArg") @tu lazy val Caps_ContainsTrait: TypeSymbol = CapsModule.requiredType("Contains") @tu lazy val Caps_containsImpl: TermSymbol = CapsModule.requiredMethod("containsImpl") diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala index 6c7610e410c0..53c4ae7dc0dd 100644 --- a/library/src/scala/caps.scala +++ b/library/src/scala/caps.scala @@ -66,20 +66,4 @@ import annotation.{experimental, compileTimeOnly, retainsCap} */ def unsafeAssumePure: T = x - /** If argument is of type `cs T`, converts to type `box cs T`. This - * avoids the error that would be raised when boxing `cap`. - */ - def unsafeBox: T = x - - /** If argument is of type `box cs T`, converts to type `cs T`. This - * avoids the error that would be raised when unboxing `cap`. - */ - def unsafeUnbox: T = x - - extension [T, U](f: T => U) - /** If argument is of type `box cs T`, converts to type `cs T`. This - * avoids the error that would be raised when unboxing `cap`. - */ - def unsafeBoxFunArg: T => U = f - end unsafe From 1e79a2dea28d322af643bfdc33cf76b1b184ae50 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 8 Nov 2024 19:24:30 +0100 Subject: [PATCH 050/202] Don't show redudundant existental wrappers Don't show an `(ex$n: Exists) ->` if the bound variable does not appear in the result. The full type will be shown under -Ycc-debug. Also, avoid spurious ineffective mappings in widenReach. --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 9 +++++++-- .../src/dotty/tools/dotc/printing/RefinedPrinter.scala | 5 ++++- scala2-library-cc/src/scala/collection/SeqView.scala | 1 - tests/neg-custom-args/captures/i21620.check | 4 ++-- tests/neg-custom-args/captures/lazylist.check | 6 +++--- tests/neg-custom-args/captures/reaches2.check | 4 ++-- tests/neg-custom-args/captures/real-try.check | 2 +- tests/neg-custom-args/captures/use-capset.check | 4 ++-- tests/neg-custom-args/captures/uses.check | 4 ++-- 9 files changed, 23 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 6eb56fd69ab2..b19e46de5cf1 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -476,10 +476,12 @@ extension (tp: Type) */ def withReachCaptures(ref: Type)(using Context): Type = object narrowCaps extends TypeMap: + var change = false def apply(t: Type) = if variance <= 0 then t else t.dealiasKeepAnnots match case t @ CapturingType(p, cs) if cs.isUniversal => + change = true t.derivedCapturingType(apply(p), ref.reach.singletonCaptureSet) case t @ AnnotatedType(parent, ann) => // Don't map annotations, which includes capture sets @@ -498,8 +500,11 @@ extension (tp: Type) ref match case ref: CaptureRef if ref.isTrackableRef => val tp1 = narrowCaps(tp) - if tp1 ne tp then capt.println(i"narrow $tp of $ref to $tp1") - tp1 + if narrowCaps.change then + capt.println(i"narrow $tp of $ref to $tp1") + tp1 + else + tp case _ => tp diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 3caba59a091f..95a0007c1dfe 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -30,7 +30,7 @@ import config.SourceVersion.* import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} -import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef, isRetains, ReachCapability, MaybeCapability} +import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef, isRetains, ReachCapability, MaybeCapability, Existential} import dotty.tools.dotc.parsing.JavaParsers class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { @@ -285,6 +285,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if !printDebug && appliedText(tp.asInstanceOf[HKLambda].resType).isEmpty => // don't eta contract if the application would be printed specially toText(tycon) + case Existential(boundVar, unpacked) + if !printDebug && !ctx.settings.YccDebug.value && !unpacked.existsPart(_ == boundVar) => + toText(unpacked) case tp: RefinedType if defn.isFunctionType(tp) && !printDebug => toTextMethodAsFunction(tp.refinedInfo, isPure = Feature.pureFunsEnabled && !tp.typeSymbol.name.isImpureFunction, diff --git a/scala2-library-cc/src/scala/collection/SeqView.scala b/scala2-library-cc/src/scala/collection/SeqView.scala index 292dc61ddaa8..30207fca46ff 100644 --- a/scala2-library-cc/src/scala/collection/SeqView.scala +++ b/scala2-library-cc/src/scala/collection/SeqView.scala @@ -16,7 +16,6 @@ package collection import scala.annotation.nowarn import language.experimental.captureChecking import caps.unsafe.unsafeAssumePure -import scala.annotation.unchecked.uncheckedCaptures /** !!! Scala 2 difference: Need intermediate trait SeqViewOps to collect the * necessary functionality over which SeqViews are defined, and at the same diff --git a/tests/neg-custom-args/captures/i21620.check b/tests/neg-custom-args/captures/i21620.check index ddfcdafab36f..d5be3bab9e73 100644 --- a/tests/neg-custom-args/captures/i21620.check +++ b/tests/neg-custom-args/captures/i21620.check @@ -13,14 +13,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21620.scala:9:31 ---------------------------------------- 9 | val _: () -> () ->{x} Unit = f // error | ^ - | Found: () ->{f} () ->{x} Unit + | Found: (f : () ->{x} () ->{x} Unit) | Required: () -> () ->{x} Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21620.scala:20:33 --------------------------------------- 20 | val _: () ->{} () ->{x} Unit = f // error, but could be OK | ^ - | Found: () ->{f} () ->{x} Unit + | Found: (f : () ->{x} () ->{x} Unit) | Required: () -> () ->{x} Unit | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index f0fbd1a025b5..bc95a445f3f4 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -8,8 +8,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:35:29 ------------------------------------- 35 | val ref1c: LazyList[Int] = ref1 // error | ^^^^ - | Found: lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^?}^{ref1} - | Required: lazylists.LazyList[Int] + | Found: (ref1 : lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^?}^{cap1}) + | Required: lazylists.LazyList[Int] | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:37:36 ------------------------------------- @@ -29,7 +29,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:41:42 ------------------------------------- 41 | val ref4c: LazyList[Int]^{cap1, ref3} = ref4 // error | ^^^^ - | Found: (ref4 : lazylists.LazyList[Int]^{cap3, ref2, ref1}) + | Found: (ref4 : lazylists.LazyList[Int]^{cap3, cap2, ref1, cap1}) | Required: lazylists.LazyList[Int]^{cap1, ref3} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/reaches2.check b/tests/neg-custom-args/captures/reaches2.check index 03860ee4a01b..1e921ee92072 100644 --- a/tests/neg-custom-args/captures/reaches2.check +++ b/tests/neg-custom-args/captures/reaches2.check @@ -2,9 +2,9 @@ 8 | ps.map((x, y) => compose1(x, y)) // error // error | ^ |reference ps* is not included in the allowed capture set {} - |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? (ex$15: caps.Exists) -> A^? + |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? A^? -- Error: tests/neg-custom-args/captures/reaches2.scala:8:13 ----------------------------------------------------------- 8 | ps.map((x, y) => compose1(x, y)) // error // error | ^ |reference ps* is not included in the allowed capture set {} - |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? (ex$15: caps.Exists) -> A^? + |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? A^? diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index a6f514addc95..12ea7262007a 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -27,7 +27,7 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:26:10 ---------------------------------------------------------- 26 | val y = try // error | ^ - | The result of `try` cannot have type () => (ex$8: caps.Exists) -> Cell[Unit]^? since + | The result of `try` cannot have type () => Cell[Unit]^? since | that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. 27 | () => Cell(foo(1)) diff --git a/tests/neg-custom-args/captures/use-capset.check b/tests/neg-custom-args/captures/use-capset.check index d94ff90cddb8..4002e6569c94 100644 --- a/tests/neg-custom-args/captures/use-capset.check +++ b/tests/neg-custom-args/captures/use-capset.check @@ -13,7 +13,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:15:50 ----------------------------------- 15 | val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} | ^^ - | Found: () ->? (x$0: List[box Object^{io}]^{}) ->{io} (ex$13: caps.Exists) -> Object^{io} - | Required: () -> List[box Object^{io}] -> Object^{io} + | Found: (h2 : () ->? (x$0: List[box Object^{io}]^{}) ->{io} Object^{io}) + | Required: () -> List[box Object^{io}] -> Object^{io} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/uses.check b/tests/neg-custom-args/captures/uses.check index de583d75db06..d201d79133cf 100644 --- a/tests/neg-custom-args/captures/uses.check +++ b/tests/neg-custom-args/captures/uses.check @@ -15,14 +15,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:18:34 ----------------------------------------- 18 | val _: () ->{x} () ->{y} Unit = g // error, should be ok | ^ - | Found: () ->{x, y} (ex$7: caps.Exists) -> () ->{y} Unit + | Found: () ->{x, y} () ->{y} Unit | Required: () ->{x} () ->{y} Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:19:28 ----------------------------------------- 19 | val _: () -> () -> Unit = g // error | ^ - | Found: () ->{x, y} (ex$7: caps.Exists) -> () ->{y} Unit + | Found: () ->{x, y} () ->{y} Unit | Required: () -> () -> Unit | | longer explanation available when compiling with `-explain` From ff73a2f944c29449d6aa83b0a091e55fd4dd1443 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 8 Nov 2024 19:38:06 +0100 Subject: [PATCH 051/202] Use unsafeAssumePure instead of asInstanceOf in stdlib-cc --- scala2-library-cc/src/scala/collection/Iterator.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scala2-library-cc/src/scala/collection/Iterator.scala b/scala2-library-cc/src/scala/collection/Iterator.scala index 7d5af9f021dc..91a22caa288c 100644 --- a/scala2-library-cc/src/scala/collection/Iterator.scala +++ b/scala2-library-cc/src/scala/collection/Iterator.scala @@ -17,7 +17,7 @@ import scala.annotation.tailrec import scala.annotation.unchecked.uncheckedVariance import scala.runtime.Statics import language.experimental.captureChecking - +import caps.unsafe.unsafeAssumePure /** Iterators are data structures that allow to iterate over a sequence * of elements. They have a `hasNext` method for checking @@ -1008,7 +1008,7 @@ object Iterator extends IterableFactory[Iterator] { def newBuilder[A]: Builder[A, Iterator[A]] = new ImmutableBuilder[A, Iterator[A]](empty[A]) { override def addOne(elem: A): this.type = { elems = elems ++ single(elem); this } - }.asInstanceOf // !!! CC unsafe op + }.unsafeAssumePure /** Creates iterator that produces the results of some element computation a number of times. * @@ -1160,7 +1160,7 @@ object Iterator extends IterableFactory[Iterator] { @tailrec def merge(): Unit = if (current.isInstanceOf[ConcatIterator[_]]) { val c: ConcatIterator[A] = current.asInstanceOf - current = c.current.asInstanceOf // !!! CC unsafe op + current = c.current.unsafeAssumePure // !!! CC unsafe op currentHasNextChecked = c.currentHasNextChecked if (c.tail != null) { if (last == null) last = c.last From a98903cb371f6f79f761a36c154148834cc9a946 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 11 Nov 2024 13:20:44 +0100 Subject: [PATCH 052/202] Treat by-name closures specially in recheck A by-name Closure node, which is produced by phase ElimByName gets a target type to indicate it's a contextual zero parameter closure. But for the purposes of rechecking and capture checking, it needs to be treated like a function. In particular the type of the closure needs to be derived from the result type of the anonymous function. Fixes #21920 --- .../tools/dotc/transform/ElimByName.scala | 3 ++ .../dotty/tools/dotc/transform/Recheck.scala | 4 +++ tests/neg-custom-args/captures/byname.check | 26 ++++---------- tests/neg-custom-args/captures/byname.scala | 4 +-- tests/neg-custom-args/captures/i21920.check | 10 ++++++ tests/neg-custom-args/captures/i21920.scala | 36 +++++++++++++++++++ 6 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 tests/neg-custom-args/captures/i21920.check create mode 100644 tests/neg-custom-args/captures/i21920.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala index eca3928569f1..0bf2dc107ce9 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala @@ -103,6 +103,9 @@ class ElimByName extends MiniPhase, InfoTransformer: Closure(meth, _ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase), targetType = defn.ByNameFunction(argType) + // Note: this will forget any captures on the original by-name type + // But that's not a problem since we treat these closures specially + // anyway during recheck. ).withSpan(arg.span) private def isByNameRef(tree: Tree)(using Context): Boolean = diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index b7e4b9b14c92..d3173cef252d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -387,6 +387,10 @@ abstract class Recheck extends Phase, SymTransformer: def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean = false)(using Context): Type = if tree.tpt.isEmpty then tree.meth.tpe.widen.toFunctionType(tree.meth.symbol.is(JavaDefined), alwaysDependent = forceDependent) + else if defn.isByNameFunction(tree.tpt.tpe) then + val mt @ MethodType(Nil) = tree.meth.tpe.widen: @unchecked + val cmt = ContextualMethodType(Nil, Nil, mt.resultType) + cmt.toFunctionType(alwaysDependent = forceDependent) else recheck(tree.tpt) diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index c9530f6aad50..1c113591922d 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -1,3 +1,10 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:10:6 ---------------------------------------- +10 | h(f2()) // error + | ^^^^ + | Found: (x$0: Int) ->{cap1} Int + | Required: (x$0: Int) ->? Int + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/byname.scala:19:5 ------------------------------------------------------------- 19 | h(g()) // error | ^^^ @@ -8,22 +15,3 @@ | ^^^ | reference (cap2 : Cap^) is not included in the allowed capture set {cap1} | of an enclosing function literal with expected type () ->{cap1} I --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:4:2 ----------------------------------------- - 4 | def f() = if cap1 == cap1 then g else g // error - | ^ - | Found: ((x$0: Int) ->{cap2} Int)^{} - | Required: Int -> Int - | - | Note that the expected type Int ->{} Int - | is the previously inferred result type of method test - | which is also the type seen in separately compiled sources. - | The new inferred type ((x$0: Int) ->{cap2} Int)^{} - | must conform to this type. - 5 | def g(x: Int) = if cap2 == cap2 then 1 else x - 6 | def g2(x: Int) = if cap1 == cap1 then 1 else x - 7 | def f2() = if cap1 == cap1 then g2 else g2 - 8 | def h(ff: => Int ->{cap2} Int) = ff - 9 | h(f()) -10 | h(f2()) - | - | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/byname.scala b/tests/neg-custom-args/captures/byname.scala index 75ad527dbd2d..dd8fcf1b8818 100644 --- a/tests/neg-custom-args/captures/byname.scala +++ b/tests/neg-custom-args/captures/byname.scala @@ -1,13 +1,13 @@ class Cap extends caps.Capability def test(cap1: Cap, cap2: Cap) = - def f() = if cap1 == cap1 then g else g // error + def f() = if cap1 == cap1 then g else g def g(x: Int) = if cap2 == cap2 then 1 else x def g2(x: Int) = if cap1 == cap1 then 1 else x def f2() = if cap1 == cap1 then g2 else g2 def h(ff: => Int ->{cap2} Int) = ff h(f()) - h(f2()) + h(f2()) // error class I diff --git a/tests/neg-custom-args/captures/i21920.check b/tests/neg-custom-args/captures/i21920.check new file mode 100644 index 000000000000..8efa24426d01 --- /dev/null +++ b/tests/neg-custom-args/captures/i21920.check @@ -0,0 +1,10 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21920.scala:34:34 --------------------------------------- +34 | val cell: Cell[File] = File.open(f => Cell(Seq(f))) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: Cell[box File^{f, f²}]{val head: () ?->? IterableOnce[box File^{f, f²}]^?}^? + | Required: Cell[File] + | + | where: f is a reference to a value parameter + | f² is a reference to a value parameter + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21920.scala b/tests/neg-custom-args/captures/i21920.scala new file mode 100644 index 000000000000..7ea5a63969b1 --- /dev/null +++ b/tests/neg-custom-args/captures/i21920.scala @@ -0,0 +1,36 @@ +import language.experimental.captureChecking + +trait Iterator[+A] extends IterableOnce[A]: + self: Iterator[A]^ => + def next(): A + +trait IterableOnce[+A] extends Any: + def iterator: Iterator[A]^{this} + +final class Cell[A](head: => IterableOnce[A]^): + def headIterator: Iterator[A]^{this} = head.iterator + +class File private (): + private var closed = false + + def close() = closed = true + + def read() = + assert(!closed, "File closed") + 1 + +object File: + def open[T](f: File^ => T): T = + val file = File() + try + f(file) + finally + file.close() + +object Seq: + def apply[A](xs: A*): IterableOnce[A] = ??? + +@main def Main() = + val cell: Cell[File] = File.open(f => Cell(Seq(f))) // error + val file = cell.headIterator.next() + file.read() From d8ea08df1367f8073606b0de44ac80489e4ac9e4 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 12 Nov 2024 14:59:59 +0100 Subject: [PATCH 053/202] Polish and comment Setup class --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 1 + .../src/dotty/tools/dotc/cc/Existential.scala | 1 + compiler/src/dotty/tools/dotc/cc/Setup.scala | 238 ++++++++++++------ 3 files changed, 160 insertions(+), 80 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index b19e46de5cf1..2b0d89d9703e 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -592,6 +592,7 @@ extension (sym: Symbol) && !defn.isPolymorphicAfterErasure(sym) && !defn.isTypeTestOrCast(sym) + /** It's a parameter accessor that is not annotated @constructorOnly or @uncheckedCaptures */ def isRefiningParamAccessor(using Context): Boolean = sym.is(ParamAccessor) && { diff --git a/compiler/src/dotty/tools/dotc/cc/Existential.scala b/compiler/src/dotty/tools/dotc/cc/Existential.scala index d51ce1b08dbd..ea979e0b9f7f 100644 --- a/compiler/src/dotty/tools/dotc/cc/Existential.scala +++ b/compiler/src/dotty/tools/dotc/cc/Existential.scala @@ -337,6 +337,7 @@ object Existential: if needsWrap then wrapped else tp end mapCap + /** Map `cap` in function results to fresh existentials */ def mapCapInResults(fail: Message => Unit)(using Context): TypeMap = new: def mapFunOrMethod(tp: Type, args: List[Type], res: Type): Type = diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 647e5a2cfd51..33ccea7c9995 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -13,7 +13,7 @@ import ast.tpd, tpd.* import transform.{PreRecheck, Recheck}, Recheck.* import CaptureSet.{IdentityCaptRefMap, IdempotentCaptRefMap} import Synthetics.isExcluded -import util.Property +import util.{Property, SimpleIdentitySet} import reporting.Message import printing.{Printer, Texts}, Texts.{Text, Str} import collection.mutable @@ -22,6 +22,8 @@ import dotty.tools.dotc.util.NoSourcePosition /** Operations accessed from CheckCaptures */ trait SetupAPI: + + /** The operation to recheck a ValDef or DefDef */ type DefRecheck = (tpd.ValOrDefDef, Symbol) => Context ?=> Type /** Setup procedure to run for each compilation unit @@ -54,7 +56,9 @@ object Setup: end Setup import Setup.* -/** A tree traverser that prepares a compilation unit to be capture checked. +/** Phase that sets up everthing for capture checking. + * + * A tree traverser that prepares a compilation unit to be capture checked. * It does the following: * - For every inferred type, drop any retains annotations, * add capture sets to all its parts, add refinements to class types and function types. @@ -76,8 +80,16 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere + /** A set containing symbols whose denotation is in train of being updated + * Used to suppress transforming the denotations of these symbols. + */ private val toBeUpdated = new mutable.HashSet[Symbol] + /** Drops `private` from the flags of `symd` provided it is + * a parameter accessor that's not `constructorOnly` or `uncheckedCaptured` + * and that contains at least one @retains in co- or in-variant position. + * The @retains mught be implicit for a type deriving from `Capability`. + */ private def newFlagsFor(symd: SymDenotation)(using Context): FlagSet = object containsCovarRetains extends TypeAccumulator[Boolean]: @@ -101,30 +113,17 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else symd.flags end newFlagsFor + /** Symbol is a term member of a class that was not capture checked + * The info of these symbols is made fluid. + */ def isPreCC(sym: Symbol)(using Context): Boolean = sym.isTerm && sym.maybeOwner.isClass && !sym.is(Module) && !sym.owner.is(CaptureChecked) && !defn.isFunctionSymbol(sym.owner) - private def fluidify(using Context) = new TypeMap with IdempotentCaptRefMap: - def apply(t: Type): Type = t match - case t: MethodType => - mapOver(t) - case t: TypeLambda => - t.derivedLambdaType(resType = this(t.resType)) - case CapturingType(_, _) => - t - case _ => - val t1 = t match - case t @ defn.RefinedFunctionOf(rinfo: MethodType) => - t.derivedRefinedType(t.parent, t.refinedName, this(rinfo)) - case _ => - mapOver(t) - if variance > 0 then t1 - else decorate(t1, addedSet = Function.const(CaptureSet.Fluid)) - - /** - Reset `private` flags of parameter accessors so that we can refine them + /** The symbol transformer of this phase. + * - Resets `private` flags of parameter accessors so that we can refine them * in Setup if they have non-empty capture sets. * - Special handling of some symbols defined for case classes. * Enabled only until recheck is finished, and provided some compilation unit @@ -134,7 +133,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if !pastRecheck && Feature.ccEnabledSomewhere then val sym = symd.symbol def mappedInfo = - if toBeUpdated.contains(sym) then symd.info + if toBeUpdated.contains(sym) + then symd.info // don't transform symbols that will anyway be updated else transformExplicitType(symd.info) if Synthetics.needsTransform(symd) then Synthetics.transform(symd, mappedInfo) @@ -183,7 +183,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tp: MethodOrPoly => tp // don't box results of methods outside refinements case _ => recur(tp) - /** Perform the following transformation steps everywhere in a type: + /** Transform the type of an InferredTypeTree by performing the following transformation + * steps everywhere in the type: * 1. Drop retains annotations * 2. Turn plain function types into dependent function types, so that * we can refer to their parameters in capture sets. Currently this is @@ -202,8 +203,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: override def toString = "map inferred" /** Refine a possibly applied class type C where the class has tracked parameters - * x_1: T_1, ..., x_n: T_n to C { val x_1: CV_1 T_1, ..., val x_n: CV_n T_n } - * where CV_1, ..., CV_n are fresh capture sets. + * x_1: T_1, ..., x_n: T_n to C { val x_1: T_1^{CV_1}, ..., val x_n: T_n^{CV_n} } + * where CV_1, ..., CV_n are fresh capture set variables. */ def addCaptureRefinements(tp: Type): Type = tp match case _: TypeRef | _: AppliedType if refine && tp.typeParams.isEmpty => @@ -287,14 +288,22 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: try val tp1 = mapInferred(refine = true)(tp) val tp2 = Existential.mapCapInResults(_ => assert(false))(tp1) - if tp2 ne tp then capt.println(i"expanded implicit in ${ctx.owner}: $tp --> $tp1 --> $tp2") + if tp2 ne tp then capt.println(i"expanded inferred in ${ctx.owner}: $tp --> $tp1 --> $tp2") tp2 catch case ex: AssertionError => println(i"error while mapping inferred $tp") throw ex end transformInferredType - private def transformExplicitType(tp: Type, tptToCheck: Option[Tree] = None)(using Context): Type = + /** Transform an explicitly given type by performing the following transformation + * steps everywhere in the type: + * 1. Expand throws aliases to contextual function type with CanThrow parameters + * 2. Map types with retains annotations to CapturingTypes + * 3. Add universal capture sets to types deriving from Capability + * 4. Map `cap` in function result types to existentially bound variables. + * 5. Schedule deferred well-formed tests for types with retains annotations. + */ + private def transformExplicitType(tp: Type, tptToCheck: Tree = EmptyTree)(using Context): Type = val toCapturing = new DeepTypeMap: override def toString = "expand aliases" @@ -323,6 +332,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: CapturingType(fntpe, cs, boxed = false) else fntpe + /** If C derives from Capability and we have a C^cs in source, we leave it as is + * instead of expanding it to C^{cap}^cs. We do this by stripping capability-generated + * universal capture sets from the parent of a CapturingType. + */ def stripImpliedCaptureSet(tp: Type): Type = tp match case tp @ CapturingType(parent, refs) if (refs eq defn.universalCSImpliedByCapability) && !tp.isBoxedCapturing => @@ -338,7 +351,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val parent1 = this(parent) if ann.symbol.isRetains then val parent2 = stripImpliedCaptureSet(parent1) - for tpt <- tptToCheck do + if !tptToCheck.isEmpty then checkWellformedLater(parent2, ann.tree, tpt) try CapturingType(parent2, ann.tree.toCaptureSet) @@ -357,7 +370,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: end toCapturing def fail(msg: Message) = - for tree <- tptToCheck do report.error(msg, tree.srcPos) + if !tptToCheck.isEmpty then report.error(msg, tptToCheck.srcPos) val tp1 = toCapturing(tp) val tp2 = Existential.mapCapInResults(fail)(tp1) @@ -365,13 +378,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tp2 end transformExplicitType - /** Transform type of type tree, and remember the transformed type as the type the tree */ + /** Transform type of tree, and remember the transformed type as the type the tree */ private def transformTT(tree: TypeTree, boxed: Boolean)(using Context): Unit = if !tree.hasRememberedType then val transformed = if tree.isInferred then transformInferredType(tree.tpe) - else transformExplicitType(tree.tpe, tptToCheck = Some(tree)) + else transformExplicitType(tree.tpe, tptToCheck = tree) tree.rememberType(if boxed then box(transformed) else transformed) /** Substitute parameter symbols in `from` to paramRefs in corresponding @@ -417,27 +430,32 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: toBeUpdated += sym sym.updateInfo(thisPhase, info, newFlagsFor(sym)) toBeUpdated -= sym - sym.namedType match - case ref: CaptureRef if ref.isTrackableRef => ref.invalidateCaches() // TODO: needed? - case _ => + /** The info of `sym` at the CheckCaptures phase */ extension (sym: Symbol) def nextInfo(using Context): Type = atPhase(thisPhase.next)(sym.info) + /** A traverser that adds knownTypes and updates symbol infos */ def setupTraverser(recheckDef: DefRecheck) = new TreeTraverserWithPreciseImportContexts: + /** Transform the type of a val or var or the result type of a def */ def transformResultType(tpt: TypeTree, sym: Symbol)(using Context): Unit = + // First step: Transform the type and record it as knownType of tpt. try transformTT(tpt, boxed = sym.is(Mutable, butNot = Method) && !ccConfig.useSealed && !sym.hasAnnotation(defn.UncheckedCapturesAnnot), - // types of mutable variables are boxed in pre 3.3 code + // Under the sealed policy, we disallow root capabilities in the type of mutable + // variables, no need to box them here. ) catch case ex: IllegalCaptureRef => capt.println(i"fail while transforming result type $tpt of $sym") throw ex + + // Second step: Add descriptions for all capture set variables created in + // step one stating that they belong to `sym`. val addDescription = new TypeTraverser: def traverse(tp: Type) = tp match case tp @ CapturingType(parent, refs) => @@ -447,6 +465,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => traverseChildren(tp) addDescription.traverse(tpt.knownType) + end transformResultType def traverse(tree: Tree)(using Context): Unit = tree match @@ -461,7 +480,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: paramss.foreach(traverse) transformResultType(tpt, meth) traverse(tree.rhs) - //println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}") case tree @ ValDef(_, tpt: TypeTree, _) => val sym = tree.symbol @@ -469,7 +487,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) inContext(defCtx): transformResultType(tpt, sym) - capt.println(i"mapped $tree = ${tpt.knownType}") traverse(tree.rhs) case tree @ TypeApply(fn, args) => @@ -498,30 +515,33 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: checkProperUse(tree) end traverse + /** Processing done on node `tree` after its children are traversed */ def postProcess(tree: Tree)(using Context): Unit = tree match case tree: TypeTree => transformTT(tree, boxed = false) case tree: ValOrDefDef => + // Make sure denotation of tree's symbol is correct val sym = tree.symbol - /** The return type of a constructor instantiated with local type and value - * parameters. Constructors have `unit` result type, that's why we can't - * get this type by reading the result type tree, and have to construct it - * explicitly. - */ + // The return type of a constructor instantiated with local type and value + // parameters. Constructor defs have `Unit` as result type tree, that's why + // we can't get this type by reading the result type tree, and have to construct + // it explicitly. def constrReturnType(info: Type, psymss: List[List[Symbol]]): Type = info match case info: MethodOrPoly => constrReturnType(info.instantiate(psymss.head.map(_.namedType)), psymss.tail) case _ => info - /** The local result type, which is the known type of the result type tree, - * with special treatment for constructors. - */ + // The local result type, which is the known type of the result type tree, + // with special treatment for constructors. def localReturnType = if sym.isConstructor then constrReturnType(sym.info, sym.paramSymss) else tree.tpt.knownType + // A test whether parameter signature might change. This returns true if one of + // the parameters has a remembered type. The idea here is that we store a remembered + // type only if the transformed type is different from the original. def paramSignatureChanges = tree.match case tree: DefDef => tree.paramss.nestedExists: @@ -535,7 +555,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tree.tpt.hasRememberedType && !sym.isConstructor || paramSignatureChanges // Replace an existing symbol info with inferred types where capture sets of - // TypeParamRefs and TermParamRefs put in correspondence by BiTypeMaps with the + // TypeParamRefs and TermParamRefs are put in correspondence by BiTypeMaps with the // capture sets of the types of the method's parameter symbols and result type. def integrateRT( info: Type, // symbol info to replace @@ -572,6 +592,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if prevLambdas.isEmpty then resType else SubstParams(prevPsymss, prevLambdas)(resType) + // If there's a change in the signature, update the info of `sym` if sym.exists && signatureChanges then val newInfo = Existential.mapCapInResults(report.error(_, tree.srcPos)): @@ -588,9 +609,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // the expected type and only afterwards we recheck the definition newInfo else new LazyType: + // infos of other methods are determined from their definitions, which + // are checked on demand def complete(denot: SymDenotation)(using Context) = - // infos of other methods are determined from their definitions which - // are checked on demand assert(ctx.phase == thisPhase.next, i"$sym") capt.println(i"forcing $sym, printing = ${ctx.mode.is(Mode.Printing)}") //if ctx.mode.is(Mode.Printing) then new Error().printStackTrace() @@ -606,9 +627,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case cls: ClassSymbol => inNestedLevelUnless(cls.is(Module)): val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo - def innerModule = cls.is(ModuleClass) && !cls.isStatic + + // Compute new self type + def isInnerModule = cls.is(ModuleClass) && !cls.isStatic val selfInfo1 = - if (selfInfo ne NoType) && !innerModule then + if (selfInfo ne NoType) && !isInnerModule then // if selfInfo is explicitly given then use that one, except if // self info applies to non-static modules, these still need to be inferred selfInfo @@ -623,8 +646,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // self types (to which we also add nested module classes), provided they are // neither pure, nor are publicily extensible with an unconstrained self type. CapturingType(cinfo.selfType, CaptureSet.Var(cls, level = currentLevel)) + + // Compute new parent types val ps1 = inContext(ctx.withOwner(cls)): ps.mapConserve(transformExplicitType(_)) + + // Install new types and if it is a module class also update module object if (selfInfo1 ne selfInfo) || (ps1 ne ps) then val newInfo = ClassInfo(prefix, cls, ps1, decls, selfInfo1) updateInfo(cls, newInfo) @@ -639,6 +666,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => end postProcess + /** Check that @use annotations only appear on parameters and not on anonymous function parameters */ def checkProperUse(tree: Tree)(using Context): Unit = tree match case tree: MemberDef => def useAllowed(sym: Symbol) = @@ -650,7 +678,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: end checkProperUse end setupTraverser - /** Checks whether an abstract type could be impure. See also: [[needsVariable]]. */ +// --------------- Adding capture set variables ---------------------------------- + + /** Checks whether an abstract type or its bound could be impure. If that's the case, + * the abstract type gets a capture set variable in `decorate`. + * See also: [[needsVariable]]. + */ private def instanceCanBeImpure(tp: Type)(using Context): Boolean = { tp.dealiasKeepAnnots match case CapturingType(_, refs) => @@ -673,7 +706,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: false }.showing(i"instance can be impure $tp = $result", capt) - /** Should a capture set variable be added on type `tp`? */ + /** Should a capture set variable be added on type `tp`? + * Notable exceptions are: + * - If type refers to a class which is Pure, or it is Any, it does not need a variable. + * - If type is an abstract or parameter type we decide according to `instanceCanBeImpure` + * - If type is a capturing type that has already a capture set variable or has + * the universal capture set, it does not need a variable. + */ def needsVariable(tp: Type)(using Context): Boolean = { tp.typeParams.isEmpty && tp.match case tp: (TypeRef | AppliedType) => @@ -705,6 +744,61 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: false }.showing(i"can have inferred capture $tp = $result", captDebug) + /** Add a capture set variable or set to `tp` if necessary. + * Dealias `tp` (but keep annotations and opaque types) if doing + * so ends up adding a capture set. + * @param tp the type to add a capture set to + * @param added A function producing the added capture set from a set of initial elements. + */ + def decorate(tp: Type, added: CaptureSet.Refs => CaptureSet)(using Context): Type = + if tp.typeSymbol == defn.FromJavaObjectSymbol then + // For capture checking, we assume Object from Java is the same as Any + tp + else + def maybeAdd(target: Type, fallback: Type) = + if needsVariable(target) then + target match + case CapturingType(_, CaptureSet.Fluid) => + target + case CapturingType(target1, cs1) if cs1.isConst => + CapturingType(target1, added(cs1.elems)) + case _ => + CapturingType(target, added(SimpleIdentitySet.empty)) + else fallback + val dealiased = tp.dealiasKeepAnnotsAndOpaques + if dealiased ne tp then + val transformed = transformInferredType(dealiased) + maybeAdd(transformed, if transformed ne dealiased then transformed else tp) + else maybeAdd(tp, tp) + + /** Add a capture set variable to `tp` if necessary. */ + private def addVar(tp: Type, owner: Symbol)(using Context): Type = + decorate(tp, CaptureSet.Var(owner, _, level = currentLevel)) + + /** A map that adds capture sets at all contra- and invariant positions + * in a type where a capture set would be needed. This is used to make types + * that were not capture checked compatible with types that are capture checked. + * We don't need to add in covariant positions since pure types are + * anyway compatible with capturing types. + */ + private def fluidify(using Context) = new TypeMap with IdempotentCaptRefMap: + def apply(t: Type): Type = t match + case t: MethodType => + mapOver(t) + case t: TypeLambda => + t.derivedLambdaType(resType = this(t.resType)) + case CapturingType(_, _) => + t + case _ => + val t1 = t match + case t @ defn.RefinedFunctionOf(rinfo: MethodType) => + // Just refine the apply method, don't bother with the parent + t.derivedRefinedType(t.parent, t.refinedName, this(rinfo)) + case _ => + mapOver(t) + if variance > 0 then t1 + else decorate(t1, Function.const(CaptureSet.Fluid)) + /** Pull out an embedded capture set from a part of `tp` */ def normalizeCaptures(tp: Type)(using Context): Type = tp match case tp @ RefinedType(parent @ CapturingType(parent1, refs), rname, rinfo) => @@ -736,32 +830,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => tp - /** Add a capture set variable to `tp` if necessary, or maybe pull out - * an embedded capture set variable from a part of `tp`. - */ - def decorate(tp: Type, addedSet: Type => CaptureSet)(using Context): Type = - if tp.typeSymbol == defn.FromJavaObjectSymbol then - // For capture checking, we assume Object from Java is the same as Any - tp - else - def maybeAdd(target: Type, fallback: Type) = - if needsVariable(target) then CapturingType(target, addedSet(target)) - else fallback - val dealiased = tp.dealiasKeepAnnotsAndOpaques - if dealiased ne tp then - val transformed = transformInferredType(dealiased) - maybeAdd(transformed, if transformed ne dealiased then transformed else tp) - else maybeAdd(tp, tp) - - /** Add a capture set variable to `tp` if necessary, or maybe pull out - * an embedded capture set variable from a part of `tp`. + /** Run setup on a compilation unit with given `tree`. + * @param recheckDef the function to run for completing a val or def */ - private def addVar(tp: Type, owner: Symbol)(using Context): Type = - decorate(tp, - addedSet = _.dealias.match - case CapturingType(_, refs) => CaptureSet.Var(owner, refs.elems, level = currentLevel) - case _ => CaptureSet.Var(owner, level = currentLevel)) - def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit = setupTraverser(recheckDef).traverse(tree)(using ctx.withPhase(thisPhase)) @@ -776,8 +847,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * This check is performed after capture sets are computed in phase cc. * Note: We need to perform the check on the original annotation rather than its * capture set since the conversion to a capture set already eliminates redundant elements. + * @param parent the parent of the capturing type + * @param ann the original retains annotation + * @param tpt the tree for which an error or warning should be reported */ - private def checkWellformedPost(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = + private def checkWellformed(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = capt.println(i"checkWF post $parent ${ann.retainedElems} in $tpt") var retained = ann.retainedElems.toArray for i <- 0 until retained.length do @@ -806,14 +880,18 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: check(remaining, remaining) end for end for - end checkWellformedPost + end checkWellformed - /** Check well formed at post check time */ + /** Check well formed at post check time. We need to wait until after + * recheck because we find out only then whether capture sets are empty or + * capture references are redundant. + */ private def checkWellformedLater(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = if !tpt.span.isZeroExtent && enclosingInlineds.isEmpty then todoAtPostCheck += (ctx1 => - checkWellformedPost(parent, ann, tpt)(using ctx1.withOwner(ctx.owner))) + checkWellformed(parent, ann, tpt)(using ctx1.withOwner(ctx.owner))) + /** Run all well formedness tests that were deferred to post check */ def postCheck()(using Context): Unit = for chk <- todoAtPostCheck do chk(ctx) todoAtPostCheck.clear() From e309d5a541474c9dfec01320438098af72f91ec0 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 15 Nov 2024 18:26:12 +0100 Subject: [PATCH 054/202] Some tweaks after code walkthrough --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 8b811bf61809..4b969ad360c9 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -259,7 +259,7 @@ class CheckCaptures extends Recheck, SymTransformer: * is that it is sometimes better for type inference to not constrain too early * with a checkConformsExpr. */ - private var todoAtPostCheck = new mutable.ListBuffer[() => Unit] + private val todoAtPostCheck = new mutable.ListBuffer[() => Unit] override def keepType(tree: Tree) = super.keepType(tree) @@ -453,8 +453,8 @@ class CheckCaptures extends Recheck, SymTransformer: val isVisible = c.pathRoot match case ref: NamedType => isVisibleFromEnv(ref.symbol.owner, env) case ref: ThisType => isVisibleFromEnv(ref.cls, env) - case ref => - false + case ref => false + if !isVisible then if ccConfig.deferredReaches then avoidLocalCapability(c, env, lastEnv) @@ -603,22 +603,6 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => formal funtpe.derivedLambdaType(paramInfos = paramInfosWithUses) - /** If this is an application of a caps.unsafe method, handle it specially - * otherwise return NoType. - */ - private def recheckUnsafeApply(tree: Apply, pt: Type)(using Context): Type = - val meth = tree.fun.symbol - if meth == defn.Caps_unsafeAssumePure then - val arg :: Nil = tree.args: @unchecked - val argType0 = recheck(arg, pt.capturing(CaptureSet.universal)) - val argType = - if argType0.captureSet.isAlwaysEmpty then argType0 - else argType0.widen.stripCapturing - capt.println(i"rechecking $arg with $pt: $argType") - super.recheckFinish(argType, tree, pt) - else NoType - end recheckUnsafeApply - /** Recheck applications, with special handling of unsafeAssumePure. * More work is done in `recheckApplication`, `recheckArg` and `instantiate` below. */ @@ -667,7 +651,7 @@ class CheckCaptures extends Recheck, SymTransformer: * is the capture set of the argument. * If the function `f` does have an `@use` parameter, then it could in addition * unbox reach capabilities over its formal parameter. Therefore, the approximation - * would be `Cq \union dcs(Ca)` instead. + * would be `Cq \union dcs(Ta)` instead. * If the approximation might subcapture the declared result Cr, we pick it for C * otherwise we pick Cr. */ @@ -741,7 +725,7 @@ class CheckCaptures extends Recheck, SymTransformer: for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol if !getter.is(Private) && getter.hasTrackedParts then - refined = RefinedType(refined, getterName, argType.unboxed) + refined = RefinedType(refined, getterName, argType.unboxed) // Yichen you might want to check this allCaptures ++= argType.captureSet (refined, allCaptures) @@ -783,7 +767,7 @@ class CheckCaptures extends Recheck, SymTransformer: end recheckTypeApply /** Faced with a tree of form `caps.contansImpl[CS, r.type]`, check that `R` is a tracked - * capability and assert that `{r} <:CS`. + * capability and assert that `{r} <: CS`. */ def checkContains(tree: TypeApply)(using Context): Unit = tree match case ContainsImpl(csArg, refArg) => From 6f51fc0825aaeafcc7d42123107313453585cfd8 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 21 Nov 2024 19:31:38 +0100 Subject: [PATCH 055/202] Address review comments --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 5 ++--- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 2 +- .../src/dotty/tools/dotc/cc/CheckCaptures.scala | 16 +++++++--------- .../tools/dotc/printing/RefinedPrinter.scala | 2 +- tests/neg-custom-args/captures/bad-uses-2.scala | 3 ++- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 2b0d89d9703e..bc4eb92234eb 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -269,10 +269,9 @@ extension (tp: Type) tp /** The first element of this path type */ - final def pathRoot(using Context): Type = tp.dealiasKeepAnnots match + final def pathRoot(using Context): Type = tp.dealias match case tp1: NamedType if tp1.symbol.owner.isClass => tp1.prefix.pathRoot - case ReachCapability(tp1) => tp1.pathRoot - case _ => tp + case tp1 => tp1 /** If this part starts with `C.this`, the class `C`. * Otherwise, if it starts with a reference `r`, `r`'s owner. diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 675b00a09ff5..5be4f6a2d1cd 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1137,7 +1137,7 @@ object CaptureSet: seen += t.symbol val upper = t.info.bounds.hi if includeTypevars && upper.isExactlyAny then CaptureSet.universal - else this(cs, t.info.bounds.hi) + else this(cs, upper) case t @ FunctionOrMethod(args, res @ Existential(_, _)) if args.forall(_.isAlwaysPure) => this(cs, Existential.toCap(res)) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 4b969ad360c9..23e7d8f8ecf8 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -380,10 +380,12 @@ class CheckCaptures extends Recheck, SymTransformer: // A captured reference with the symbol `sym` is visible from the environment // if `sym` is not defined inside the owner of the environment. inline def isVisibleFromEnv(sym: Symbol, env: Env) = - if env.kind == EnvKind.NestedInOwner then - !sym.isProperlyContainedIn(env.owner) - else - !sym.isContainedIn(env.owner) + sym.exists && { + if env.kind == EnvKind.NestedInOwner then + !sym.isProperlyContainedIn(env.owner) + else + !sym.isContainedIn(env.owner) + } /** If captureRef `c` refers to a parameter that is not @use declared, report an error. * Exception under deferredReaches: If use comes from a nested closure, accept it. @@ -450,11 +452,7 @@ class CheckCaptures extends Recheck, SymTransformer: // Only captured references that are visible from the environment // should be included. val included = cs.filter: c => - val isVisible = c.pathRoot match - case ref: NamedType => isVisibleFromEnv(ref.symbol.owner, env) - case ref: ThisType => isVisibleFromEnv(ref.cls, env) - case ref => false - + val isVisible = isVisibleFromEnv(c.pathOwner, env) if !isVisible then if ccConfig.deferredReaches then avoidLocalCapability(c, env, lastEnv) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 95a0007c1dfe..a399b8173b5d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -30,7 +30,7 @@ import config.SourceVersion.* import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} -import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef, isRetains, ReachCapability, MaybeCapability, Existential} +import cc.* import dotty.tools.dotc.parsing.JavaParsers class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { diff --git a/tests/neg-custom-args/captures/bad-uses-2.scala b/tests/neg-custom-args/captures/bad-uses-2.scala index 00eec3e5a2e9..8dd121b2b134 100644 --- a/tests/neg-custom-args/captures/bad-uses-2.scala +++ b/tests/neg-custom-args/captures/bad-uses-2.scala @@ -3,4 +3,5 @@ class Test: @use def F = ??? // error @use val x = ??? // error @use type T // error - def foo(@use c: Test): Unit = ??? // OK + def foo[@use T](@use c: T): Unit = ??? // OK + From da14277dd764ea08cac36002d3cf8ea58c4c611f Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 21 Nov 2024 19:41:59 +0100 Subject: [PATCH 056/202] Fix rebase breakage --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 4 ++-- tests/neg-custom-args/captures/use-capset.check | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 33ccea7c9995..dfb9ec70bdba 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -352,11 +352,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if ann.symbol.isRetains then val parent2 = stripImpliedCaptureSet(parent1) if !tptToCheck.isEmpty then - checkWellformedLater(parent2, ann.tree, tpt) + checkWellformedLater(parent2, ann.tree, tptToCheck) try CapturingType(parent2, ann.tree.toCaptureSet) catch case ex: IllegalCaptureRef => - report.error(em"Illegal capture reference: ${ex.getMessage.nn}", tptToCheck.fold(NoSourcePosition)(_.srcPos)) + report.error(em"Illegal capture reference: ${ex.getMessage.nn}", tptToCheck.srcPos) parent2 else t.derivedAnnotatedType(parent1, ann) diff --git a/tests/neg-custom-args/captures/use-capset.check b/tests/neg-custom-args/captures/use-capset.check index 4002e6569c94..cb330daf67f8 100644 --- a/tests/neg-custom-args/captures/use-capset.check +++ b/tests/neg-custom-args/captures/use-capset.check @@ -13,7 +13,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:15:50 ----------------------------------- 15 | val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} | ^^ - | Found: (h2 : () ->? (x$0: List[box Object^{io}]^{}) ->{io} Object^{io}) - | Required: () -> List[box Object^{io}] -> Object^{io} + | Found: (h2 : () ->? (x$0: List[box Object^]^{}) ->{io} Object^{io}) + | Required: () -> List[box Object^{io}] -> Object^{io} | | longer explanation available when compiling with `-explain` From 353f80b416356b9b32e69edcc2b93847a298045b Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 22 Nov 2024 15:05:58 +0100 Subject: [PATCH 057/202] Fix more rebase breakage --- tests/neg-custom-args/captures/lazylist.check | 2 +- tests/neg-custom-args/captures/real-try.check | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index bc95a445f3f4..65fed0c4ec7e 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -29,7 +29,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:41:42 ------------------------------------- 41 | val ref4c: LazyList[Int]^{cap1, ref3} = ref4 // error | ^^^^ - | Found: (ref4 : lazylists.LazyList[Int]^{cap3, cap2, ref1, cap1}) + | Found: (ref4 : lazylists.LazyList[Int]^{cap3, ref1, ref2}) | Required: lazylists.LazyList[Int]^{cap1, ref3} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index 12ea7262007a..7a4b12ac08f6 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -1,7 +1,7 @@ --- [E190] Potential Issue Warning: tests/neg-custom-args/captures/real-try.scala:38:4 ---------------------------------- -38 | b.x - | ^^^ - | Discarded non-Unit value of type () -> Unit. Add `: Unit` to discard silently. +-- [E190] Potential Issue Warning: tests/neg-custom-args/captures/real-try.scala:38:2 ---------------------------------- +38 | b + | ^ + | Discarded non-Unit value of type Cell[() -> Unit]. Add `: Unit` to discard silently. | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/real-try.scala:14:2 ----------------------------------------------------------- From 7653e85f676f7a5d965b5ca879000954ad723f2a Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Fri, 22 Nov 2024 12:56:13 +0100 Subject: [PATCH 058/202] Delete duplicate inductive-implicits test This is a duplicate of `tests/bench/inductive-implicits.scala`, which is already tested from `dotty.tools.dotc.BootstrappedOnlyCompilationTest`, also without `-Yno-deep-subtypes`. --- .../inductive-implicits-bench.scala | 127 ------------------ 1 file changed, 127 deletions(-) delete mode 100644 tests/pos-deep-subtype/inductive-implicits-bench.scala diff --git a/tests/pos-deep-subtype/inductive-implicits-bench.scala b/tests/pos-deep-subtype/inductive-implicits-bench.scala deleted file mode 100644 index aff8bffd8024..000000000000 --- a/tests/pos-deep-subtype/inductive-implicits-bench.scala +++ /dev/null @@ -1,127 +0,0 @@ -// Adapted from https://github.com/scala/compiler-benchmark/blob/master/corpus/induction/latest/inductive-implicits-bench.scala - -// With polymorphic implicit pruning: -// set resolvers in compilation += "pr-scala snapshots" at "https://scala-ci.typesafe.com/artifactory/scala-pr-validation-snapshots/" -// set scalaVersion in compilation := "2.13.0-pre-765b3ed-SNAPSHOT" -// -// Without polymorphic implicit pruning: -// set resolvers in compilation += "scala-integration" at "https://scala-ci.typesafe.com/artifactory/scala-integration/" -// set scalaVersion in compilation := "2.13.0-pre-1c56f0a" -// -// Then: -// cold -psource=induction -jvmArgs -Xss4M -jvmArgs -Xmx2G -// -// Nb. this is *very* slow without the pruning (> 400s). -// With the pruning: 10-20s on reasonable hardware. - -package shapeless { - sealed trait HList extends Product with Serializable - - final case class ::[+H, +T <: HList](head : H, tail : T) extends HList { - def ::[HH](h : HH) : HH :: H :: T = shapeless.::(h, this) - - override def toString = head match { - case _: ::[_, _] => "("+head.toString+") :: "+tail.toString - case _ => head.toString+" :: "+tail.toString - } - } - - sealed trait HNil extends HList { - def ::[H](h : H) = shapeless.::(h, this) - override def toString = "HNil" - } - - case object HNil extends HNil - - //@annotation.inductive - trait Selector[L <: HList, U] { - def apply(l: L): U - } - - object Selector { - def apply[L <: HList, U](implicit selector: Selector[L, U]): Selector[L, U] = selector - - implicit def inHead[H, T <: HList]: Selector[H :: T, H] = - new Selector[H :: T, H] { - def apply(l : H :: T) = l.head - } - - implicit def inTail[H, T <: HList, U] - (implicit st : Selector[T, U]): Selector[H :: T, U] = - new Selector[H :: T, U] { - def apply(l : H :: T) = st(l.tail) - } - } -} - -import shapeless.* - -object Test extends App { - val sel = Selector[L, Boolean] - - type L = - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - /* - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - */ - Boolean :: - HNil -} From 1e8bbcae29a3c4caa3ad096eb6d53e038631ab45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:08:31 +0000 Subject: [PATCH 059/202] Bump VirtusLab/scala-cli-setup from 1.5.3 to 1.5.4 Bumps [VirtusLab/scala-cli-setup](https://github.com/virtuslab/scala-cli-setup) from 1.5.3 to 1.5.4. - [Release notes](https://github.com/virtuslab/scala-cli-setup/releases) - [Commits](https://github.com/virtuslab/scala-cli-setup/compare/v1.5.3...v1.5.4) --- updated-dependencies: - dependency-name: VirtusLab/scala-cli-setup dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/lts-backport.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lts-backport.yaml b/.github/workflows/lts-backport.yaml index 24d2329ed9da..b26075c180e9 100644 --- a/.github/workflows/lts-backport.yaml +++ b/.github/workflows/lts-backport.yaml @@ -15,7 +15,7 @@ jobs: with: fetch-depth: 0 - uses: coursier/cache-action@v6 - - uses: VirtusLab/scala-cli-setup@v1.5.3 + - uses: VirtusLab/scala-cli-setup@v1.5.4 - run: scala-cli ./project/scripts/addToBackportingProject.scala -- ${{ github.sha }} env: GRAPHQL_API_TOKEN: ${{ secrets.GRAPHQL_API_TOKEN }} From d3b9d767a4f74457482e7d2c5ec9a9ef8d55b230 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Fri, 22 Nov 2024 12:38:05 +0100 Subject: [PATCH 060/202] Comment out 35 more lines in inductive-implicits To avoid reaching the stack limit. --- tests/bench/inductive-implicits.scala | 76 +++++++++++++-------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/tests/bench/inductive-implicits.scala b/tests/bench/inductive-implicits.scala index 58714214ce33..09f147caef3e 100644 --- a/tests/bench/inductive-implicits.scala +++ b/tests/bench/inductive-implicits.scala @@ -174,49 +174,49 @@ object Test extends App { Int :: Int :: // - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: -// - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: -// - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: -// - Int :: - Int :: - Int :: - Int :: - Int :: // Int :: // Int :: // Int :: // Int :: // Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// // +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// // +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// // +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: // // // Int :: // Int :: From e89a66cefb21dd83245748859c3869ab09c06f94 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 25 Nov 2024 16:38:36 +0100 Subject: [PATCH 061/202] Make context bounds for poly functions a standard feature (#22019) closes #22017 --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- tests/pos/contextbounds-for-poly-functions.scala | 3 --- tests/run/contextbounds-for-poly-functions.scala | 3 --- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 2e441553689c..ca8ebaf79b09 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3491,7 +3491,7 @@ object Parsers { val hkparams = typeParamClauseOpt(ParamOwner.Hk) val bounds = if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name) - else if in.featureEnabled(Feature.modularity) && paramOwner == ParamOwner.Type then typeAndCtxBounds(name) + else if sourceVersion.isAtLeast(`3.6`) && paramOwner == ParamOwner.Type then typeAndCtxBounds(name) else typeBounds() TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods) } diff --git a/tests/pos/contextbounds-for-poly-functions.scala b/tests/pos/contextbounds-for-poly-functions.scala index 13411a3ad769..49975cf8591d 100644 --- a/tests/pos/contextbounds-for-poly-functions.scala +++ b/tests/pos/contextbounds-for-poly-functions.scala @@ -1,6 +1,3 @@ -import scala.language.experimental.modularity -import scala.language.future - trait Ord[X]: def compare(x: X, y: X): Int type T diff --git a/tests/run/contextbounds-for-poly-functions.scala b/tests/run/contextbounds-for-poly-functions.scala index dcc974fce198..72eed8939fcf 100644 --- a/tests/run/contextbounds-for-poly-functions.scala +++ b/tests/run/contextbounds-for-poly-functions.scala @@ -1,6 +1,3 @@ -import scala.language.experimental.modularity -import scala.language.future - trait Show[X]: def show(x: X): String From 0afabd6e5a506cc833ff01d3000cc8f206ba8255 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Mon, 25 Nov 2024 23:45:03 +0100 Subject: [PATCH 062/202] Update Scala CLI to 1.5.4 (was 1.5.1) & `coursier` to 2.1.18 (was 2.1.13) (#22021) https://github.com/VirtusLab/scala-cli/releases/tag/v1.5.4 https://github.com/coursier/coursier/releases/tag/v2.1.18 cc @WojciechMazur --- project/Build.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index f5593144a631..b2aca3705d83 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -162,9 +162,9 @@ object Build { val mimaPreviousLTSDottyVersion = "3.3.0" /** Version of Scala CLI to download */ - val scalaCliLauncherVersion = "1.5.1" + val scalaCliLauncherVersion = "1.5.4" /** Version of Coursier to download for initializing the local maven repo of Scala command */ - val coursierJarVersion = "2.1.13" + val coursierJarVersion = "2.1.18" object CompatMode { final val BinaryCompatible = 0 From 75ddad2c856aeff1d434bed699573c8070253cdb Mon Sep 17 00:00:00 2001 From: Mathias Date: Tue, 26 Nov 2024 04:54:03 -0500 Subject: [PATCH 063/202] [ISSUE-#19208] If scaladocs on file protocol don't do SPA routing. (#22013) If we detect scaladocs are being served locally (i.e page on the "file" protocol) then we let the link click event go back to normal handling. Otherwise unless a user has allowed pages on the file protocol to access other files https://github.com/chromium/chromium/blob/b6c23ba2056e65081f8a5bcbf73b176baaa42645/content/public/common/content_switches.cc#L15 for example with the allow-file-access-from-files flag in chrome then SPA routing won't work. Fixes #19208. --- scaladoc/resources/dotty_res/scripts/ux.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scaladoc/resources/dotty_res/scripts/ux.js b/scaladoc/resources/dotty_res/scripts/ux.js index 97f9bf14939d..29a610f62b0c 100644 --- a/scaladoc/resources/dotty_res/scripts/ux.js +++ b/scaladoc/resources/dotty_res/scripts/ux.js @@ -167,6 +167,11 @@ function attachAllListeners() { if (url.origin !== window.location.origin) { return; } + // ISSUE-19208, treat as normal link when lacking HTTP server, + // otherwise GET request blocked by CORS protections. + if (window.location.protocol.startsWith("file")) { + return; + } if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) { return; } From d4968e7805b9280df5a7073a2b9d42b161e8325e Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 26 Nov 2024 12:54:45 +0100 Subject: [PATCH 064/202] Fix .toTuple insertion It previously did not follow aliases or upper bounds when deciding whether something was a named tuple. --- .../src/dotty/tools/dotc/core/TypeUtils.scala | 9 +++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/pos/named-tuple-downcast.scala | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/pos/named-tuple-downcast.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index f343d7227bf8..0a219fa6ddfd 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -145,6 +145,15 @@ class TypeUtils: case defn.NamedTuple(_, _) => true case _ => false + def derivesFromNamedTuple(using Context): Boolean = self match + case defn.NamedTuple(_, _) => true + case tp: MatchType => + tp.bound.derivesFromNamedTuple || tp.reduced.derivesFromNamedTuple + case tp: TypeProxy => tp.superType.derivesFromNamedTuple + case tp: AndType => tp.tp1.derivesFromNamedTuple || tp.tp2.derivesFromNamedTuple + case tp: OrType => tp.tp1.derivesFromNamedTuple && tp.tp2.derivesFromNamedTuple + case _ => false + /** Drop all named elements in tuple type */ def stripNamedTuple(using Context): Type = self.normalized.dealias match case defn.NamedTuple(_, vals) => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 13f7b4eb1726..1816d81ebdad 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4641,7 +4641,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _: SelectionProto => tree // adaptations for selections are handled in typedSelect case _ if ctx.mode.is(Mode.ImplicitsEnabled) && tree.tpe.isValueType => - if tree.tpe.widen.isNamedTupleType && pt.derivesFrom(defn.TupleClass) then + if tree.tpe.derivesFromNamedTuple && pt.derivesFrom(defn.TupleClass) then readapt(typed(untpd.Select(untpd.TypedSplice(tree), nme.toTuple))) else if pt.isRef(defn.AnyValClass, skipRefined = false) || pt.isRef(defn.ObjectClass, skipRefined = false) diff --git a/tests/pos/named-tuple-downcast.scala b/tests/pos/named-tuple-downcast.scala new file mode 100644 index 000000000000..239089b60c3d --- /dev/null +++ b/tests/pos/named-tuple-downcast.scala @@ -0,0 +1,20 @@ +type Person = (name: String, age: Int) + +val Bob: Person = (name = "Bob", age = 33) + +type SI = (String, Int) + +def id[X](x: X): X = x +val x: (String, Int) = Bob +val y: SI = id(Bob) +val and: Person & String = ??? +val _: SI = and +val or: Person | (name: "Bob", age: 33) = ??? +val _: SI = or + +class C[P <: Person](p: P): + val x: (String, Int) = p + + + + From d7aceee85a634ba5a867f4af686bf6aa800b6120 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 26 Nov 2024 14:36:46 +0100 Subject: [PATCH 065/202] Tweak other occurrences of `isNamedTuple` --- compiler/src/dotty/tools/dotc/interactive/Completion.scala | 2 +- compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 7a0a19552f48..ff5716b227ca 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -543,7 +543,7 @@ object Completion: .groupByName val qualTpe = qual.typeOpt - if qualTpe.isNamedTupleType then + if qualTpe.derivesFromNamedTuple then namedTupleCompletionsFromType(qualTpe) else if qualTpe.derivesFrom(defn.SelectableClass) then val pre = if !TypeOps.isLegalPrefix(qualTpe) then Types.SkolemType(qualTpe) else qualTpe diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 9750c41b7252..ee608a4297bf 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -107,7 +107,7 @@ object PatternMatcher { // TODO: Drop Case once we use everywhere else `isPatmatGenerated`. private def dropNamedTuple(tree: Tree): Tree = - val tpe = tree.tpe.widen + val tpe = tree.tpe.widenDealias if tpe.isNamedTupleType then tree.cast(tpe.stripNamedTuple) else tree /** The plan `let x = rhs in body(x)` where `x` is a fresh variable */ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 0727c83d8469..228206d8fb1e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -876,7 +876,7 @@ trait Implicits: || inferView(dummyTreeOfType(from), to) (using ctx.fresh.addMode(Mode.ImplicitExploration).setExploreTyperState()).isSuccess // TODO: investigate why we can't TyperState#test here - || from.widen.isNamedTupleType && to.derivesFrom(defn.TupleClass) + || from.widen.derivesFromNamedTuple && to.derivesFrom(defn.TupleClass) && from.widen.stripNamedTuple <:< to ) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1816d81ebdad..96d57c644142 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2706,7 +2706,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer body1.isInstanceOf[RefTree] && !isWildcardArg(body1) || body1.isInstanceOf[Literal] val symTp = - if isStableIdentifierOrLiteral || pt.isNamedTupleType then pt + if isStableIdentifierOrLiteral || pt.dealias.isNamedTupleType then pt // need to combine tuple element types with expected named type else if isWildcardStarArg(body1) || pt == defn.ImplicitScrutineeTypeRef From 49f287ac081fb3d10a18fdcc56e2bfb75c5054d4 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Tue, 26 Nov 2024 20:26:20 +0000 Subject: [PATCH 066/202] Do not lift annotation arguments --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 4 +-- .../tools/dotc/printing/RefinedPrinter.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 7 +++++- .../dependent-annot-default-args.check | 25 +++++++++++++++++++ .../dependent-annot-default-args.scala | 5 ++++ 5 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 tests/printing/dependent-annot-default-args.check create mode 100644 tests/printing/dependent-annot-default-args.scala diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 09c855847fac..e0fe17755257 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -144,7 +144,7 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => def allTermArguments(tree: Tree): List[Tree] = unsplice(tree) match { case Apply(fn, args) => allTermArguments(fn) ::: args case TypeApply(fn, args) => allTermArguments(fn) - case Block(_, expr) => allTermArguments(expr) + case Block(Nil, expr) => allTermArguments(expr) case _ => Nil } @@ -152,7 +152,7 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => def allArguments(tree: Tree): List[Tree] = unsplice(tree) match { case Apply(fn, args) => allArguments(fn) ::: args case TypeApply(fn, args) => allArguments(fn) ::: args - case Block(_, expr) => allArguments(expr) + case Block(Nil, expr) => allArguments(expr) case _ => Nil } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index a399b8173b5d..b7f2eef8c8f9 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -1124,7 +1124,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def recur(t: untpd.Tree): Text = t match case Apply(fn, Nil) => recur(fn) case Apply(fn, args) => - val explicitArgs = args.filterNot(_.symbol.name.is(DefaultGetterName)) + val explicitArgs = args.filterNot(untpd.stripNamedArg(_).symbol.name.is(DefaultGetterName)) recur(fn) ~ "(" ~ toTextGlobal(explicitArgs, ", ") ~ ")" case TypeApply(fn, args) => recur(fn) ~ "[" ~ toTextGlobal(args, ", ") ~ "]" case Select(qual, nme.CONSTRUCTOR) => recur(qual) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 5fb91694b8a6..41e48f7595dc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -513,7 +513,7 @@ trait Applications extends Compatibility { case tp => args.size } - !isJavaAnnotConstr(methRef.symbol) && + !isAnnotConstr(methRef.symbol) && args.size < requiredArgNum(funType) } @@ -662,6 +662,11 @@ trait Applications extends Compatibility { def isJavaAnnotConstr(sym: Symbol): Boolean = sym.is(JavaDefined) && sym.isConstructor && sym.owner.is(JavaAnnotation) + + /** Is `sym` a constructor of an annotation? */ + def isAnnotConstr(sym: Symbol): Boolean = + sym.isConstructor && sym.owner.isAnnotation + /** Match re-ordered arguments against formal parameters * @param n The position of the first parameter in formals in `methType`. */ diff --git a/tests/printing/dependent-annot-default-args.check b/tests/printing/dependent-annot-default-args.check new file mode 100644 index 000000000000..44c1fe31e2d1 --- /dev/null +++ b/tests/printing/dependent-annot-default-args.check @@ -0,0 +1,25 @@ +[[syntax trees at end of typer]] // tests/printing/dependent-annot-default-args.scala +package { + class annot(x: Any, y: Any) extends annotation.Annotation() { + private[this] val x: Any + private[this] val y: Any + } + final lazy module val annot: annot = new annot() + final module class annot() extends AnyRef() { this: annot.type => + def $lessinit$greater$default$2: Any @uncheckedVariance = 42 + } + final lazy module val dependent-annot-default-args$package: + dependent-annot-default-args$package = + new dependent-annot-default-args$package() + final module class dependent-annot-default-args$package() extends Object() { + this: dependent-annot-default-args$package.type => + def f(x: Int): Int @annot(x) = x + def test: Unit = + { + val y: Int = ??? + val z: Int @annot(y) = f(y) + () + } + } +} + diff --git a/tests/printing/dependent-annot-default-args.scala b/tests/printing/dependent-annot-default-args.scala new file mode 100644 index 000000000000..7ddce711fedc --- /dev/null +++ b/tests/printing/dependent-annot-default-args.scala @@ -0,0 +1,5 @@ +class annot(x: Any, y: Any = 42) extends annotation.Annotation +def f(x: Int): Int @annot(x) = x +def test = + val y: Int = ??? + val z = f(y) From a4ea8bfb92142b86cd6a06071464ea8c66f7f2a7 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 27 Nov 2024 18:49:45 +0100 Subject: [PATCH 067/202] Make NamedTupls an experimental feature again --- .../src/dotty/tools/dotc/config/Feature.scala | 2 + .../dotty/tools/dotc/parsing/Parsers.scala | 4 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- .../named-tuples.md | 4 +- docs/sidebar.yml | 2 +- library/src/scala/NamedTuple.scala | 3 + .../runtime/stdLibPatches/language.scala | 1 - .../pc/tests/completion/CompletionSuite.scala | 6 +- tests/neg/i20517.check | 14 +-- tests/neg/i20517.scala | 1 + tests/neg/infix-named-args.check | 20 ++-- tests/neg/infix-named-args.scala | 2 + tests/neg/named-tuple-selectable.scala | 2 + tests/neg/named-tuples-2.check | 8 +- tests/neg/named-tuples-2.scala | 1 + tests/neg/named-tuples-3.check | 4 +- tests/neg/named-tuples-3.scala | 2 + tests/neg/named-tuples.check | 92 +++++++++---------- tests/neg/named-tuples.scala | 3 +- tests/new/test.scala | 2 + tests/pos/fieldsOf.scala | 2 + tests/pos/i20377.scala | 1 + tests/pos/i21300.scala | 6 +- tests/pos/i21413.scala | 2 + tests/pos/named-tuple-combinators.scala | 1 + tests/pos/named-tuple-selectable.scala | 1 + tests/pos/named-tuple-selections.scala | 1 + tests/pos/named-tuple-unstable.scala | 1 + tests/pos/named-tuple-widen.scala | 1 + tests/pos/named-tuples-ops-mirror.scala | 1 + tests/pos/named-tuples1.scala | 1 + tests/pos/namedtuple-src-incompat.scala | 1 + tests/pos/tuple-ops.scala | 1 + tests/rewrites/infix-named-args.check | 4 +- tests/rewrites/infix-named-args.scala | 2 + .../stdlibExperimentalDefinitions.scala | 6 ++ tests/run/named-patmatch.scala | 1 + tests/run/named-patterns.scala | 1 + tests/run/named-tuple-ops.scala | 1 + tests/run/named-tuples-xxl.scala | 1 + tests/run/named-tuples.scala | 1 + tests/run/tyql.scala | 1 + tests/warn/21681.check | 4 +- tests/warn/21681.scala | 2 + tests/warn/21681b.check | 4 +- tests/warn/21681b.scala | 2 + tests/warn/21681c.check | 4 +- tests/warn/21681c.scala | 2 + tests/warn/21770.check | 4 +- tests/warn/21770.scala | 4 +- tests/warn/infix-named-args-migration.scala | 2 + 51 files changed, 150 insertions(+), 91 deletions(-) rename docs/_docs/reference/{other-new-features => experimental}/named-tuples.md (98%) diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index ad20bab46c1e..8b9a64924ace 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -34,6 +34,7 @@ object Feature: val pureFunctions = experimental("pureFunctions") val captureChecking = experimental("captureChecking") val into = experimental("into") + val namedTuples = experimental("namedTuples") val modularity = experimental("modularity") val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors") val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions") @@ -65,6 +66,7 @@ object Feature: (pureFunctions, "Enable pure functions for capture checking"), (captureChecking, "Enable experimental capture checking"), (into, "Allow into modifier on parameter types"), + (namedTuples, "Allow named tuples"), (modularity, "Enable experimental modularity features"), (betterMatchTypeExtractors, "Enable better match type extractors"), (betterFors, "Enable improvements in `for` comprehensions") diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index ca8ebaf79b09..3ea4131d5d78 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -667,7 +667,7 @@ object Parsers { else leading :: Nil def maybeNamed(op: () => Tree): () => Tree = () => - if isIdent && in.lookahead.token == EQUALS && sourceVersion.isAtLeast(`3.6`) then + if isIdent && in.lookahead.token == EQUALS && in.featureEnabled(Feature.namedTuples) then atSpan(in.offset): val name = ident() in.nextToken() @@ -2172,7 +2172,7 @@ object Parsers { if namedOK && isIdent && in.lookahead.token == EQUALS then commaSeparated(() => namedArgType()) - else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.isAtLeast(`3.6`) then + else if tupleOK && isIdent && in.lookahead.isColon && in.featureEnabled(Feature.namedTuples) then commaSeparated(() => namedElem()) else commaSeparated(() => argType()) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 13f7b4eb1726..57a027653241 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -795,7 +795,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def tryNamedTupleSelection() = val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes val nameIdx = namedTupleElems.indexWhere(_._1 == selName) - if nameIdx >= 0 && sourceVersion.isAtLeast(`3.6`) then + if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then typed( untpd.Apply( untpd.Select(untpd.TypedSplice(qual), nme.apply), diff --git a/docs/_docs/reference/other-new-features/named-tuples.md b/docs/_docs/reference/experimental/named-tuples.md similarity index 98% rename from docs/_docs/reference/other-new-features/named-tuples.md rename to docs/_docs/reference/experimental/named-tuples.md index 5483c5cc255b..27d74259725d 100644 --- a/docs/_docs/reference/other-new-features/named-tuples.md +++ b/docs/_docs/reference/experimental/named-tuples.md @@ -1,10 +1,10 @@ --- layout: doc-page title: "Named Tuples" -nightlyOf: https://docs.scala-lang.org/scala3/reference/other-new-features/named-tuples.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/named-tuples.html --- -Starting in Scala 3.6, the elements of a tuple can be named. Example: +The elements of a tuple can now be named. Example: ```scala type Person = (name: String, age: Int) val Bob: Person = (name = "Bob", age = 33) diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 74aee3dfc668..a306d8bdf274 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -72,7 +72,6 @@ subsection: - page: reference/other-new-features/export.md - page: reference/other-new-features/opaques.md - page: reference/other-new-features/opaques-details.md - - page: reference/other-new-features/named-tuples.md - page: reference/other-new-features/open-classes.md - page: reference/other-new-features/parameter-untupling.md - page: reference/other-new-features/parameter-untupling-spec.md @@ -159,6 +158,7 @@ subsection: - page: reference/experimental/cc.md - page: reference/experimental/purefuns.md - page: reference/experimental/tupled-function.md + - page: reference/experimental/named-tuples.md - page: reference/experimental/modularity.md - page: reference/experimental/typeclasses.md - page: reference/experimental/runtimeChecked.md diff --git a/library/src/scala/NamedTuple.scala b/library/src/scala/NamedTuple.scala index d105cf042f37..6da7f940dc47 100644 --- a/library/src/scala/NamedTuple.scala +++ b/library/src/scala/NamedTuple.scala @@ -1,6 +1,8 @@ package scala +import annotation.experimental import compiletime.ops.boolean.* +@experimental object NamedTuple: /** The type to which named tuples get mapped to. For instance, @@ -131,6 +133,7 @@ object NamedTuple: end NamedTuple /** Separate from NamedTuple object so that we can match on the opaque type NamedTuple. */ +@experimental object NamedTupleDecomposition: import NamedTuple.* extension [N <: Tuple, V <: Tuple](x: NamedTuple[N, V]) diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index b8d990cf56f5..547710d55293 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -97,7 +97,6 @@ object language: * @see [[https://dotty.epfl.ch/docs/reference/experimental/named-tuples]] */ @compileTimeOnly("`namedTuples` can only be used at compile time in import statements") - @deprecated("The experimental.namedTuples language import is no longer needed since the feature is now standard", since = "3.6") object namedTuples /** Experimental support for new features for better modularity, including 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 7be2ea6181ef..ab28baea994b 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -1988,7 +1988,8 @@ class CompletionSuite extends BaseCompletionSuite: @Test def `namedTuple completions` = check( - """|import scala.NamedTuple.* + """|import scala.language.experimental.namedTuples + |import scala.NamedTuple.* | |val person = (name = "Jamie", city = "Lausanne") | @@ -1999,7 +2000,8 @@ class CompletionSuite extends BaseCompletionSuite: @Test def `Selectable with namedTuple Fields member` = check( - """|import scala.NamedTuple.* + """|import scala.language.experimental.namedTuples + |import scala.NamedTuple.* | |class NamedTupleSelectable extends Selectable { | type Fields <: AnyNamedTuple diff --git a/tests/neg/i20517.check b/tests/neg/i20517.check index 119c34025ee0..55aeff46572b 100644 --- a/tests/neg/i20517.check +++ b/tests/neg/i20517.check @@ -1,7 +1,7 @@ --- [E007] Type Mismatch Error: tests/neg/i20517.scala:9:43 ------------------------------------------------------------- -9 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error - | ^^^^^^^^^^^ - | Found: (elem : String) - | Required: NamedTuple.From[(foo : Foo[Any])] - | - | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/i20517.scala:10:43 ------------------------------------------------------------ +10 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error + | ^^^^^^^^^^^ + | Found: (elem : String) + | Required: NamedTuple.From[(foo : Foo[Any])] + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i20517.scala b/tests/neg/i20517.scala index 342a7d86ca7e..11c4432434dd 100644 --- a/tests/neg/i20517.scala +++ b/tests/neg/i20517.scala @@ -1,3 +1,4 @@ +import scala.language.experimental.namedTuples import NamedTuple.From case class Foo[+T](elem: T) diff --git a/tests/neg/infix-named-args.check b/tests/neg/infix-named-args.check index 0cfbbaef73a3..291c7616a57c 100644 --- a/tests/neg/infix-named-args.check +++ b/tests/neg/infix-named-args.check @@ -1,5 +1,5 @@ --- [E134] Type Error: tests/neg/infix-named-args.scala:2:13 ------------------------------------------------------------ -2 | def f = 42 + (x = 1) // error // werror +-- [E134] Type Error: tests/neg/infix-named-args.scala:4:13 ------------------------------------------------------------ +4 | def f = 42 + (x = 1) // error // werror | ^^^^ | None of the overloaded alternatives of method + in class Int with types | (x: Double): Double @@ -11,29 +11,29 @@ | (x: Byte): Int | (x: String): String | match arguments ((x : Int)) (a named tuple) --- [E204] Syntax Warning: tests/neg/infix-named-args.scala:2:15 -------------------------------------------------------- -2 | def f = 42 + (x = 1) // error // werror +-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:4:15 -------------------------------------------------------- +4 | def f = 42 + (x = 1) // error // werror | ^^^^^^^ |Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list. |This can be rewritten automatically under -rewrite -source 3.6-migration. | | longer explanation available when compiling with `-explain` --- [E204] Syntax Warning: tests/neg/infix-named-args.scala:5:26 -------------------------------------------------------- -5 | def g = new C() `multi` (x = 42, y = 27) // werror +-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:7:26 -------------------------------------------------------- +7 | def g = new C() `multi` (x = 42, y = 27) // werror | ^^^^^^^^^^^^^^^^ |Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list. |This can be rewritten automatically under -rewrite -source 3.6-migration. | | longer explanation available when compiling with `-explain` --- [E204] Syntax Warning: tests/neg/infix-named-args.scala:6:21 -------------------------------------------------------- -6 | def h = new C() ** (x = 42, y = 27) // werror +-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:8:21 -------------------------------------------------------- +8 | def h = new C() ** (x = 42, y = 27) // werror | ^^^^^^^^^^^^^^^^ |Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list. |This can be rewritten automatically under -rewrite -source 3.6-migration. | | longer explanation available when compiling with `-explain` --- [E204] Syntax Warning: tests/neg/infix-named-args.scala:13:18 ------------------------------------------------------- -13 | def f = this ** (x = 2) // werror +-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:15:18 ------------------------------------------------------- +15 | def f = this ** (x = 2) // werror | ^^^^^^^ |Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list. |This can be rewritten automatically under -rewrite -source 3.6-migration. diff --git a/tests/neg/infix-named-args.scala b/tests/neg/infix-named-args.scala index d8616899540c..b0ef555cf965 100644 --- a/tests/neg/infix-named-args.scala +++ b/tests/neg/infix-named-args.scala @@ -1,3 +1,5 @@ +import scala.language.experimental.namedTuples + class C: def f = 42 + (x = 1) // error // werror def multi(x: Int, y: Int): Int = x + y diff --git a/tests/neg/named-tuple-selectable.scala b/tests/neg/named-tuple-selectable.scala index c81eba1237ff..5cf7e68654ef 100644 --- a/tests/neg/named-tuple-selectable.scala +++ b/tests/neg/named-tuple-selectable.scala @@ -1,3 +1,5 @@ +import scala.language.experimental.namedTuples + class FromFields extends Selectable: type Fields = (i: Int) def selectDynamic(key: String) = diff --git a/tests/neg/named-tuples-2.check b/tests/neg/named-tuples-2.check index daa1c0d69069..0a52d5f3989b 100644 --- a/tests/neg/named-tuples-2.check +++ b/tests/neg/named-tuples-2.check @@ -1,8 +1,8 @@ --- Error: tests/neg/named-tuples-2.scala:4:9 --------------------------------------------------------------------------- -4 | case (name, age) => () // error +-- Error: tests/neg/named-tuples-2.scala:5:9 --------------------------------------------------------------------------- +5 | case (name, age) => () // error | ^ | this case is unreachable since type (String, Int, Boolean) is not a subclass of class Tuple2 --- Error: tests/neg/named-tuples-2.scala:5:9 --------------------------------------------------------------------------- -5 | case (n, a, m, x) => () // error +-- Error: tests/neg/named-tuples-2.scala:6:9 --------------------------------------------------------------------------- +6 | case (n, a, m, x) => () // error | ^ | this case is unreachable since type (String, Int, Boolean) is not a subclass of class Tuple4 diff --git a/tests/neg/named-tuples-2.scala b/tests/neg/named-tuples-2.scala index b3917d9ad57c..0507891e0549 100644 --- a/tests/neg/named-tuples-2.scala +++ b/tests/neg/named-tuples-2.scala @@ -1,3 +1,4 @@ +import language.experimental.namedTuples def Test = val person = (name = "Bob", age = 33, married = true) person match diff --git a/tests/neg/named-tuples-3.check b/tests/neg/named-tuples-3.check index 2809836b4803..2091c36191c0 100644 --- a/tests/neg/named-tuples-3.check +++ b/tests/neg/named-tuples-3.check @@ -1,5 +1,5 @@ --- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:5:16 ----------------------------------------------------- -5 |val p: Person = f // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:7:16 ----------------------------------------------------- +7 |val p: Person = f // error | ^ | Found: NamedTuple.NamedTuple[(Int, Any), (Int, String)] | Required: Person diff --git a/tests/neg/named-tuples-3.scala b/tests/neg/named-tuples-3.scala index 21e6ed9b3741..0f1215338b0a 100644 --- a/tests/neg/named-tuples-3.scala +++ b/tests/neg/named-tuples-3.scala @@ -1,3 +1,5 @@ +import language.experimental.namedTuples + def f: NamedTuple.NamedTuple[(Int, Any), (Int, String)] = ??? type Person = (name: Int, age: String) diff --git a/tests/neg/named-tuples.check b/tests/neg/named-tuples.check index 8ec958b6a75d..db3cc703722f 100644 --- a/tests/neg/named-tuples.check +++ b/tests/neg/named-tuples.check @@ -1,101 +1,101 @@ --- Error: tests/neg/named-tuples.scala:8:19 ---------------------------------------------------------------------------- -8 | val illformed = (_2 = 2) // error +-- Error: tests/neg/named-tuples.scala:9:19 ---------------------------------------------------------------------------- +9 | val illformed = (_2 = 2) // error | ^^^^^^ | _2 cannot be used as the name of a tuple element because it is a regular tuple selector --- Error: tests/neg/named-tuples.scala:9:20 ---------------------------------------------------------------------------- -9 | type Illformed = (_1: Int) // error - | ^^^^^^^ - | _1 cannot be used as the name of a tuple element because it is a regular tuple selector --- Error: tests/neg/named-tuples.scala:10:40 --------------------------------------------------------------------------- -10 | val illformed2 = (name = "", age = 0, name = true) // error +-- Error: tests/neg/named-tuples.scala:10:20 --------------------------------------------------------------------------- +10 | type Illformed = (_1: Int) // error + | ^^^^^^^ + | _1 cannot be used as the name of a tuple element because it is a regular tuple selector +-- Error: tests/neg/named-tuples.scala:11:40 --------------------------------------------------------------------------- +11 | val illformed2 = (name = "", age = 0, name = true) // error | ^^^^^^^^^^^ | Duplicate tuple element name --- Error: tests/neg/named-tuples.scala:11:45 --------------------------------------------------------------------------- -11 | type Illformed2 = (name: String, age: Int, name: Boolean) // error +-- Error: tests/neg/named-tuples.scala:12:45 --------------------------------------------------------------------------- +12 | type Illformed2 = (name: String, age: Int, name: Boolean) // error | ^^^^^^^^^^^^^ | Duplicate tuple element name --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:19:20 ------------------------------------------------------ -19 | val _: NameOnly = person // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:20:20 ------------------------------------------------------ +20 | val _: NameOnly = person // error | ^^^^^^ | Found: (Test.person : (name : String, age : Int)) | Required: Test.NameOnly | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:20:18 ------------------------------------------------------ -20 | val _: Person = nameOnly // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:21:18 ------------------------------------------------------ +21 | val _: Person = nameOnly // error | ^^^^^^^^ | Found: (Test.nameOnly : (name : String)) | Required: Test.Person | | longer explanation available when compiling with `-explain` --- [E172] Type Error: tests/neg/named-tuples.scala:21:41 --------------------------------------------------------------- -21 | val _: Person = (name = "") ++ nameOnly // error +-- [E172] Type Error: tests/neg/named-tuples.scala:22:41 --------------------------------------------------------------- +22 | val _: Person = (name = "") ++ nameOnly // error | ^ | Cannot prove that Tuple.Disjoint[Tuple1[("name" : String)], Tuple1[("name" : String)]] =:= (true : Boolean). --- [E008] Not Found Error: tests/neg/named-tuples.scala:22:9 ----------------------------------------------------------- -22 | person._1 // error +-- [E008] Not Found Error: tests/neg/named-tuples.scala:23:9 ----------------------------------------------------------- +23 | person._1 // error | ^^^^^^^^^ | value _1 is not a member of (name : String, age : Int) --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:24:36 ------------------------------------------------------ -24 | val _: (age: Int, name: String) = person // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:25:36 ------------------------------------------------------ +25 | val _: (age: Int, name: String) = person // error | ^^^^^^ | Found: (Test.person : (name : String, age : Int)) | Required: (age : Int, name : String) | | longer explanation available when compiling with `-explain` --- Error: tests/neg/named-tuples.scala:26:17 --------------------------------------------------------------------------- -26 | val (name = x, agee = y) = person // error +-- Error: tests/neg/named-tuples.scala:27:17 --------------------------------------------------------------------------- +27 | val (name = x, agee = y) = person // error | ^^^^^^^^ | No element named `agee` is defined in selector type (name : String, age : Int) --- Error: tests/neg/named-tuples.scala:29:10 --------------------------------------------------------------------------- -29 | case (name = n, age = a) => () // error // error +-- Error: tests/neg/named-tuples.scala:30:10 --------------------------------------------------------------------------- +30 | case (name = n, age = a) => () // error // error | ^^^^^^^^ | No element named `name` is defined in selector type (String, Int) --- Error: tests/neg/named-tuples.scala:29:20 --------------------------------------------------------------------------- -29 | case (name = n, age = a) => () // error // error +-- Error: tests/neg/named-tuples.scala:30:20 --------------------------------------------------------------------------- +30 | case (name = n, age = a) => () // error // error | ^^^^^^^ | No element named `age` is defined in selector type (String, Int) --- [E172] Type Error: tests/neg/named-tuples.scala:31:27 --------------------------------------------------------------- -31 | val pp = person ++ (1, 2) // error +-- [E172] Type Error: tests/neg/named-tuples.scala:32:27 --------------------------------------------------------------- +32 | val pp = person ++ (1, 2) // error | ^ | Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), Tuple] =:= (true : Boolean). --- [E172] Type Error: tests/neg/named-tuples.scala:34:18 --------------------------------------------------------------- -34 | person ++ (1, 2) match // error +-- [E172] Type Error: tests/neg/named-tuples.scala:35:18 --------------------------------------------------------------- +35 | person ++ (1, 2) match // error | ^ | Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), Tuple] =:= (true : Boolean). --- Error: tests/neg/named-tuples.scala:37:17 --------------------------------------------------------------------------- -37 | val bad = ("", age = 10) // error +-- Error: tests/neg/named-tuples.scala:38:17 --------------------------------------------------------------------------- +38 | val bad = ("", age = 10) // error | ^^^^^^^^ | Illegal combination of named and unnamed tuple elements --- Error: tests/neg/named-tuples.scala:40:20 --------------------------------------------------------------------------- -40 | case (name = n, age) => () // error +-- Error: tests/neg/named-tuples.scala:41:20 --------------------------------------------------------------------------- +41 | case (name = n, age) => () // error | ^^^ | Illegal combination of named and unnamed tuple elements --- Error: tests/neg/named-tuples.scala:41:16 --------------------------------------------------------------------------- -41 | case (name, age = a) => () // error +-- Error: tests/neg/named-tuples.scala:42:16 --------------------------------------------------------------------------- +42 | case (name, age = a) => () // error | ^^^^^^^ | Illegal combination of named and unnamed tuple elements --- Error: tests/neg/named-tuples.scala:44:10 --------------------------------------------------------------------------- -44 | case (age = x) => // error +-- Error: tests/neg/named-tuples.scala:45:10 --------------------------------------------------------------------------- +45 | case (age = x) => // error | ^^^^^^^ | No element named `age` is defined in selector type Tuple --- [E172] Type Error: tests/neg/named-tuples.scala:46:27 --------------------------------------------------------------- -46 | val p2 = person ++ person // error +-- [E172] Type Error: tests/neg/named-tuples.scala:47:27 --------------------------------------------------------------- +47 | val p2 = person ++ person // error | ^ |Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), (("name" : String), ("age" : String))] =:= (true : Boolean). --- [E172] Type Error: tests/neg/named-tuples.scala:47:43 --------------------------------------------------------------- -47 | val p3 = person ++ (first = 11, age = 33) // error +-- [E172] Type Error: tests/neg/named-tuples.scala:48:43 --------------------------------------------------------------- +48 | val p3 = person ++ (first = 11, age = 33) // error | ^ |Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), (("first" : String), ("age" : String))] =:= (true : Boolean). --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:49:22 ------------------------------------------------------ -49 | val p5 = person.zip((first = 11, age = 33)) // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:50:22 ------------------------------------------------------ +50 | val p5 = person.zip((first = 11, age = 33)) // error | ^^^^^^^^^^^^^^^^^^^^^^ | Found: (first : Int, age : Int) | Required: NamedTuple.NamedTuple[(("name" : String), ("age" : String)), Tuple] | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:60:32 ------------------------------------------------------ -60 | val typo: (name: ?, age: ?) = (name = "he", ag = 1) // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:61:32 ------------------------------------------------------ +61 | val typo: (name: ?, age: ?) = (name = "he", ag = 1) // error | ^^^^^^^^^^^^^^^^^^^^^ | Found: (name : String, ag : Int) | Required: (name : ?, age : ?) diff --git a/tests/neg/named-tuples.scala b/tests/neg/named-tuples.scala index daae6e26bac2..8f78f7915206 100644 --- a/tests/neg/named-tuples.scala +++ b/tests/neg/named-tuples.scala @@ -1,6 +1,7 @@ import annotation.experimental +import language.experimental.namedTuples -object Test: +@experimental object Test: type Person = (name: String, age: Int) val person = (name = "Bob", age = 33): (name: String, age: Int) diff --git a/tests/new/test.scala b/tests/new/test.scala index dc1891f3525c..18644422ab06 100644 --- a/tests/new/test.scala +++ b/tests/new/test.scala @@ -1,3 +1,5 @@ +import language.experimental.namedTuples + type Person = (name: String, age: Int) trait A: diff --git a/tests/pos/fieldsOf.scala b/tests/pos/fieldsOf.scala index 08f20a1f7e8e..2594dae2cbf7 100644 --- a/tests/pos/fieldsOf.scala +++ b/tests/pos/fieldsOf.scala @@ -1,3 +1,5 @@ +import language.experimental.namedTuples + case class Person(name: String, age: Int) type PF = NamedTuple.From[Person] diff --git a/tests/pos/i20377.scala b/tests/pos/i20377.scala index a555e01867ab..661fa7adfca9 100644 --- a/tests/pos/i20377.scala +++ b/tests/pos/i20377.scala @@ -1,3 +1,4 @@ +import language.experimental.namedTuples import NamedTuple.{NamedTuple, AnyNamedTuple} // Repros for bugs or questions diff --git a/tests/pos/i21300.scala b/tests/pos/i21300.scala index e7c7965b0e9a..22859482ef98 100644 --- a/tests/pos/i21300.scala +++ b/tests/pos/i21300.scala @@ -1,15 +1,17 @@ +import scala.language.experimental.namedTuples + class Test[S <: String & Singleton](name: S): type NT = NamedTuple.NamedTuple[(S, "foo"), (Int, Long)] def nt: NT = ??? type Name = S - + type NT2 = NamedTuple.NamedTuple[(Name, "foo"), (Int, Long)] def nt2: NT2 = ??? def test = val foo = new Test("bar") - + foo.nt.bar foo.nt2.bar diff --git a/tests/pos/i21413.scala b/tests/pos/i21413.scala index d2dc52e34630..72b5c6d59d8d 100644 --- a/tests/pos/i21413.scala +++ b/tests/pos/i21413.scala @@ -1,2 +1,4 @@ +import scala.language.experimental.namedTuples + val x = (aaa = 1).aaa //val y = x.aaa \ No newline at end of file diff --git a/tests/pos/named-tuple-combinators.scala b/tests/pos/named-tuple-combinators.scala index c027ba688d02..a5134b2e7d26 100644 --- a/tests/pos/named-tuple-combinators.scala +++ b/tests/pos/named-tuple-combinators.scala @@ -1,3 +1,4 @@ +import scala.language.experimental.namedTuples object Test: // original code from issue https://github.com/scala/scala3/issues/20427 diff --git a/tests/pos/named-tuple-selectable.scala b/tests/pos/named-tuple-selectable.scala index 0e1324f70ae6..be5f0400e58c 100644 --- a/tests/pos/named-tuple-selectable.scala +++ b/tests/pos/named-tuple-selectable.scala @@ -1,3 +1,4 @@ +import scala.language.experimental.namedTuples class FromFields extends Selectable: type Fields = (xs: List[Int], poly: [T] => (x: List[T]) => Option[T]) diff --git a/tests/pos/named-tuple-selections.scala b/tests/pos/named-tuple-selections.scala index 7b73daad2e72..c3569f21b323 100644 --- a/tests/pos/named-tuple-selections.scala +++ b/tests/pos/named-tuple-selections.scala @@ -1,3 +1,4 @@ +import scala.language.experimental.namedTuples object Test1: // original code from issue https://github.com/scala/scala3/issues/20439 diff --git a/tests/pos/named-tuple-unstable.scala b/tests/pos/named-tuple-unstable.scala index d15bdc578a3a..6a6a36732a14 100644 --- a/tests/pos/named-tuple-unstable.scala +++ b/tests/pos/named-tuple-unstable.scala @@ -1,3 +1,4 @@ +import scala.language.experimental.namedTuples import NamedTuple.{AnyNamedTuple, NamedTuple} trait Foo extends Selectable: diff --git a/tests/pos/named-tuple-widen.scala b/tests/pos/named-tuple-widen.scala index cc12a5f09b16..410832e04c17 100644 --- a/tests/pos/named-tuple-widen.scala +++ b/tests/pos/named-tuple-widen.scala @@ -1,3 +1,4 @@ +import language.experimental.namedTuples class A class B diff --git a/tests/pos/named-tuples-ops-mirror.scala b/tests/pos/named-tuples-ops-mirror.scala index b8745cf785d5..f66eb89534fb 100644 --- a/tests/pos/named-tuples-ops-mirror.scala +++ b/tests/pos/named-tuples-ops-mirror.scala @@ -1,3 +1,4 @@ +import language.experimental.namedTuples import NamedTuple.* @FailsWith[HttpError] diff --git a/tests/pos/named-tuples1.scala b/tests/pos/named-tuples1.scala index 532f1df7efd4..58e3fc065e61 100644 --- a/tests/pos/named-tuples1.scala +++ b/tests/pos/named-tuples1.scala @@ -1,4 +1,5 @@ import annotation.experimental +import language.experimental.namedTuples @main def Test = val bob = (name = "Bob", age = 33): (name: String, age: Int) diff --git a/tests/pos/namedtuple-src-incompat.scala b/tests/pos/namedtuple-src-incompat.scala index 76eb5e4aa850..57451a4321b7 100644 --- a/tests/pos/namedtuple-src-incompat.scala +++ b/tests/pos/namedtuple-src-incompat.scala @@ -1,3 +1,4 @@ +import language.experimental.namedTuples var age = 22 val x = (age = 1) val _: (age: Int) = x diff --git a/tests/pos/tuple-ops.scala b/tests/pos/tuple-ops.scala index e89c0e8e51aa..739b1ebeeb02 100644 --- a/tests/pos/tuple-ops.scala +++ b/tests/pos/tuple-ops.scala @@ -1,3 +1,4 @@ +import language.experimental.namedTuples import Tuple.* def test = diff --git a/tests/rewrites/infix-named-args.check b/tests/rewrites/infix-named-args.check index a50593ef18a8..5f59cf272ba1 100644 --- a/tests/rewrites/infix-named-args.check +++ b/tests/rewrites/infix-named-args.check @@ -1,3 +1,5 @@ +import scala.language.experimental.namedTuples + class C: def multi(x: Int, y: Int): Int = x + y def **(x: Int, y: Int): Int = x + y @@ -12,4 +14,4 @@ class D(d: Int): def f = this.**(x = 2) def g = this ** 2 def h = this ** ((x = 2)) - def i = this.**(x = (1 + 1)) \ No newline at end of file + def i = this.**(x = (1 + 1)) diff --git a/tests/rewrites/infix-named-args.scala b/tests/rewrites/infix-named-args.scala index bcdf4a21a9d2..a954776a9104 100644 --- a/tests/rewrites/infix-named-args.scala +++ b/tests/rewrites/infix-named-args.scala @@ -1,3 +1,5 @@ +import scala.language.experimental.namedTuples + class C: def multi(x: Int, y: Int): Int = x + y def **(x: Int, y: Int): Int = x + y diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index 7a8dcb9bd2df..65e3a730ee7e 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -77,6 +77,12 @@ val experimentalDefinitionInLibrary = Set( // New feature: fromNullable for explicit nulls "scala.Predef$.fromNullable", + // New feature: named tuples + "scala.NamedTuple", + "scala.NamedTuple$", + "scala.NamedTupleDecomposition", + "scala.NamedTupleDecomposition$", + // New feature: modularity "scala.Precise", "scala.annotation.internal.WitnessNames", diff --git a/tests/run/named-patmatch.scala b/tests/run/named-patmatch.scala index 6fe1934f008e..e62497e4aa8f 100644 --- a/tests/run/named-patmatch.scala +++ b/tests/run/named-patmatch.scala @@ -1,4 +1,5 @@ import annotation.experimental +import language.experimental.namedTuples @main def Test = locally: diff --git a/tests/run/named-patterns.scala b/tests/run/named-patterns.scala index e92bbf751c22..7c24dc8d683a 100644 --- a/tests/run/named-patterns.scala +++ b/tests/run/named-patterns.scala @@ -1,3 +1,4 @@ +import language.experimental.namedTuples object Test1: class Person(val name: String, val age: Int) diff --git a/tests/run/named-tuple-ops.scala b/tests/run/named-tuple-ops.scala index 8c6db6f2fa1c..076ab5028c6c 100644 --- a/tests/run/named-tuple-ops.scala +++ b/tests/run/named-tuple-ops.scala @@ -1,4 +1,5 @@ //> using options -source future +import language.experimental.namedTuples import scala.compiletime.asMatchable type City = (name: String, zip: Int, pop: Int) diff --git a/tests/run/named-tuples-xxl.scala b/tests/run/named-tuples-xxl.scala index 8c831fb1d223..3a0a1e5e1294 100644 --- a/tests/run/named-tuples-xxl.scala +++ b/tests/run/named-tuples-xxl.scala @@ -1,3 +1,4 @@ +import language.experimental.namedTuples import NamedTuple.toTuple type Person = ( diff --git a/tests/run/named-tuples.scala b/tests/run/named-tuples.scala index c99393a403b3..406c6195cf0f 100644 --- a/tests/run/named-tuples.scala +++ b/tests/run/named-tuples.scala @@ -1,3 +1,4 @@ +import language.experimental.namedTuples import NamedTuple.* type Person = (name: String, age: Int) diff --git a/tests/run/tyql.scala b/tests/run/tyql.scala index ee3fd1138265..8fe253b559ac 100644 --- a/tests/run/tyql.scala +++ b/tests/run/tyql.scala @@ -1,3 +1,4 @@ +import language.experimental.namedTuples import NamedTuple.{NamedTuple, AnyNamedTuple} /* This is a demonstrator that shows how to map regular for expressions to diff --git a/tests/warn/21681.check b/tests/warn/21681.check index e86ce4e36134..6ca2c3816b01 100644 --- a/tests/warn/21681.check +++ b/tests/warn/21681.check @@ -1,5 +1,5 @@ --- [E203] Syntax Migration Warning: tests/warn/21681.scala:3:2 --------------------------------------------------------- -3 | (age = 29) // warn +-- [E203] Syntax Migration Warning: tests/warn/21681.scala:5:2 --------------------------------------------------------- +5 | (age = 29) // warn | ^^^^^^^^^^ | Ambiguous syntax: this is interpreted as a named tuple with one element, | not as an assignment. diff --git a/tests/warn/21681.scala b/tests/warn/21681.scala index 76a19c96e1cb..67f45571ecf6 100644 --- a/tests/warn/21681.scala +++ b/tests/warn/21681.scala @@ -1,3 +1,5 @@ +import scala.language.experimental.namedTuples + def main() = var age: Int = 28 (age = 29) // warn diff --git a/tests/warn/21681b.check b/tests/warn/21681b.check index 32760e00ebb6..c77c1438d9a6 100644 --- a/tests/warn/21681b.check +++ b/tests/warn/21681b.check @@ -1,5 +1,5 @@ --- [E203] Syntax Migration Warning: tests/warn/21681b.scala:3:2 -------------------------------------------------------- -3 | (age = 29) // warn +-- [E203] Syntax Migration Warning: tests/warn/21681b.scala:5:2 -------------------------------------------------------- +5 | (age = 29) // warn | ^^^^^^^^^^ | Ambiguous syntax: this is interpreted as a named tuple with one element, | not as an assignment. diff --git a/tests/warn/21681b.scala b/tests/warn/21681b.scala index 710d69b0dd23..44d04fc98aad 100644 --- a/tests/warn/21681b.scala +++ b/tests/warn/21681b.scala @@ -1,3 +1,5 @@ +import scala.language.experimental.namedTuples + object Test: var age: Int = 28 (age = 29) // warn diff --git a/tests/warn/21681c.check b/tests/warn/21681c.check index 11c427f87cfe..00c74ddba5bc 100644 --- a/tests/warn/21681c.check +++ b/tests/warn/21681c.check @@ -1,5 +1,5 @@ --- [E203] Syntax Migration Warning: tests/warn/21681c.scala:5:2 -------------------------------------------------------- -5 | (age = 29) // warn +-- [E203] Syntax Migration Warning: tests/warn/21681c.scala:7:2 -------------------------------------------------------- +7 | (age = 29) // warn | ^^^^^^^^^^ | Ambiguous syntax: this is interpreted as a named tuple with one element, | not as an assignment. diff --git a/tests/warn/21681c.scala b/tests/warn/21681c.scala index 5e2eae11708c..a0c361382a54 100644 --- a/tests/warn/21681c.scala +++ b/tests/warn/21681c.scala @@ -1,3 +1,5 @@ +import scala.language.experimental.namedTuples + object Test: def age: Int = ??? def age_=(x: Int): Unit = () diff --git a/tests/warn/21770.check b/tests/warn/21770.check index 0899f11d6ca5..10a1c287599c 100644 --- a/tests/warn/21770.check +++ b/tests/warn/21770.check @@ -1,5 +1,5 @@ --- [E203] Syntax Migration Warning: tests/warn/21770.scala:5:9 --------------------------------------------------------- -5 | f(i => (cache = Some(i))) // warn +-- [E203] Syntax Migration Warning: tests/warn/21770.scala:7:9 --------------------------------------------------------- +7 | f(i => (cache = Some(i))) // warn | ^^^^^^^^^^^^^^^^^ | Ambiguous syntax: this is interpreted as a named tuple with one element, | not as an assignment. diff --git a/tests/warn/21770.scala b/tests/warn/21770.scala index 9696a31d6ba8..8ee5b52e7b3f 100644 --- a/tests/warn/21770.scala +++ b/tests/warn/21770.scala @@ -1,5 +1,7 @@ +import scala.language.experimental.namedTuples + def f(g: Int => Unit) = g(0) -def test = +def test = var cache: Option[Int] = None f(i => (cache = Some(i))) // warn diff --git a/tests/warn/infix-named-args-migration.scala b/tests/warn/infix-named-args-migration.scala index df4bfb50271c..361004f08f13 100644 --- a/tests/warn/infix-named-args-migration.scala +++ b/tests/warn/infix-named-args-migration.scala @@ -1,4 +1,6 @@ //> using options -source:3.6-migration +import scala.language.experimental.namedTuples + class C: def f = 42 + (x = 1) // warn // interpreted as 42.+(x = 1) under migration, x is a valid synthetic parameter name def multi(x: Int, y: Int): Int = x + y From 861beeef093501717bf322a5b442199188664daa Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 27 Nov 2024 22:36:20 +0100 Subject: [PATCH 068/202] Add migration rewrite for deprecated assignment syntax --- .../tools/dotc/config/MigrationVersion.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 4 ++-- .../tools/dotc/reporting/ErrorMessageID.scala | 4 ++-- .../dotty/tools/dotc/reporting/messages.scala | 18 ++++++++--------- .../src/dotty/tools/dotc/typer/Typer.scala | 6 +++--- tests/neg/infix-named-args.check | 20 ++++++++----------- tests/warn/21681.check | 2 +- tests/warn/21681b.check | 2 +- tests/warn/21681c.check | 2 +- tests/warn/21770.check | 2 +- 10 files changed, 28 insertions(+), 34 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala b/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala index 247e3f62a98d..1d99caa789d3 100644 --- a/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala @@ -26,7 +26,7 @@ enum MigrationVersion(val warnFrom: SourceVersion, val errorFrom: SourceVersion) case WithOperator extends MigrationVersion(`3.4`, future) case FunctionUnderscore extends MigrationVersion(`3.4`, future) case NonNamedArgumentInJavaAnnotation extends MigrationVersion(`3.6`, `3.6`) - case AmbiguousNamedTupleInfixApply extends MigrationVersion(`3.6`, never) + case AmbiguousNamedTupleSyntax extends MigrationVersion(`3.6`, future) case ImportWildcard extends MigrationVersion(future, future) case ImportRename extends MigrationVersion(future, future) case ParameterEnclosedByParenthesis extends MigrationVersion(future, future) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 3ea4131d5d78..220053e277a5 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1149,8 +1149,8 @@ object Parsers { if isType then infixOp else infixOp.right match case Tuple(args) if args.exists(_.isInstanceOf[NamedArg]) && !isNamedTupleOperator => - report.errorOrMigrationWarning(AmbiguousNamedTupleInfixApply(), infixOp.right.srcPos, MigrationVersion.AmbiguousNamedTupleInfixApply) - if MigrationVersion.AmbiguousNamedTupleInfixApply.needsPatch then + report.errorOrMigrationWarning(DeprecatedInfixNamedArgumentSyntax(), infixOp.right.srcPos, MigrationVersion.AmbiguousNamedTupleSyntax) + if MigrationVersion.AmbiguousNamedTupleSyntax.needsPatch then val asApply = cpy.Apply(infixOp)(Select(opInfo.operand, opInfo.operator.name), args) patch(source, infixOp.span, asApply.show(using ctx.withoutColors)) asApply // allow to use pre-3.6 syntax in migration mode diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 35c170858bbf..2c3774b59a9a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -216,8 +216,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case FinalLocalDefID // errorNumber: 200 case NonNamedArgumentInJavaAnnotationID // errorNumber: 201 case QuotedTypeMissingID // errorNumber: 202 - case AmbiguousNamedTupleAssignmentID // errorNumber: 203 - case AmbiguousNamedTupleInfixApplyID // errorNumber: 204 + case DeprecatedAssignmentSyntaxID // errorNumber: 203 + case DeprecatedInfixNamedArgumentSyntaxID // errorNumber: 204 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 f94a4b58d6fb..2e74c5816c5e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3344,21 +3344,19 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q end QuotedTypeMissing -final class AmbiguousNamedTupleAssignment(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(AmbiguousNamedTupleAssignmentID): +final class DeprecatedAssignmentSyntax(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(DeprecatedAssignmentSyntaxID): override protected def msg(using Context): String = - i"""Ambiguous syntax: this is interpreted as a named tuple with one element, + i"""Deprecated syntax: in the future it would be interpreted as a named tuple with one element, |not as an assignment. | |To assign a value, use curly braces: `{${key} = ${value}}`.""" - + override protected def explain(using Context): String = "" -class AmbiguousNamedTupleInfixApply()(using Context) extends SyntaxMsg(AmbiguousNamedTupleInfixApplyID): +class DeprecatedInfixNamedArgumentSyntax()(using Context) extends SyntaxMsg(DeprecatedInfixNamedArgumentSyntaxID): def msg(using Context) = - "Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list." - + Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`) + i"""Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument. + |To avoid this warning, either remove the argument names or use dotted selection.""" + + Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`) - def explain(using Context) = - i"""Starting with Scala 3.6 infix named arguments are interpretted as Named Tuple. - | - |To avoid this warning, either remove the argument names or use dotted selection.""" + def explain(using Context) = "" diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 57a027653241..d74a69f2a114 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3404,7 +3404,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer /** Translate tuples of all arities */ def typedTuple(tree: untpd.Tuple, pt: Type)(using Context): Tree = val tree1 = desugar.tuple(tree, pt) - checkAmbiguousNamedTupleAssignment(tree) + checkDeprecatedAssignmentSyntax(tree) if tree1 ne tree then typed(tree1, pt) else val arity = tree.trees.length @@ -3433,7 +3433,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer /** Checks if `tree` is a named tuple with one element that could be * interpreted as an assignment, such as `(x = 1)`. If so, issues a warning. */ - def checkAmbiguousNamedTupleAssignment(tree: untpd.Tuple)(using Context): Unit = + def checkDeprecatedAssignmentSyntax(tree: untpd.Tuple)(using Context): Unit = tree.trees match case List(NamedArg(name, value)) => val tmpCtx = ctx.fresh.setNewTyperState() @@ -3441,7 +3441,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if !tmpCtx.reporter.hasErrors then // If there are no errors typing the above, then the named tuple is // ambiguous and we issue a warning. - report.migrationWarning(AmbiguousNamedTupleAssignment(name, value), tree.srcPos) + report.migrationWarning(DeprecatedAssignmentSyntax(name, value), tree.srcPos) case _ => () /** Retrieve symbol attached to given tree */ diff --git a/tests/neg/infix-named-args.check b/tests/neg/infix-named-args.check index 291c7616a57c..d960892a9624 100644 --- a/tests/neg/infix-named-args.check +++ b/tests/neg/infix-named-args.check @@ -14,28 +14,24 @@ -- [E204] Syntax Warning: tests/neg/infix-named-args.scala:4:15 -------------------------------------------------------- 4 | def f = 42 + (x = 1) // error // werror | ^^^^^^^ - |Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list. + |Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument. + |To avoid this warning, either remove the argument names or use dotted selection. |This can be rewritten automatically under -rewrite -source 3.6-migration. - | - | longer explanation available when compiling with `-explain` -- [E204] Syntax Warning: tests/neg/infix-named-args.scala:7:26 -------------------------------------------------------- 7 | def g = new C() `multi` (x = 42, y = 27) // werror | ^^^^^^^^^^^^^^^^ - |Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list. + |Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument. + |To avoid this warning, either remove the argument names or use dotted selection. |This can be rewritten automatically under -rewrite -source 3.6-migration. - | - | longer explanation available when compiling with `-explain` -- [E204] Syntax Warning: tests/neg/infix-named-args.scala:8:21 -------------------------------------------------------- 8 | def h = new C() ** (x = 42, y = 27) // werror | ^^^^^^^^^^^^^^^^ - |Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list. + |Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument. + |To avoid this warning, either remove the argument names or use dotted selection. |This can be rewritten automatically under -rewrite -source 3.6-migration. - | - | longer explanation available when compiling with `-explain` -- [E204] Syntax Warning: tests/neg/infix-named-args.scala:15:18 ------------------------------------------------------- 15 | def f = this ** (x = 2) // werror | ^^^^^^^ - |Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list. + |Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument. + |To avoid this warning, either remove the argument names or use dotted selection. |This can be rewritten automatically under -rewrite -source 3.6-migration. - | - | longer explanation available when compiling with `-explain` diff --git a/tests/warn/21681.check b/tests/warn/21681.check index 6ca2c3816b01..5156a600d609 100644 --- a/tests/warn/21681.check +++ b/tests/warn/21681.check @@ -1,7 +1,7 @@ -- [E203] Syntax Migration Warning: tests/warn/21681.scala:5:2 --------------------------------------------------------- 5 | (age = 29) // warn | ^^^^^^^^^^ - | Ambiguous syntax: this is interpreted as a named tuple with one element, + | Deprecated syntax: in the future it would be interpreted as a named tuple with one element, | not as an assignment. | | To assign a value, use curly braces: `{age = 29}`. diff --git a/tests/warn/21681b.check b/tests/warn/21681b.check index c77c1438d9a6..dd28df3168ed 100644 --- a/tests/warn/21681b.check +++ b/tests/warn/21681b.check @@ -1,7 +1,7 @@ -- [E203] Syntax Migration Warning: tests/warn/21681b.scala:5:2 -------------------------------------------------------- 5 | (age = 29) // warn | ^^^^^^^^^^ - | Ambiguous syntax: this is interpreted as a named tuple with one element, + | Deprecated syntax: in the future it would be interpreted as a named tuple with one element, | not as an assignment. | | To assign a value, use curly braces: `{age = 29}`. diff --git a/tests/warn/21681c.check b/tests/warn/21681c.check index 00c74ddba5bc..bfb62618cbb9 100644 --- a/tests/warn/21681c.check +++ b/tests/warn/21681c.check @@ -1,7 +1,7 @@ -- [E203] Syntax Migration Warning: tests/warn/21681c.scala:7:2 -------------------------------------------------------- 7 | (age = 29) // warn | ^^^^^^^^^^ - | Ambiguous syntax: this is interpreted as a named tuple with one element, + | Deprecated syntax: in the future it would be interpreted as a named tuple with one element, | not as an assignment. | | To assign a value, use curly braces: `{age = 29}`. diff --git a/tests/warn/21770.check b/tests/warn/21770.check index 10a1c287599c..6c978a6078a2 100644 --- a/tests/warn/21770.check +++ b/tests/warn/21770.check @@ -1,7 +1,7 @@ -- [E203] Syntax Migration Warning: tests/warn/21770.scala:7:9 --------------------------------------------------------- 7 | f(i => (cache = Some(i))) // warn | ^^^^^^^^^^^^^^^^^ - | Ambiguous syntax: this is interpreted as a named tuple with one element, + | Deprecated syntax: in the future it would be interpreted as a named tuple with one element, | not as an assignment. | | To assign a value, use curly braces: `{cache = Some(i)}`. From 24c3e7f601c6ba74c541f1603251253bfde45ab3 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 27 Nov 2024 23:54:11 +0100 Subject: [PATCH 069/202] Add migration rewrite deprecated assignment syntax --- .../dotty/tools/dotc/reporting/messages.scala | 1 + .../src/dotty/tools/dotc/typer/Typer.scala | 3 +++ .../dotty/tools/dotc/CompilationTests.scala | 1 + .../ambigious-named-tuple-assignment.check | 19 +++++++++++++++++++ .../ambigious-named-tuple-assignment.scala | 19 +++++++++++++++++++ tests/warn/21681.check | 1 + tests/warn/21681b.check | 1 + tests/warn/21681c.check | 1 + tests/warn/21770.check | 1 + 9 files changed, 47 insertions(+) create mode 100644 tests/rewrites/ambigious-named-tuple-assignment.check create mode 100644 tests/rewrites/ambigious-named-tuple-assignment.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 2e74c5816c5e..b396aa62f599 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3350,6 +3350,7 @@ final class DeprecatedAssignmentSyntax(key: Name, value: untpd.Tree)(using Conte |not as an assignment. | |To assign a value, use curly braces: `{${key} = ${value}}`.""" + + Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`) override protected def explain(using Context): String = "" diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d74a69f2a114..f5318759bac2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3442,6 +3442,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // If there are no errors typing the above, then the named tuple is // ambiguous and we issue a warning. report.migrationWarning(DeprecatedAssignmentSyntax(name, value), tree.srcPos) + if MigrationVersion.AmbiguousNamedTupleSyntax.needsPatch then + patch(tree.source, Span(tree.span.start, tree.span.start + 1), "{") + patch(tree.source, Span(tree.span.end - 1, tree.span.end), "}") case _ => () /** Retrieve symbol attached to given tree */ diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 3bd3b5138fad..9f72db6fc390 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -80,6 +80,7 @@ class CompilationTests { compileDir("tests/rewrites/annotation-named-pararamters", defaultOptions.and("-rewrite", "-source:3.6-migration")), compileFile("tests/rewrites/i21418.scala", unindentOptions.and("-rewrite", "-source:3.5-migration")), compileFile("tests/rewrites/infix-named-args.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), + compileFile("tests/rewrites/ambigious-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), ).checkRewrites() } diff --git a/tests/rewrites/ambigious-named-tuple-assignment.check b/tests/rewrites/ambigious-named-tuple-assignment.check new file mode 100644 index 000000000000..00e6cc4112f1 --- /dev/null +++ b/tests/rewrites/ambigious-named-tuple-assignment.check @@ -0,0 +1,19 @@ +import scala.language.experimental.namedTuples + +object i21770: + def f(g: Int => Unit) = g(0) + var cache: Option[Int] = None + f(i => {cache = Some(i)}) + +object i21861: + var age: Int = 28 + { + age = 29 + } + + +object i21861c: + def age: Int = ??? + def age_=(x: Int): Unit = () + age = 29 + { age = 29 } diff --git a/tests/rewrites/ambigious-named-tuple-assignment.scala b/tests/rewrites/ambigious-named-tuple-assignment.scala new file mode 100644 index 000000000000..e9685b7b58cf --- /dev/null +++ b/tests/rewrites/ambigious-named-tuple-assignment.scala @@ -0,0 +1,19 @@ +import scala.language.experimental.namedTuples + +object i21770: + def f(g: Int => Unit) = g(0) + var cache: Option[Int] = None + f(i => (cache = Some(i))) + +object i21861: + var age: Int = 28 + ( + age = 29 + ) + + +object i21861c: + def age: Int = ??? + def age_=(x: Int): Unit = () + age = 29 + ( age = 29 ) diff --git a/tests/warn/21681.check b/tests/warn/21681.check index 5156a600d609..adf3586e6e0b 100644 --- a/tests/warn/21681.check +++ b/tests/warn/21681.check @@ -5,3 +5,4 @@ | not as an assignment. | | To assign a value, use curly braces: `{age = 29}`. + | This can be rewritten automatically under -rewrite -source 3.6-migration. diff --git a/tests/warn/21681b.check b/tests/warn/21681b.check index dd28df3168ed..09c007f351b4 100644 --- a/tests/warn/21681b.check +++ b/tests/warn/21681b.check @@ -5,3 +5,4 @@ | not as an assignment. | | To assign a value, use curly braces: `{age = 29}`. + | This can be rewritten automatically under -rewrite -source 3.6-migration. diff --git a/tests/warn/21681c.check b/tests/warn/21681c.check index bfb62618cbb9..20273f723384 100644 --- a/tests/warn/21681c.check +++ b/tests/warn/21681c.check @@ -5,3 +5,4 @@ | not as an assignment. | | To assign a value, use curly braces: `{age = 29}`. + | This can be rewritten automatically under -rewrite -source 3.6-migration. diff --git a/tests/warn/21770.check b/tests/warn/21770.check index 6c978a6078a2..7853d77a423c 100644 --- a/tests/warn/21770.check +++ b/tests/warn/21770.check @@ -5,3 +5,4 @@ | not as an assignment. | | To assign a value, use curly braces: `{cache = Some(i)}`. + | This can be rewritten automatically under -rewrite -source 3.6-migration. From 688ed4f168d2bd40acf478f637d27f6e03c1a7d2 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Wed, 27 Nov 2024 23:01:26 +0000 Subject: [PATCH 070/202] Do not lift annotation arguments (bis) --- .../dotty/tools/dotc/typer/Applications.scala | 2 ++ .../dependent-annot-default-args.check | 23 +++++++++++++++++++ .../dependent-annot-default-args.scala | 10 ++++++++ 3 files changed, 35 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 41e48f7595dc..ff6c71f57355 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -963,6 +963,8 @@ trait Applications extends Compatibility { case (arg: NamedArg, _) => arg case (arg, name) => NamedArg(name, arg) } + else if isAnnotConstr(methRef.symbol) then + typedArgs else if !sameSeq(args, orderedArgs) && !typedArgs.forall(isSafeArg) then // need to lift arguments to maintain evaluation order in the // presence of argument reorderings. diff --git a/tests/printing/dependent-annot-default-args.check b/tests/printing/dependent-annot-default-args.check index 44c1fe31e2d1..f457d5d62edb 100644 --- a/tests/printing/dependent-annot-default-args.check +++ b/tests/printing/dependent-annot-default-args.check @@ -8,16 +8,39 @@ package { final module class annot() extends AnyRef() { this: annot.type => def $lessinit$greater$default$2: Any @uncheckedVariance = 42 } + class annot2(x: Any, y: Array[Any]) extends annotation.Annotation() { + private[this] val x: Any + private[this] val y: Array[Any] + } + final lazy module val annot2: annot2 = new annot2() + final module class annot2() extends AnyRef() { this: annot2.type => + def $lessinit$greater$default$1: Any @uncheckedVariance = -1 + def $lessinit$greater$default$2: Array[Any] @uncheckedVariance = + Array.apply[Any](["Hello" : Any]*)(scala.reflect.ClassTag.Any) + } final lazy module val dependent-annot-default-args$package: dependent-annot-default-args$package = new dependent-annot-default-args$package() final module class dependent-annot-default-args$package() extends Object() { this: dependent-annot-default-args$package.type => def f(x: Int): Int @annot(x) = x + def f2(x: Int): + Int @annot2( + y = Array.apply[Any](["Hello",x : Any]*)(scala.reflect.ClassTag.Any)) + = x def test: Unit = { val y: Int = ??? val z: Int @annot(y) = f(y) + val z2: + Int @annot2( + y = Array.apply[Any](["Hello",y : Any]*)(scala.reflect.ClassTag.Any) + ) + = f2(y) + @annot(44) val z3: Int = 45 + @annot2( + y = Array.apply[Any](["Hello",y : Any]*)(scala.reflect.ClassTag.Any)) + val z4: Int = 45 () } } diff --git a/tests/printing/dependent-annot-default-args.scala b/tests/printing/dependent-annot-default-args.scala index 7ddce711fedc..11fc9ef52cc9 100644 --- a/tests/printing/dependent-annot-default-args.scala +++ b/tests/printing/dependent-annot-default-args.scala @@ -1,5 +1,15 @@ class annot(x: Any, y: Any = 42) extends annotation.Annotation +class annot2(x: Any = -1, y: Array[Any] = Array("Hello")) extends annotation.Annotation + def f(x: Int): Int @annot(x) = x +def f2(x: Int): Int @annot2(y = Array("Hello", x)) = x + def test = val y: Int = ??? + val z = f(y) + val z2 = f2(y) + + @annot(44) val z3 = 45 + @annot2(y = Array("Hello", y)) val z4 = 45 + From 08d592fd34f60409be52d3b3b929e18928391b45 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 31 Oct 2024 17:54:29 +0000 Subject: [PATCH 071/202] Resolve name when named imp is behind wild imps When a named import (such as `import bug.util.List`) is defined before two clashing wildcard imports (`import bug.util.*; import java.util.*`) the name "List" should resolve to it, rather than a resolution error being emitted. This was due to the fact that `findRefRecur` didn't return the precedence at which it found that import, `checkImportAlternatives` used the `prevPrec` to `checkNewOrShadowed`. Now we check against the entire `foundResult`, allowing an early named import to be picked over later wildcard imports. --- .../src/dotty/tools/dotc/typer/Typer.scala | 81 ++++++++++--------- tests/pos/i18529/JCode1.java | 9 +++ tests/pos/i18529/JCode2.java | 9 +++ tests/pos/i18529/List.java | 3 + tests/pos/i18529/SCode1.scala | 9 +++ tests/pos/i18529/SCode2.scala | 9 +++ tests/pos/i18529/Test.scala | 1 + 7 files changed, 82 insertions(+), 39 deletions(-) create mode 100644 tests/pos/i18529/JCode1.java create mode 100644 tests/pos/i18529/JCode2.java create mode 100644 tests/pos/i18529/List.java create mode 100644 tests/pos/i18529/SCode1.scala create mode 100644 tests/pos/i18529/SCode2.scala create mode 100644 tests/pos/i18529/Test.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 13f7b4eb1726..c133d70ff9c7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -241,38 +241,40 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer !owner.isEmptyPackage || ctx.owner.enclosingPackageClass.isEmptyPackage } + import BindingPrec.* + type Result = (Type, BindingPrec) + val NoResult = (NoType, NothingBound) + /** Find the denotation of enclosing `name` in given context `ctx`. - * @param previous A denotation that was found in a more deeply nested scope, - * or else `NoDenotation` if nothing was found yet. - * @param prevPrec The binding precedence of the previous denotation, - * or else `nothingBound` if nothing was found yet. + * @param prevResult A type that was found in a more deeply nested scope, + * and its precedence, or NoResult if nothing was found yet. * @param prevCtx The context of the previous denotation, * or else `NoContext` if nothing was found yet. */ - def findRefRecur(previous: Type, prevPrec: BindingPrec, prevCtx: Context)(using Context): Type = { - import BindingPrec.* + def findRefRecur(prevResult: Result, prevCtx: Context)(using Context): Result = { + val (previous, prevPrec) = prevResult /** Check that any previously found result from an inner context * does properly shadow the new one from an outer context. - * @param found The newly found result - * @param newPrec Its precedence + * @param newResult The newly found type and its precedence. * @param scala2pkg Special mode where we check members of the same package, but defined * in different compilation units under Scala2. If set, and the * previous and new contexts do not have the same scope, we select * the previous (inner) definition. This models what scalac does. */ - def checkNewOrShadowed(found: Type, newPrec: BindingPrec, scala2pkg: Boolean = false)(using Context): Type = + def checkNewOrShadowed(newResult: Result, scala2pkg: Boolean = false)(using Context): Result = + val (found, newPrec) = newResult if !previous.exists || TypeComparer.isSameRef(previous, found) then - found + newResult else if (prevCtx.scope eq ctx.scope) && newPrec.beats(prevPrec) then // special cases: definitions beat imports, and named imports beat // wildcard imports, provided both are in contexts with same scope - found + newResult else if !scala2pkg && !previous.isError && !found.isError then fail(AmbiguousReference(name, newPrec, prevPrec, prevCtx, isExtension = previous.termSymbol.is(ExtensionMethod) && found.termSymbol.is(ExtensionMethod))) - previous + prevResult /** Assemble and check alternatives to an imported reference. This implies: * - If we expand an extension method (i.e. altImports != null), @@ -285,12 +287,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer * shadowed. This order of checking is necessary since an outer package-level * definition might trump two conflicting inner imports, so no error should be * issued in that case. See i7876.scala. - * @param previous the previously found reference (which is an import) - * @param prevPrec the precedence of the reference (either NamedImport or WildImport) + * @param prevResult the previously found reference (which is an import), and + * the precedence of the reference (either NamedImport or WildImport) * @param prevCtx the context in which the reference was found * @param using_Context the outer context of `precCtx` */ - def checkImportAlternatives(previous: Type, prevPrec: BindingPrec, prevCtx: Context)(using Context): Type = + def checkImportAlternatives(prevResult: Result, prevCtx: Context)(using Context): Result = + val (previous, prevPrec) = prevResult def addAltImport(altImp: TermRef) = if !TypeComparer.isSameRef(previous, altImp) @@ -305,20 +308,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if prevPrec == WildImport then // Discard all previously found references and continue with `altImp` altImports.clear() - checkImportAlternatives(altImp, NamedImport, ctx)(using ctx.outer) + checkImportAlternatives((altImp, NamedImport), ctx)(using ctx.outer) else addAltImport(altImp) - checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer) + checkImportAlternatives(prevResult, prevCtx)(using ctx.outer) case _ => if prevPrec == WildImport then wildImportRef(curImport) match case altImp: TermRef => addAltImport(altImp) case _ => - checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer) + checkImportAlternatives(prevResult, prevCtx)(using ctx.outer) else - val found = findRefRecur(previous, prevPrec, prevCtx) - if found eq previous then checkNewOrShadowed(found, prevPrec)(using prevCtx) - else found + val foundResult = findRefRecur(prevResult, prevCtx) + if foundResult._1 eq previous then checkNewOrShadowed(foundResult)(using prevCtx) + else foundResult end checkImportAlternatives def selection(imp: ImportInfo, name: Name, checkBounds: Boolean): Type = @@ -408,10 +411,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer !noImports && (prevPrec.ordinal < prec.ordinal || prevPrec == prec && (prevCtx.scope eq ctx.scope)) - @tailrec def loop(lastCtx: Context)(using Context): Type = - if (ctx.scope eq EmptyScope) previous + @tailrec def loop(lastCtx: Context)(using Context): Result = + if (ctx.scope eq EmptyScope) prevResult else { - var result: Type = NoType + var result: Result = NoResult val curOwner = ctx.owner /** Is curOwner a package object that should be skipped? @@ -510,7 +513,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer effectiveOwner.thisType.select(name, defDenot).makePackageObjPrefixExplicit } if !curOwner.is(Package) || isDefinedInCurrentUnit(defDenot) then - result = checkNewOrShadowed(found, Definition) // no need to go further out, we found highest prec entry + result = checkNewOrShadowed((found, Definition)) // no need to go further out, we found highest prec entry found match case found: NamedType if curOwner.isClass && isInherited(found.denot) && !ctx.compilationUnit.isJava => @@ -518,29 +521,28 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => else if migrateTo3 && !foundUnderScala2.exists then - foundUnderScala2 = checkNewOrShadowed(found, Definition, scala2pkg = true) + foundUnderScala2 = checkNewOrShadowed((found, Definition), scala2pkg = true)._1 if (defDenot.symbol.is(Package)) - result = checkNewOrShadowed(previous orElse found, PackageClause) + result = checkNewOrShadowed((previous orElse found, PackageClause)) else if (prevPrec.ordinal < PackageClause.ordinal) - result = findRefRecur(found, PackageClause, ctx)(using ctx.outer) + result = findRefRecur((found, PackageClause), ctx)(using ctx.outer) } - if result.exists then result + if result._1.exists then result else { // find import val outer = ctx.outer val curImport = ctx.importInfo - def updateUnimported() = - if (curImport.nn.unimported ne NoSymbol) unimported += curImport.nn.unimported if (curOwner.is(Package) && curImport != null && curImport.isRootImport && previous.exists) - previous // no more conflicts possible in this case - else if (isPossibleImport(NamedImport) && (curImport nen outer.importInfo)) { - val namedImp = namedImportRef(curImport.uncheckedNN) + prevResult // no more conflicts possible in this case + else if (isPossibleImport(NamedImport) && curImport != null && (curImport ne outer.importInfo)) { + def updateUnimported() = if curImport.unimported ne NoSymbol then unimported += curImport.unimported + val namedImp = namedImportRef(curImport) if (namedImp.exists) - checkImportAlternatives(namedImp, NamedImport, ctx)(using outer) - else if (isPossibleImport(WildImport) && !curImport.nn.importSym.isCompleting) { - val wildImp = wildImportRef(curImport.uncheckedNN) + checkImportAlternatives((namedImp, NamedImport), ctx)(using outer) + else if (isPossibleImport(WildImport) && !curImport.importSym.isCompleting) { + val wildImp = wildImportRef(curImport) if (wildImp.exists) - checkImportAlternatives(wildImp, WildImport, ctx)(using outer) + checkImportAlternatives((wildImp, WildImport), ctx)(using outer) else { updateUnimported() loop(ctx)(using outer) @@ -559,7 +561,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer loop(NoContext) } - findRefRecur(NoType, BindingPrec.NothingBound, NoContext) + val (foundRef, foundPrec) = findRefRecur(NoResult, NoContext) + foundRef } /** If `tree`'s type is a `TermRef` identified by flow typing to be non-null, then diff --git a/tests/pos/i18529/JCode1.java b/tests/pos/i18529/JCode1.java new file mode 100644 index 000000000000..e1f12f852c00 --- /dev/null +++ b/tests/pos/i18529/JCode1.java @@ -0,0 +1,9 @@ +package bug.code; + +import bug.util.List; +import bug.util.*; +import java.util.*; + +public class JCode1 { + public void m1(List xs) { return; } +} diff --git a/tests/pos/i18529/JCode2.java b/tests/pos/i18529/JCode2.java new file mode 100644 index 000000000000..2a1ec812852c --- /dev/null +++ b/tests/pos/i18529/JCode2.java @@ -0,0 +1,9 @@ +package bug.code; + +import bug.util.*; +import bug.util.List; +import java.util.*; + +public class JCode2 { + public void m1(List xs) { return; } +} diff --git a/tests/pos/i18529/List.java b/tests/pos/i18529/List.java new file mode 100644 index 000000000000..caf3c0b8036b --- /dev/null +++ b/tests/pos/i18529/List.java @@ -0,0 +1,3 @@ +package bug.util; + +public final class List {} diff --git a/tests/pos/i18529/SCode1.scala b/tests/pos/i18529/SCode1.scala new file mode 100644 index 000000000000..b6796b1540c6 --- /dev/null +++ b/tests/pos/i18529/SCode1.scala @@ -0,0 +1,9 @@ +package bug.code + +import bug.util.List +import bug.util.* +import java.util.* + +class SCode1 { + def work(xs: List[Int]): Unit = {} +} diff --git a/tests/pos/i18529/SCode2.scala b/tests/pos/i18529/SCode2.scala new file mode 100644 index 000000000000..30fc7d0e6f91 --- /dev/null +++ b/tests/pos/i18529/SCode2.scala @@ -0,0 +1,9 @@ +package bug.code + +import bug.util.* +import bug.util.List +import java.util.* + +class SCode2 { + def work(xs: List[Int]): Unit = {} +} diff --git a/tests/pos/i18529/Test.scala b/tests/pos/i18529/Test.scala new file mode 100644 index 000000000000..be7795442a7a --- /dev/null +++ b/tests/pos/i18529/Test.scala @@ -0,0 +1 @@ +class Test From ae0070392c63882231bc97b143c39b001a5bfbec Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 29 Nov 2024 15:43:07 +0100 Subject: [PATCH 072/202] Update a test related to library cc --- tests/neg-custom-args/captures/i21614.check | 22 ++++++++++----------- tests/neg-custom-args/captures/i21614.scala | 3 +++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/neg-custom-args/captures/i21614.check b/tests/neg-custom-args/captures/i21614.check index ced3ab7fd59a..f4967253455f 100644 --- a/tests/neg-custom-args/captures/i21614.check +++ b/tests/neg-custom-args/captures/i21614.check @@ -1,15 +1,15 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:9:33 ---------------------------------------- -9 | files.map((f: F) => new Logger(f)) // error, Q: can we make this pass (see #19076)? - | ^ - | Found: (f : F^) - | Required: File^ - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:12:12 --------------------------------------- -12 | files.map(new Logger(_)) // error, Q: can we improve the error message? +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:12:33 --------------------------------------- +12 | files.map((f: F) => new Logger(f)) // error, Q: can we make this pass (see #19076)? + | ^ + | Found: (f : F^) + | Required: File^ + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:15:12 --------------------------------------- +15 | files.map(new Logger(_)) // error, Q: can we improve the error message? | ^^^^^^^^^^^^^ - | Found: (_$1: box File^{files*}) ->{files*} (ex$13: caps.Exists) -> box Logger{val f: File^{_$1}}^{ex$13} - | Required: (_$1: box File^{files*}) -> box Logger{val f: File^?}^? + | Found: (_$1: box File^{files*}) ->{files*} (ex$16: caps.Exists) -> box Logger{val f: File^{_$1}}^{ex$16} + | Required: (_$1: box File^{files*}) => box Logger{val f: File^?}^? | | Note that the universal capability `cap` | cannot be included in capture set ? diff --git a/tests/neg-custom-args/captures/i21614.scala b/tests/neg-custom-args/captures/i21614.scala index 69aef446e9e2..f5bab90f543b 100644 --- a/tests/neg-custom-args/captures/i21614.scala +++ b/tests/neg-custom-args/captures/i21614.scala @@ -2,6 +2,9 @@ import language.experimental.captureChecking import caps.Capability import caps.use +trait List[+T]: + def map[U](f: T => U): List[U] + trait File extends Capability class Logger(f: File^) extends Capability // <- will work if we remove the extends clause From ca1b4c0ca4ac477ca8f7ad7a3a5c3078e8401726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Fri, 29 Nov 2024 16:05:01 +0100 Subject: [PATCH 073/202] Update MAINTENANCE.md --- MAINTENANCE.md | 55 +++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/MAINTENANCE.md b/MAINTENANCE.md index 79e55e11d4a9..05b90cc90b86 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -63,39 +63,48 @@ At the end of their supervision period, the supervisor reports to the team durin ## Maintenance List -The following is the list of all the principal areas of the compiler and the core team members who are responsible for their maintenance: +The following is the list of all the principal areas of the compiler and the internal team members who are responsible for their maintenance: ### Compiler -- Parser: @odersky, @hamzaremmal -- Typer: @odersky, @smarter, (@dwijnand), @noti0nal +- Parser: @odersky, @hamzaremmal, @KacperFKorban +- Typer: @odersky, @smarter, (@dwijnand), @noti0nal, @EugeneFlesselle, @KacperFKorban, @bracevac - Erasure: @smarter, @odersky -- Enums: @bishabosha -- Derivation & Mirrors: @bishabosha, (@dwijnand), @EugeneFlesselle -- Export: @bishabosha, @odersky -- Pattern Matching: @dwijnand, (@liufengyun), @sjrd -- Inline: @nicolasstucki, @odersky, @hamzaremmal -- Metaprogramming (Quotes, Reflect, Staging): @nicolasstucki, @jchyb, @hamzaremmal -- Match types: @sjrd, @dwijnand, @Decel, @Linyxus +- Enums: +- Derivation & Mirrors: (@dwijnand), @EugeneFlesselle +- Export: @odersky +- Pattern Matching: @dwijnand, @sjrd, @noti0na1 +- Inline: @odersky, @jchyb, @hamzaremmal, @EugeneFlesselle +- Metaprogramming (Quotes, Reflect, Staging): @jchyb, @hamzaremmal +- Match types: @sjrd, @dwijnand, @Linyxus, @EugeneFlesselle - GADT: @dwijnand, @Linyxus -- Initialization checker: @olhotak, @liufengyun -- Safe nulls: @noti0na1, @olhotak -- Transforms: @szymon-rd, @sjrd, @odersky, @smarter -- tailrec: @sjrd, @mbovel +- Initialization checker: +- Transforms: @sjrd, @odersky, @smarter +- Tailrec: @sjrd, @mbovel - JS backend: @sjrd -- JVM backend: @sjrd -- Java-compat: @smarter, @dwijnand -- Capture checker: @odersky, @Linyxus +- JVM backend: @sjrd, @hamzaremmal +- Java-compat: @smarter, @dwijnand, @hamzaremmal +- Extension Methods: @odersky, @dwijnand +- Safe nulls (experimental): @noti0na1 +- Capture checker (experimental): @odersky, @Linyxus, @bracevac, @noti0na1 +- Modularity (experimental): @KacperFKorban +- Named Tuples (experimental): @odersky ### Tooling -- REPL: @dwijnand, @prolativ +- REPL: @dwijnand +- Runner/CLI: @Gedochao, (@tgodzik) +- IDE: @tgodzik, (@kasiaMarek) - Scaladoc: @Florian3k -- SemanticDB: @tanishiking -- Coverage: @TheElectronWill -- Linting (especially unused warnings) / Reporting UX: @szymon-rd +- SemanticDB: @natsukagami, (@tanishiking) +- Coverage: @KacperFKorban +- Linting (especially unused warnings) / Reporting UX: @KacperFKorban +- Presentation Compiler: @rochala, @tgodzik, @kasiaMarek, @natsukagami +- Debug Adapter: @adpi2, (@tgodzik) +- Scastie: @rochala ### Infrastructure -- CI: @hamzaremmal +- CI: @hamzaremmal, (@WojciechMazur) - Community Build: @hamzaremmal - Open Community Build: @WojciechMazur -- Vulpix: @dwijnand, @prolativ +- Vulpix: @dwijnand, @prolativ, @hamzaremmal - Benchmarks: @mbovel +- Releases: @WojciechMazur, @prolativ From 31dd8558e9e9a5e3c080947bbf2192861427c128 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sat, 30 Nov 2024 14:04:54 +0100 Subject: [PATCH 074/202] Fix namedTuples import --- tests/pos/named-tuple-downcast.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/pos/named-tuple-downcast.scala b/tests/pos/named-tuple-downcast.scala index 239089b60c3d..b9876623faf2 100644 --- a/tests/pos/named-tuple-downcast.scala +++ b/tests/pos/named-tuple-downcast.scala @@ -1,3 +1,5 @@ +import scala.language.experimental.namedTuples + type Person = (name: String, age: Int) val Bob: Person = (name = "Bob", age = 33) From 921e9e0faa63d31386d060a4c1f895e0a8857d33 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 21 Nov 2024 20:10:41 +0100 Subject: [PATCH 075/202] Make capture parameters and members bounded by CapSet by default --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 10 ++++++--- .../dotty/tools/dotc/parsing/Parsers.scala | 9 +++++--- .../captures/capset-bound2.scala | 13 ++++++++++++ .../captures/capset-members.scala | 21 +++++++++++++++++++ 4 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 tests/neg-custom-args/captures/capset-bound2.scala create mode 100644 tests/neg-custom-args/captures/capset-members.scala diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index e8e3646bd087..b91c7a712f4c 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -527,10 +527,14 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def makeCapsOf(tp: RefTree)(using Context): Tree = TypeApply(Select(scalaDot(nme.caps), nme.capsOf), tp :: Nil) - def makeCapsBound()(using Context): Tree = - makeRetaining( + // `type C^` and `[C^]` becomes: + // `type C >: CapSet <: CapSet^{cap}` and `[C >: CapSet <: CapSet^{cap}]` + def makeCapsBound()(using Context): TypeBoundsTree = + TypeBoundsTree( Select(scalaDot(nme.caps), tpnme.CapSet), - Nil, tpnme.retainsCap) + makeRetaining( + Select(scalaDot(nme.caps), tpnme.CapSet), + Nil, tpnme.retainsCap)) def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef = DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 220053e277a5..a67bb72333e9 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2240,7 +2240,7 @@ object Parsers { atSpan(in.offset): if in.isIdent(nme.UPARROW) && Feature.ccEnabled then in.nextToken() - TypeBoundsTree(EmptyTree, makeCapsBound()) + makeCapsBound() else TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE)) @@ -4057,8 +4057,11 @@ object Parsers { || sourceVersion.isAtLeast(`3.6`) && in.isColon => makeTypeDef(typeAndCtxBounds(tname)) case _ => - syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token)) - return EmptyTree // return to avoid setting the span to EmptyTree + if in.isIdent(nme.UPARROW) && Feature.ccEnabled then + makeTypeDef(typeAndCtxBounds(tname)) + else + syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token)) + return EmptyTree // return to avoid setting the span to EmptyTree } } } diff --git a/tests/neg-custom-args/captures/capset-bound2.scala b/tests/neg-custom-args/captures/capset-bound2.scala new file mode 100644 index 000000000000..679606f0e43c --- /dev/null +++ b/tests/neg-custom-args/captures/capset-bound2.scala @@ -0,0 +1,13 @@ +import caps.* + +class IO + +def f[C^](io: IO^{C^}) = ??? + +def test = + f[CapSet](???) + f[CapSet^{}](???) + f[CapSet^](???) + f[Nothing](???) // error + f[String](???) // error + \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members.scala b/tests/neg-custom-args/captures/capset-members.scala new file mode 100644 index 000000000000..bcc92835b95f --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members.scala @@ -0,0 +1,21 @@ +import caps.* + +trait Abstract[X^]: + type C >: X <: CapSet^ + def boom(): Unit^{C^} + +class Concrete extends Abstract[CapSet^{}]: + type C = CapSet^{} + def boom() = () + +class Concrete2 extends Abstract[CapSet^{}]: + type C = CapSet^{} & CapSet^{} + def boom() = () + +class Concrete3 extends Abstract[CapSet^{}]: + type C = CapSet^{} | CapSet^{} + def boom() = () + +class Concrete4 extends Abstract[CapSet^{}]: + type C = Nothing // error + def boom() = () From c996062deea19cf9932d050ffae009a114a15895 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sun, 24 Nov 2024 23:22:33 +0100 Subject: [PATCH 076/202] Correct rules for CapSet; update definition for Contains; fix tests --- .../src/dotty/tools/dotc/cc/CaptureRef.scala | 23 +++++++++++++++---- library/src/scala/caps.scala | 4 ++-- .../captures/capture-poly.scala | 2 +- tests/neg-custom-args/captures/i22005.scala | 8 +++++++ .../neg-custom-args/captures/use-capset.check | 16 ++++++------- .../neg-custom-args/captures/use-capset.scala | 2 -- tests/neg/cc-poly-2.check | 7 ------ tests/neg/cc-poly-2.scala | 2 +- tests/pos/cc-poly-source-capability.scala | 5 ++-- 9 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 tests/neg-custom-args/captures/i22005.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala index 199114880c2b..a11e272685d9 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -100,7 +100,8 @@ trait CaptureRef extends TypeProxy, ValueType: * x: x1.type /\ x1 subsumes y ==> x subsumes y * TODO: Document path cases */ - final def subsumes(y: CaptureRef)(using Context): Boolean = + // import reporting.trace + final def subsumes(y: CaptureRef)(using Context): Boolean = // trace.force(i"subsumes $this, $y"): def subsumingRefs(x: Type, y: Type): Boolean = x match case x: CaptureRef => y match @@ -135,14 +136,28 @@ trait CaptureRef extends TypeProxy, ValueType: case _ => false || viaInfo(y.info)(subsumingRefs(this, _)) case MaybeCapability(y1) => this.stripMaybe.subsumes(y1) + case y: TypeRef if y.symbol.info.derivesFrom(defn.Caps_CapSet) => + y.info match + case _: TypeAlias => y.captureSetOfInfo.elems.forall(this.subsumes) + case TypeBounds(_, hi: CaptureRef) => this.subsumes(hi) + case _ => y.captureSetOfInfo.elems.forall(this.subsumes) case _ => false || this.match case ReachCapability(x1) => x1.subsumes(y.stripReach) case x: TermRef => viaInfo(x.info)(subsumingRefs(_, y)) case x: TermParamRef => subsumesExistentially(x, y) - case x: TypeRef if x.symbol.info.derivesFrom(defn.Caps_CapSet) => - x.captureSetOfInfo.elems.exists(_.subsumes(y)) - case x: TypeRef => assumedContainsOf(x).contains(y) + case x: TypeRef if assumedContainsOf(x).contains(y) => true + case x: TypeRef if x.derivesFrom(defn.Caps_CapSet) => + x.info match + case _: TypeAlias => + x.captureSetOfInfo.elems.exists(_.subsumes(y)) + case TypeBounds(lo: CaptureRef, _) => + lo.subsumes(y) + case _ => + x.captureSetOfInfo.elems.exists(_.subsumes(y)) + case AnnotatedType(parent, ann) + if ann.symbol.isRetains && parent.derivesFrom(defn.Caps_CapSet) => + ann.tree.toCaptureSet.elems.exists(_.subsumes(y)) case _ => false end subsumes diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala index 53c4ae7dc0dd..da099c4afab6 100644 --- a/library/src/scala/caps.scala +++ b/library/src/scala/caps.scala @@ -22,12 +22,12 @@ import annotation.{experimental, compileTimeOnly, retainsCap} /** A type constraint expressing that the capture set `C` needs to contain * the capability `R` */ - sealed trait Contains[C <: CapSet @retainsCap, R <: Singleton] + sealed trait Contains[+C >: CapSet <: CapSet @retainsCap, R <: Singleton] /** The only implementation of `Contains`. The constraint that `{R} <: C` is * added separately by the capture checker. */ - given containsImpl[C <: CapSet @retainsCap, R <: Singleton]: Contains[C, R]() + given containsImpl[C >: CapSet <: CapSet @retainsCap, R <: Singleton]: Contains[C, R]() /** A wrapper indicating a type variable in a capture argument list of a * @retains annotation. E.g. `^{x, Y^}` is represented as `@retains(x, capsOf[Y])`. diff --git a/tests/neg-custom-args/captures/capture-poly.scala b/tests/neg-custom-args/captures/capture-poly.scala index a3a7a4c2a3d7..0a0d773a3f64 100644 --- a/tests/neg-custom-args/captures/capture-poly.scala +++ b/tests/neg-custom-args/captures/capture-poly.scala @@ -3,7 +3,7 @@ import caps.* trait Foo extends Capability trait CaptureSet: - type C <: CapSet^ + type C^ def capturePoly[C^](a: Foo^{C^}): Foo^{C^} = a def capturePoly2(c: CaptureSet)(a: Foo^{c.C^}): Foo^{c.C^} = a diff --git a/tests/neg-custom-args/captures/i22005.scala b/tests/neg-custom-args/captures/i22005.scala new file mode 100644 index 000000000000..a9dca999e42b --- /dev/null +++ b/tests/neg-custom-args/captures/i22005.scala @@ -0,0 +1,8 @@ +import caps.* + +class IO +class File(io: IO^) + +class Handler[C^]: + def f(file: File^): File^{C^} = file // error + def g(file: File^{C^}): File^ = file // ok diff --git a/tests/neg-custom-args/captures/use-capset.check b/tests/neg-custom-args/captures/use-capset.check index cb330daf67f8..74afaa05890f 100644 --- a/tests/neg-custom-args/captures/use-capset.check +++ b/tests/neg-custom-args/captures/use-capset.check @@ -1,19 +1,19 @@ --- Error: tests/neg-custom-args/captures/use-capset.scala:7:50 --------------------------------------------------------- -7 |private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error +-- Error: tests/neg-custom-args/captures/use-capset.scala:5:50 --------------------------------------------------------- +5 |private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error | ^^^^^^^ | Capture set parameter C leaks into capture scope of method g. | To allow this, the type C should be declared with a @use annotation --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:13:22 ----------------------------------- -13 | val _: () -> Unit = h // error: should be ->{io} +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:11:22 ----------------------------------- +11 | val _: () -> Unit = h // error: should be ->{io} | ^ | Found: (h : () ->{io} Unit) | Required: () -> Unit | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:15:50 ----------------------------------- -15 | val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:13:50 ----------------------------------- +13 | val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} | ^^ - | Found: (h2 : () ->? (x$0: List[box Object^]^{}) ->{io} Object^{io}) - | Required: () -> List[box Object^{io}] -> Object^{io} + | Found: (h2 : () ->? (x$0: List[box Object^{io}]^{}) ->{io} Object^{io}) + | Required: () -> List[box Object^{io}] -> Object^{io} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/use-capset.scala b/tests/neg-custom-args/captures/use-capset.scala index 6010e955f867..74288d616396 100644 --- a/tests/neg-custom-args/captures/use-capset.scala +++ b/tests/neg-custom-args/captures/use-capset.scala @@ -1,7 +1,5 @@ import caps.{use, CapSet} - - def f[C^](@use xs: List[Object^{C^}]): Unit = ??? private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error diff --git a/tests/neg/cc-poly-2.check b/tests/neg/cc-poly-2.check index 0615ce19b5ea..7a2882775a75 100644 --- a/tests/neg/cc-poly-2.check +++ b/tests/neg/cc-poly-2.check @@ -1,10 +1,3 @@ --- [E007] Type Mismatch Error: tests/neg/cc-poly-2.scala:13:15 --------------------------------------------------------- -13 | f[Nothing](d) // error - | ^ - | Found: (d : Test.D^) - | Required: Test.D - | - | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg/cc-poly-2.scala:14:19 --------------------------------------------------------- 14 | f[CapSet^{c1}](d) // error | ^ diff --git a/tests/neg/cc-poly-2.scala b/tests/neg/cc-poly-2.scala index c5e5df6540da..809fa8ae077c 100644 --- a/tests/neg/cc-poly-2.scala +++ b/tests/neg/cc-poly-2.scala @@ -10,7 +10,7 @@ object Test: def test(c1: C, c2: C) = val d: D^ = D() - f[Nothing](d) // error + // f[Nothing](d) // already rule out at typer f[CapSet^{c1}](d) // error val x = f(d) val _: D^{c1} = x // error diff --git a/tests/pos/cc-poly-source-capability.scala b/tests/pos/cc-poly-source-capability.scala index 363f261dadc1..6f6bdd91d20a 100644 --- a/tests/pos/cc-poly-source-capability.scala +++ b/tests/pos/cc-poly-source-capability.scala @@ -20,13 +20,14 @@ import caps.use def test1(async1: Async, @use others: List[Async]) = val src = Source[CapSet^{async1, others*}] + val _: Set[Listener^{async1, others*}] = src.allListeners val lst1 = listener(async1) val lsts = others.map(listener) val _: List[Listener^{others*}] = lsts src.register{lst1} src.register(listener(async1)) - lsts.foreach(src.register) - others.map(listener).foreach(src.register) + lsts.foreach(src.register(_)) // TODO: why we need to use _ explicitly here? + others.map(listener).foreach(src.register(_)) val ls = src.allListeners val _: Set[Listener^{async1, others*}] = ls From 8a15af3976715da2504f62862f176a1146df6aef Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 26 Nov 2024 01:57:42 +0100 Subject: [PATCH 077/202] Refine subsumes rule; fix test --- .../src/dotty/tools/dotc/cc/CaptureRef.scala | 24 ++++++++++++------- .../captures/cc-poly-varargs.scala | 21 +++++++++------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala index a11e272685d9..ec3e1908e1b5 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -93,15 +93,21 @@ trait CaptureRef extends TypeProxy, ValueType: final def invalidateCaches() = myCaptureSetRunId = NoRunId - /** x subsumes x - * this subsumes this.f + /** x subsumes x + * x subsumes x.f + * x =:= y ==> x subsumes y * x subsumes y ==> x* subsumes y, x subsumes y? * x subsumes y ==> x* subsumes y*, x? subsumes y? * x: x1.type /\ x1 subsumes y ==> x subsumes y - * TODO: Document path cases + * X = CapSet^cx, exists rx in cx, rx subsumes y ==> X subsumes y + * Y = CapSet^cy, forall ry in cy, x subsumes ry ==> x subsumes Y + * X: CapSet^c1...CapSet^c2, (CapSet^c1) subsumes y ==> X subsumes y + * Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y + * Contains[X, y] ==> X subsumes y + * + * TODO: Document cases with more comments. */ - // import reporting.trace - final def subsumes(y: CaptureRef)(using Context): Boolean = // trace.force(i"subsumes $this, $y"): + final def subsumes(y: CaptureRef)(using Context): Boolean = def subsumingRefs(x: Type, y: Type): Boolean = x match case x: CaptureRef => y match @@ -136,11 +142,13 @@ trait CaptureRef extends TypeProxy, ValueType: case _ => false || viaInfo(y.info)(subsumingRefs(this, _)) case MaybeCapability(y1) => this.stripMaybe.subsumes(y1) - case y: TypeRef if y.symbol.info.derivesFrom(defn.Caps_CapSet) => + case y: TypeRef if y.derivesFrom(defn.Caps_CapSet) => y.info match - case _: TypeAlias => y.captureSetOfInfo.elems.forall(this.subsumes) case TypeBounds(_, hi: CaptureRef) => this.subsumes(hi) case _ => y.captureSetOfInfo.elems.forall(this.subsumes) + case AnnotatedType(parent, ann) + if ann.symbol.isRetains && parent.derivesFrom(defn.Caps_CapSet) => + ann.tree.toCaptureSet.elems.forall(this.subsumes) case _ => false || this.match case ReachCapability(x1) => x1.subsumes(y.stripReach) @@ -149,8 +157,6 @@ trait CaptureRef extends TypeProxy, ValueType: case x: TypeRef if assumedContainsOf(x).contains(y) => true case x: TypeRef if x.derivesFrom(defn.Caps_CapSet) => x.info match - case _: TypeAlias => - x.captureSetOfInfo.elems.exists(_.subsumes(y)) case TypeBounds(lo: CaptureRef, _) => lo.subsumes(y) case _ => diff --git a/tests/pos-custom-args/captures/cc-poly-varargs.scala b/tests/pos-custom-args/captures/cc-poly-varargs.scala index ac76c47d6dd5..0d2478f37bdf 100644 --- a/tests/pos-custom-args/captures/cc-poly-varargs.scala +++ b/tests/pos-custom-args/captures/cc-poly-varargs.scala @@ -1,17 +1,22 @@ -trait Cancellable +abstract class Source[+T, Cap^]: + def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{this, f} = ??? -abstract class Source[+T, Cap^] - -extension[T, Cap^](src: Source[T, Cap]^) - def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{src, f} = ??? +// TODO: The extension version of `transformValuesWith` doesn't work currently. +// extension[T, Cap^](src: Source[T, Cap]^) +// def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{src, f} = ??? def race[T, Cap^](sources: Source[T, Cap]^{Cap^}*): Source[T, Cap]^{Cap^} = ??? -def either[T1, T2, Cap^](src1: Source[T1, Cap]^{Cap^}, src2: Source[T2, Cap]^{Cap^}): Source[Either[T1, T2], Cap]^{Cap^} = +def either[T1, T2, Cap^]( + src1: Source[T1, Cap]^{Cap^}, + src2: Source[T2, Cap]^{Cap^}): Source[Either[T1, T2], Cap]^{Cap^} = val left = src1.transformValuesWith(Left(_)) val right = src2.transformValuesWith(Right(_)) - race(left, right) - + race[Either[T1, T2], Cap](left, right) + // An explcit type argument is required here because the second argument is + // inferred as `CapSet^{Cap^}` instead of `Cap`. + // Although `CapSet^{Cap^}` subsums `Cap` in terms of capture set, + // `Cap` is not a subtype of `CapSet^{Cap^}` in terms of subtyping. From 092b358a9b6903cc2732affaf89c6e3bcb2c66f9 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 26 Nov 2024 15:30:40 +0100 Subject: [PATCH 078/202] Add constraint to capsOf --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 4 ++- compiler/src/dotty/tools/dotc/cc/Setup.scala | 5 ++- library/src/scala/caps.scala | 2 +- .../captures/capset-members.scala | 4 +-- tests/neg-custom-args/captures/i21868.scala | 17 +++++----- tests/neg-custom-args/captures/i21868b.scala | 33 +++++++++++++++++++ .../captures/cc-poly-varargs.scala | 10 ++---- 7 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 tests/neg-custom-args/captures/i21868b.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 5be4f6a2d1cd..1750e98f708a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -161,7 +161,9 @@ sealed abstract class CaptureSet extends Showable: def debugInfo(using Context) = i"$this accountsFor $x, which has capture set ${x.captureSetOfInfo}" def test(using Context) = reporting.trace(debugInfo): elems.exists(_.subsumes(x)) - || !x.isMaxCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK + || !x.isMaxCapability + && !x.derivesFrom(defn.Caps_CapSet) + && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK comparer match case comparer: ExplainingTypeComparer => comparer.traceIndented(debugInfo)(test) case _ => test diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index dfb9ec70bdba..9757e1af3cdb 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -117,6 +117,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * The info of these symbols is made fluid. */ def isPreCC(sym: Symbol)(using Context): Boolean = + // TODO: check type members as well sym.isTerm && sym.maybeOwner.isClass && !sym.is(Module) && !sym.owner.is(CaptureChecked) @@ -866,7 +867,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if others.accountsFor(ref) then report.warning(em"redundant capture: $dom already accounts for $ref", pos) - if ref.captureSetOfInfo.elems.isEmpty && !ref.derivesFrom(defn.Caps_Capability) then + if ref.captureSetOfInfo.elems.isEmpty + && !ref.derivesFrom(defn.Caps_Capability) + && !ref.derivesFrom(defn.Caps_CapSet) then val deepStr = if ref.isReach then " deep" else "" report.error(em"$ref cannot be tracked since its$deepStr capture set is empty", pos) check(parent.captureSet, parent) diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala index da099c4afab6..c35b3b55e813 100644 --- a/library/src/scala/caps.scala +++ b/library/src/scala/caps.scala @@ -33,7 +33,7 @@ import annotation.{experimental, compileTimeOnly, retainsCap} * @retains annotation. E.g. `^{x, Y^}` is represented as `@retains(x, capsOf[Y])`. */ @compileTimeOnly("Should be be used only internally by the Scala compiler") - def capsOf[CS]: Any = ??? + def capsOf[CS >: CapSet <: CapSet @retainsCap]: Any = ??? /** Reach capabilities x* which appear as terms in @retains annotations are encoded * as `caps.reachCapability(x)`. When converted to CaptureRef types in capture sets diff --git a/tests/neg-custom-args/captures/capset-members.scala b/tests/neg-custom-args/captures/capset-members.scala index bcc92835b95f..2be4b4e32263 100644 --- a/tests/neg-custom-args/captures/capset-members.scala +++ b/tests/neg-custom-args/captures/capset-members.scala @@ -16,6 +16,6 @@ class Concrete3 extends Abstract[CapSet^{}]: type C = CapSet^{} | CapSet^{} def boom() = () -class Concrete4 extends Abstract[CapSet^{}]: - type C = Nothing // error +class Concrete4 extends Abstract[CapSet^]: + type C = CapSet // error def boom() = () diff --git a/tests/neg-custom-args/captures/i21868.scala b/tests/neg-custom-args/captures/i21868.scala index 929e770a21c6..4a617f98d07a 100644 --- a/tests/neg-custom-args/captures/i21868.scala +++ b/tests/neg-custom-args/captures/i21868.scala @@ -1,14 +1,13 @@ import caps.* trait AbstractWrong: - type C <: CapSet - def boom(): Unit^{C^} // error + type C <: CapSet + def f(): Unit^{C^} // error -trait Abstract: - type C <: CapSet^ - def boom(): Unit^{C^} - -class Concrete extends Abstract: - type C = Nothing - def boom() = () // error +trait Abstract1: + type C^ + def f(): Unit^{C^} +class Abstract2: + type C >: CapSet <: CapSet^ + def f(): Unit^{C^} \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i21868b.scala b/tests/neg-custom-args/captures/i21868b.scala new file mode 100644 index 000000000000..feb21894908f --- /dev/null +++ b/tests/neg-custom-args/captures/i21868b.scala @@ -0,0 +1,33 @@ +import caps.* + +class IO + +class File + +trait Abstract: + type C >: CapSet <: CapSet^ + def f(file: File^{C^}): Unit + +class Concrete1 extends Abstract: + type C = CapSet + def f(file: File) = () + +class Concrete2(io: IO^) extends Abstract: + type C = CapSet^{io} + def f(file: File^{io}) = () + +class Concrete3(io: IO^) extends Abstract: + type C = CapSet^{io} + def f(file: File) = () // error + +trait Abstract2(io: IO^): + type C >: CapSet <: CapSet^{io} + def f(file: File^{C^}): Unit + +class Concrete4(io: IO^) extends Abstract2(io): + type C = CapSet + def f(file: File) = () + +class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1): + type C = CapSet^{io2} // error + def f(file: File^{io2}) = () \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cc-poly-varargs.scala b/tests/pos-custom-args/captures/cc-poly-varargs.scala index 0d2478f37bdf..168cc4d0275f 100644 --- a/tests/pos-custom-args/captures/cc-poly-varargs.scala +++ b/tests/pos-custom-args/captures/cc-poly-varargs.scala @@ -13,13 +13,7 @@ def either[T1, T2, Cap^]( val left = src1.transformValuesWith(Left(_)) val right = src2.transformValuesWith(Right(_)) race[Either[T1, T2], Cap](left, right) - // An explcit type argument is required here because the second argument is - // inferred as `CapSet^{Cap^}` instead of `Cap`. + // Explcit type arguments are required here because the second argument + // is inferred as `CapSet^{Cap^}` instead of `Cap`. // Although `CapSet^{Cap^}` subsums `Cap` in terms of capture set, // `Cap` is not a subtype of `CapSet^{Cap^}` in terms of subtyping. - - - - - - From acde5c0e06615c5f082e8225f77ddcbd7101e289 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 26 Nov 2024 16:31:22 +0100 Subject: [PATCH 079/202] Update test --- .../neg-custom-args/captures/capset-members.scala | 6 +++++- .../captures/capture-parameters.scala | 9 +++++++++ tests/neg-custom-args/captures/i21868b.scala | 15 ++++++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tests/neg-custom-args/captures/capture-parameters.scala diff --git a/tests/neg-custom-args/captures/capset-members.scala b/tests/neg-custom-args/captures/capset-members.scala index 2be4b4e32263..7f975abbbc72 100644 --- a/tests/neg-custom-args/captures/capset-members.scala +++ b/tests/neg-custom-args/captures/capset-members.scala @@ -16,6 +16,10 @@ class Concrete3 extends Abstract[CapSet^{}]: type C = CapSet^{} | CapSet^{} def boom() = () -class Concrete4 extends Abstract[CapSet^]: +class Concrete4(a: AnyRef^) extends Abstract[CapSet^{a}]: type C = CapSet // error def boom() = () + +class Concrete5(a: AnyRef^) extends Abstract[CapSet^{a}]: + type C = CapSet^{} | CapSet^{a} + def boom() = () diff --git a/tests/neg-custom-args/captures/capture-parameters.scala b/tests/neg-custom-args/captures/capture-parameters.scala new file mode 100644 index 000000000000..d59305ae0cb8 --- /dev/null +++ b/tests/neg-custom-args/captures/capture-parameters.scala @@ -0,0 +1,9 @@ +import caps.* + +class C + +def test[X^, Y^, Z >: X <: Y](x: C^{X^}, y: C^{Y^}, z: C^{Z^}) = + val x2z: C^{Z^} = x + val z2y: C^{Y^} = z + val x2y: C^{Y^} = x // error + \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i21868b.scala b/tests/neg-custom-args/captures/i21868b.scala index feb21894908f..cbecb485472e 100644 --- a/tests/neg-custom-args/captures/i21868b.scala +++ b/tests/neg-custom-args/captures/i21868b.scala @@ -1,3 +1,4 @@ +import language.experimental.modularity import caps.* class IO @@ -20,7 +21,7 @@ class Concrete3(io: IO^) extends Abstract: type C = CapSet^{io} def f(file: File) = () // error -trait Abstract2(io: IO^): +trait Abstract2(tracked val io: IO^): type C >: CapSet <: CapSet^{io} def f(file: File^{C^}): Unit @@ -29,5 +30,17 @@ class Concrete4(io: IO^) extends Abstract2(io): def f(file: File) = () class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1): + type C = CapSet^{io2} // error + def f(file: File^{io2}) = () + +trait Abstract3[X^]: + type C >: CapSet <: X + def f(file: File^{C^}): Unit + +class Concrete6(io: IO^) extends Abstract3[CapSet^{io}]: + type C = CapSet + def f(file: File) = () + +class Concrete7(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]: type C = CapSet^{io2} // error def f(file: File^{io2}) = () \ No newline at end of file From ed7eed681f406df43a3f8522cf08eaead8cb4497 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 26 Nov 2024 22:26:15 +0100 Subject: [PATCH 080/202] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Oliver Bračevac --- tests/neg/cc-poly-2.scala | 2 +- tests/pos-custom-args/captures/cc-poly-varargs.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/neg/cc-poly-2.scala b/tests/neg/cc-poly-2.scala index 809fa8ae077c..c9249ba59437 100644 --- a/tests/neg/cc-poly-2.scala +++ b/tests/neg/cc-poly-2.scala @@ -10,7 +10,7 @@ object Test: def test(c1: C, c2: C) = val d: D^ = D() - // f[Nothing](d) // already rule out at typer + // f[Nothing](d) // already ruled out at typer f[CapSet^{c1}](d) // error val x = f(d) val _: D^{c1} = x // error diff --git a/tests/pos-custom-args/captures/cc-poly-varargs.scala b/tests/pos-custom-args/captures/cc-poly-varargs.scala index 168cc4d0275f..7f04ed987b28 100644 --- a/tests/pos-custom-args/captures/cc-poly-varargs.scala +++ b/tests/pos-custom-args/captures/cc-poly-varargs.scala @@ -13,7 +13,7 @@ def either[T1, T2, Cap^]( val left = src1.transformValuesWith(Left(_)) val right = src2.transformValuesWith(Right(_)) race[Either[T1, T2], Cap](left, right) - // Explcit type arguments are required here because the second argument + // Explicit type arguments are required here because the second argument // is inferred as `CapSet^{Cap^}` instead of `Cap`. - // Although `CapSet^{Cap^}` subsums `Cap` in terms of capture set, + // Although `CapSet^{Cap^}` subsumes `Cap` in terms of capture sets, // `Cap` is not a subtype of `CapSet^{Cap^}` in terms of subtyping. From bf8dcccf74a8e63e2d0fa723eea5687814f27a23 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 27 Nov 2024 05:42:03 +0100 Subject: [PATCH 081/202] Fix test --- .../tools/dotc/cc/CaptureAnnotation.scala | 1 + .../src/dotty/tools/dotc/cc/CaptureRef.scala | 12 +++++----- .../captures/capset-members.scala | 22 ++++++++++--------- tests/neg-custom-args/captures/i21868b.scala | 13 +++++++---- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala index e437a8ad5d5f..f0018cc93d7e 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala @@ -42,6 +42,7 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte case cr: TermRef => ref(cr) case cr: TermParamRef => untpd.Ident(cr.paramName).withType(cr) case cr: ThisType => This(cr.cls) + // TODO: Will crash if the type is an annotated type, for example `cap?` } val arg = repeated(elems, TypeTree(defn.AnyType)) New(symbol.typeRef, arg :: Nil) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala index ec3e1908e1b5..6bfb83e07921 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -94,15 +94,15 @@ trait CaptureRef extends TypeProxy, ValueType: myCaptureSetRunId = NoRunId /** x subsumes x - * x subsumes x.f - * x =:= y ==> x subsumes y + * x =:= y ==> x subsumes y + * x subsumes y ==> x subsumes y.f * x subsumes y ==> x* subsumes y, x subsumes y? * x subsumes y ==> x* subsumes y*, x? subsumes y? * x: x1.type /\ x1 subsumes y ==> x subsumes y - * X = CapSet^cx, exists rx in cx, rx subsumes y ==> X subsumes y - * Y = CapSet^cy, forall ry in cy, x subsumes ry ==> x subsumes Y - * X: CapSet^c1...CapSet^c2, (CapSet^c1) subsumes y ==> X subsumes y - * Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y + * X = CapSet^cx, exists rx in cx, rx subsumes y ==> X subsumes y + * Y = CapSet^cy, forall ry in cy, x subsumes ry ==> x subsumes Y + * X: CapSet^c1...CapSet^c2, (CapSet^c1) subsumes y ==> X subsumes y + * Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y * Contains[X, y] ==> X subsumes y * * TODO: Document cases with more comments. diff --git a/tests/neg-custom-args/captures/capset-members.scala b/tests/neg-custom-args/captures/capset-members.scala index 7f975abbbc72..984df756f48d 100644 --- a/tests/neg-custom-args/captures/capset-members.scala +++ b/tests/neg-custom-args/captures/capset-members.scala @@ -2,24 +2,26 @@ import caps.* trait Abstract[X^]: type C >: X <: CapSet^ - def boom(): Unit^{C^} + // Don't test the return type using Unit, because it is a pure type. + def boom(): AnyRef^{C^} class Concrete extends Abstract[CapSet^{}]: type C = CapSet^{} - def boom() = () + // TODO: Why do we get error without the return type here? + def boom(): AnyRef = new Object class Concrete2 extends Abstract[CapSet^{}]: - type C = CapSet^{} & CapSet^{} - def boom() = () + type C = CapSet^{} + def boom(): AnyRef^ = new Object // error class Concrete3 extends Abstract[CapSet^{}]: - type C = CapSet^{} | CapSet^{} - def boom() = () + def boom(): AnyRef = new Object class Concrete4(a: AnyRef^) extends Abstract[CapSet^{a}]: type C = CapSet // error - def boom() = () + def boom(): AnyRef^{a} = a // error -class Concrete5(a: AnyRef^) extends Abstract[CapSet^{a}]: - type C = CapSet^{} | CapSet^{a} - def boom() = () +class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: + // TODO: Crash with the type member + // type C = CapSet^{a} + def boom(): AnyRef^{b} = b // error diff --git a/tests/neg-custom-args/captures/i21868b.scala b/tests/neg-custom-args/captures/i21868b.scala index cbecb485472e..bf512c411c19 100644 --- a/tests/neg-custom-args/captures/i21868b.scala +++ b/tests/neg-custom-args/captures/i21868b.scala @@ -29,9 +29,10 @@ class Concrete4(io: IO^) extends Abstract2(io): type C = CapSet def f(file: File) = () -class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1): - type C = CapSet^{io2} // error - def f(file: File^{io2}) = () +// TODO: Should be an error +// class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1): +// type C = CapSet^{io2} +// def f(file: File^{io2}) = () trait Abstract3[X^]: type C >: CapSet <: X @@ -43,4 +44,8 @@ class Concrete6(io: IO^) extends Abstract3[CapSet^{io}]: class Concrete7(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]: type C = CapSet^{io2} // error - def f(file: File^{io2}) = () \ No newline at end of file + def f(file: File^{io2}) = () + +class Concrete8(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]: + type C = CapSet^{io1} + def f(file: File^{io2}) = () // error \ No newline at end of file From 1fb05b2a82df1553a194502473b4f9a3232af470 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 27 Nov 2024 13:59:08 +0100 Subject: [PATCH 082/202] Fix crash error --- compiler/src/dotty/tools/dotc/cc/CaptureRef.scala | 10 ++++------ tests/neg-custom-args/captures/capset-members.scala | 7 +++++-- tests/neg-custom-args/captures/i21868b.scala | 12 +++++++++--- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala index 6bfb83e07921..c4990b4413a8 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -146,9 +146,8 @@ trait CaptureRef extends TypeProxy, ValueType: y.info match case TypeBounds(_, hi: CaptureRef) => this.subsumes(hi) case _ => y.captureSetOfInfo.elems.forall(this.subsumes) - case AnnotatedType(parent, ann) - if ann.symbol.isRetains && parent.derivesFrom(defn.Caps_CapSet) => - ann.tree.toCaptureSet.elems.forall(this.subsumes) + case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) => + refs.elems.forall(this.subsumes) case _ => false || this.match case ReachCapability(x1) => x1.subsumes(y.stripReach) @@ -161,9 +160,8 @@ trait CaptureRef extends TypeProxy, ValueType: lo.subsumes(y) case _ => x.captureSetOfInfo.elems.exists(_.subsumes(y)) - case AnnotatedType(parent, ann) - if ann.symbol.isRetains && parent.derivesFrom(defn.Caps_CapSet) => - ann.tree.toCaptureSet.elems.exists(_.subsumes(y)) + case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) => + refs.elems.exists(_.subsumes(y)) case _ => false end subsumes diff --git a/tests/neg-custom-args/captures/capset-members.scala b/tests/neg-custom-args/captures/capset-members.scala index 984df756f48d..540216852a43 100644 --- a/tests/neg-custom-args/captures/capset-members.scala +++ b/tests/neg-custom-args/captures/capset-members.scala @@ -22,6 +22,9 @@ class Concrete4(a: AnyRef^) extends Abstract[CapSet^{a}]: def boom(): AnyRef^{a} = a // error class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: - // TODO: Crash with the type member - // type C = CapSet^{a} + type C = CapSet^{a} def boom(): AnyRef^{b} = b // error + +class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: + def boom(): AnyRef^{b} = b // error + \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i21868b.scala b/tests/neg-custom-args/captures/i21868b.scala index bf512c411c19..490b5964b05b 100644 --- a/tests/neg-custom-args/captures/i21868b.scala +++ b/tests/neg-custom-args/captures/i21868b.scala @@ -30,9 +30,15 @@ class Concrete4(io: IO^) extends Abstract2(io): def f(file: File) = () // TODO: Should be an error -// class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1): -// type C = CapSet^{io2} -// def f(file: File^{io2}) = () +class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1): + // Similar to Concrete8, this type member should have overriding error. + // Parent class is Abstract2 { val io = Concrete5.this.io1 } + // Abstract2.this.C >: CapSet <: CapSet^{Concrete5.this.io1} + // Concrete5.this.C = CapSet^{Concrete5.this.io2} + // CapSet^{Concrete5.this.io2} !<:< CapSet^{Concrete5.this.io1} + // Hence, Concrete5.this.C !<:< Abstract2.this.C + type C = CapSet^{io2} + def f(file: File^{io2}) = () trait Abstract3[X^]: type C >: CapSet <: X From 82c5512009912b9b6535d0e5cf816695220a46f0 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 28 Nov 2024 01:15:14 +0100 Subject: [PATCH 083/202] Fix overriding check --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 1 - compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 7 +++++-- tests/neg-custom-args/captures/i21868b.scala | 10 +--------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 9757e1af3cdb..c5c362dbe8dc 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -117,7 +117,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * The info of these symbols is made fluid. */ def isPreCC(sym: Symbol)(using Context): Boolean = - // TODO: check type members as well sym.isTerm && sym.maybeOwner.isClass && !sym.is(Module) && !sym.owner.is(CaptureChecked) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 0ec9458cac5c..e03a2e3764c3 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -21,6 +21,7 @@ import config.MigrationVersion import config.Printers.refcheck import reporting.* import Constants.Constant +import cc.stripCapturing object RefChecks { import tpd.* @@ -83,8 +84,10 @@ object RefChecks { * This one used to succeed only if forwarding parameters is on. * (Forwarding tends to hide problems by binding parameter names). */ + private def upwardsThisType(cls: Symbol)(using Context) = cls.info match { - case ClassInfo(_, _, _, _, tp: Type) if (tp ne cls.typeRef) && !cls.isOneOf(FinalOrModuleClass) => + case ClassInfo(_, _, _, _, tp: Type) if (tp.stripCapturing ne cls.typeRef) && !cls.isOneOf(FinalOrModuleClass) => + // println(i"upwardsThisType($cls) = ${cls.typeRef}, ne $tp") SkolemType(cls.appliedRef).withName(nme.this_) case _ => cls.thisType @@ -439,7 +442,7 @@ object RefChecks { val (mtp, otp) = if compareTypes then (memberTp(self), otherTp(self)) else (NoType, NoType) OverrideError(core, self, member, other, mtp, otp) - def compatTypes(memberTp: Type, otherTp: Type): Boolean = + def compatTypes(memberTp: Type, otherTp: Type): Boolean = // race.force(i"compatTypes $memberTp <:< $otherTp"): try isOverridingPair(member, memberTp, other, otherTp, fallBack = warnOnMigration( diff --git a/tests/neg-custom-args/captures/i21868b.scala b/tests/neg-custom-args/captures/i21868b.scala index 490b5964b05b..70f4e9c9d59c 100644 --- a/tests/neg-custom-args/captures/i21868b.scala +++ b/tests/neg-custom-args/captures/i21868b.scala @@ -29,15 +29,8 @@ class Concrete4(io: IO^) extends Abstract2(io): type C = CapSet def f(file: File) = () -// TODO: Should be an error class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1): - // Similar to Concrete8, this type member should have overriding error. - // Parent class is Abstract2 { val io = Concrete5.this.io1 } - // Abstract2.this.C >: CapSet <: CapSet^{Concrete5.this.io1} - // Concrete5.this.C = CapSet^{Concrete5.this.io2} - // CapSet^{Concrete5.this.io2} !<:< CapSet^{Concrete5.this.io1} - // Hence, Concrete5.this.C !<:< Abstract2.this.C - type C = CapSet^{io2} + type C = CapSet^{io2} // error def f(file: File^{io2}) = () trait Abstract3[X^]: @@ -53,5 +46,4 @@ class Concrete7(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]: def f(file: File^{io2}) = () class Concrete8(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]: - type C = CapSet^{io1} def f(file: File^{io2}) = () // error \ No newline at end of file From 0750d5c4433ca0dba5b508ef46723c063da6b2c1 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 29 Nov 2024 12:49:36 +0100 Subject: [PATCH 084/202] Update Stepper in the library --- scala2-library-cc/src/scala/collection/Stepper.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scala2-library-cc/src/scala/collection/Stepper.scala b/scala2-library-cc/src/scala/collection/Stepper.scala index 2f8abee4cffb..1723a110ad8a 100644 --- a/scala2-library-cc/src/scala/collection/Stepper.scala +++ b/scala2-library-cc/src/scala/collection/Stepper.scala @@ -53,7 +53,7 @@ trait Stepper[@specialized(Double, Int, Long) +A] { * * See method `trySplit` in [[java.util.Spliterator]]. */ - def trySplit(): Stepper[A] + def trySplit(): Stepper[A]^{this} /** Returns an estimate of the number of elements of this Stepper, or [[Long.MaxValue]]. See * method `estimateSize` in [[java.util.Spliterator]]. @@ -71,7 +71,7 @@ trait Stepper[@specialized(Double, Int, Long) +A] { * a [[java.util.Spliterator.OfInt]] (which is a `Spliterator[Integer]`) in the subclass [[IntStepper]] * (which is a `Stepper[Int]`). */ - def spliterator[B >: A]: Spliterator[_] + def spliterator[B >: A]: Spliterator[_]^{this} /** Returns a Java [[java.util.Iterator]] corresponding to this Stepper. * @@ -79,7 +79,7 @@ trait Stepper[@specialized(Double, Int, Long) +A] { * a [[java.util.PrimitiveIterator.OfInt]] (which is a `Iterator[Integer]`) in the subclass * [[IntStepper]] (which is a `Stepper[Int]`). */ - def javaIterator[B >: A]: JIterator[_] + def javaIterator[B >: A]: JIterator[_]^{this} /** Returns an [[Iterator]] corresponding to this Stepper. Note that Iterators corresponding to * primitive Steppers box the elements. From 1ed08dee4a26d6ae1806a6d5368ef0160bbe3a8d Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 29 Nov 2024 15:31:18 +0100 Subject: [PATCH 085/202] Clean comments --- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index e03a2e3764c3..7e53b38b5f98 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -84,10 +84,8 @@ object RefChecks { * This one used to succeed only if forwarding parameters is on. * (Forwarding tends to hide problems by binding parameter names). */ - private def upwardsThisType(cls: Symbol)(using Context) = cls.info match { case ClassInfo(_, _, _, _, tp: Type) if (tp.stripCapturing ne cls.typeRef) && !cls.isOneOf(FinalOrModuleClass) => - // println(i"upwardsThisType($cls) = ${cls.typeRef}, ne $tp") SkolemType(cls.appliedRef).withName(nme.this_) case _ => cls.thisType @@ -442,7 +440,7 @@ object RefChecks { val (mtp, otp) = if compareTypes then (memberTp(self), otherTp(self)) else (NoType, NoType) OverrideError(core, self, member, other, mtp, otp) - def compatTypes(memberTp: Type, otherTp: Type): Boolean = // race.force(i"compatTypes $memberTp <:< $otherTp"): + def compatTypes(memberTp: Type, otherTp: Type): Boolean = try isOverridingPair(member, memberTp, other, otherTp, fallBack = warnOnMigration( From c1a083547e0b0cf80f20561eb6216cb0168e891f Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sat, 30 Nov 2024 01:42:10 +0100 Subject: [PATCH 086/202] Drop syntax sugar for capture set member --- compiler/src/dotty/tools/dotc/cc/CaptureRef.scala | 3 +++ compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 7 ++----- tests/neg-custom-args/captures/capture-poly.scala | 2 +- tests/neg-custom-args/captures/i21868.scala | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala index c4990b4413a8..9bda9102cbb8 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -143,6 +143,9 @@ trait CaptureRef extends TypeProxy, ValueType: || viaInfo(y.info)(subsumingRefs(this, _)) case MaybeCapability(y1) => this.stripMaybe.subsumes(y1) case y: TypeRef if y.derivesFrom(defn.Caps_CapSet) => + // The upper and lower bounds don't have to be in the form of `CapSet^{...}`. + // They can be other capture set variables, which are bounded by `CapSet`, + // like `def test[X^, Y^, Z >: X <: Y]`. y.info match case TypeBounds(_, hi: CaptureRef) => this.subsumes(hi) case _ => y.captureSetOfInfo.elems.forall(this.subsumes) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index a67bb72333e9..89f88faebc17 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -4057,11 +4057,8 @@ object Parsers { || sourceVersion.isAtLeast(`3.6`) && in.isColon => makeTypeDef(typeAndCtxBounds(tname)) case _ => - if in.isIdent(nme.UPARROW) && Feature.ccEnabled then - makeTypeDef(typeAndCtxBounds(tname)) - else - syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token)) - return EmptyTree // return to avoid setting the span to EmptyTree + syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token)) + return EmptyTree // return to avoid setting the span to EmptyTree } } } diff --git a/tests/neg-custom-args/captures/capture-poly.scala b/tests/neg-custom-args/captures/capture-poly.scala index 0a0d773a3f64..88989b418726 100644 --- a/tests/neg-custom-args/captures/capture-poly.scala +++ b/tests/neg-custom-args/captures/capture-poly.scala @@ -3,7 +3,7 @@ import caps.* trait Foo extends Capability trait CaptureSet: - type C^ + type C >: CapSet <: CapSet^ def capturePoly[C^](a: Foo^{C^}): Foo^{C^} = a def capturePoly2(c: CaptureSet)(a: Foo^{c.C^}): Foo^{c.C^} = a diff --git a/tests/neg-custom-args/captures/i21868.scala b/tests/neg-custom-args/captures/i21868.scala index 4a617f98d07a..876b68ac90a4 100644 --- a/tests/neg-custom-args/captures/i21868.scala +++ b/tests/neg-custom-args/captures/i21868.scala @@ -5,9 +5,9 @@ trait AbstractWrong: def f(): Unit^{C^} // error trait Abstract1: - type C^ + type C >: CapSet <: CapSet^ def f(): Unit^{C^} -class Abstract2: - type C >: CapSet <: CapSet^ - def f(): Unit^{C^} \ No newline at end of file +// class Abstract2: +// type C^ +// def f(): Unit^{C^} \ No newline at end of file From 6866bd11544fd29ac3c12117184346134b54ca63 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sat, 30 Nov 2024 01:45:07 +0100 Subject: [PATCH 087/202] Update comment --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index b91c7a712f4c..2acfc4cf86e3 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -527,8 +527,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def makeCapsOf(tp: RefTree)(using Context): Tree = TypeApply(Select(scalaDot(nme.caps), nme.capsOf), tp :: Nil) - // `type C^` and `[C^]` becomes: - // `type C >: CapSet <: CapSet^{cap}` and `[C >: CapSet <: CapSet^{cap}]` + // Capture set variable `[C^]` becomes: `[C >: CapSet <: CapSet^{cap}]` def makeCapsBound()(using Context): TypeBoundsTree = TypeBoundsTree( Select(scalaDot(nme.caps), tpnme.CapSet), From 300043eae504350784b32e9f200b89da74480fa6 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 28 Nov 2024 12:47:28 +0100 Subject: [PATCH 088/202] Restore execution of parallel backend tests --- compiler/test/dotty/tools/dotc/CompilationTests.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 9f72db6fc390..2c42204a4b4d 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -263,7 +263,6 @@ class CompilationTests { } // parallel backend tests - @Ignore("Temporarily disabled due to frequent timeouts") @Test def parallelBackend: Unit = { given TestGroup = TestGroup("parallelBackend") val parallelism = Runtime.getRuntime().availableProcessors().min(16) From 790a50ae595aedc990b98fa8ce4e695e492e2bd0 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Sat, 30 Nov 2024 14:42:10 +0100 Subject: [PATCH 089/202] chore: add workflow to trigger cc --- .github/workflows/test-cc.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/test-cc.yml diff --git a/.github/workflows/test-cc.yml b/.github/workflows/test-cc.yml new file mode 100644 index 000000000000..39d21b5b952f --- /dev/null +++ b/.github/workflows/test-cc.yml @@ -0,0 +1,26 @@ +name: Scala 3 with Capture Checking + +on: + push: + branches: + - main + pull_request: + paths: + - .github/workflows/test-cc.yml + - scala2-library-cc/** + - scala2-library-cc-tasty/** + - compiler/src/dotty/tools/dotc/cc/** + +env: + DOTTY_CI_RUN: true + DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + +jobs: + suite-with-stdlib-cc: + name: Test Suite with the CC Standard Library + runs-on: ubuntu-latest + steps: + - name: Git Checkout + uses: actions/checkout@v4 + - name: Test with Scala 2 library with CC TASTy + run: ./project/scripts/sbt ";set ThisBuild/Build.scala2Library := Build.Scala2LibraryCCTasty ;scala3-bootstrapped/test" From f47d103eb9dc3d679441d5efad6b3b08129ccca5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:36:03 +0000 Subject: [PATCH 090/202] Bump webrick from 1.8.2 to 1.9.1 in /docs/_spec Bumps [webrick](https://github.com/ruby/webrick) from 1.8.2 to 1.9.1. - [Release notes](https://github.com/ruby/webrick/releases) - [Commits](https://github.com/ruby/webrick/compare/v1.8.2...v1.9.1) --- updated-dependencies: - dependency-name: webrick dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/_spec/Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_spec/Gemfile.lock b/docs/_spec/Gemfile.lock index c703a87bf993..38a790c676eb 100644 --- a/docs/_spec/Gemfile.lock +++ b/docs/_spec/Gemfile.lock @@ -41,7 +41,7 @@ GEM sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - webrick (1.8.2) + webrick (1.9.1) PLATFORMS ruby From 0650ed7880710d86e4a1abb71307849b7dc50180 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:56:21 +0000 Subject: [PATCH 091/202] Bump vedantmgoyal9/winget-releaser Bumps [vedantmgoyal9/winget-releaser](https://github.com/vedantmgoyal9/winget-releaser) from b87a066d9e624db1394edcd947f8c4e5a7e30cd7 to 4ffc7888bffd451b357355dc214d43bb9f23917e. - [Release notes](https://github.com/vedantmgoyal9/winget-releaser/releases) - [Commits](https://github.com/vedantmgoyal9/winget-releaser/compare/b87a066d9e624db1394edcd947f8c4e5a7e30cd7...4ffc7888bffd451b357355dc214d43bb9f23917e) --- updated-dependencies: - dependency-name: vedantmgoyal9/winget-releaser dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/publish-winget.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-winget.yml b/.github/workflows/publish-winget.yml index 03ebc5d0fa7d..ba5877e63ec7 100644 --- a/.github/workflows/publish-winget.yml +++ b/.github/workflows/publish-winget.yml @@ -26,7 +26,7 @@ jobs: publish: runs-on: windows-latest steps: - - uses: vedantmgoyal9/winget-releaser@b87a066d9e624db1394edcd947f8c4e5a7e30cd7 + - uses: vedantmgoyal9/winget-releaser@4ffc7888bffd451b357355dc214d43bb9f23917e with: identifier : Scala.Scala.3 version : ${{ inputs.version }} From dcc66c9ee24f057dcc1f87247942f64fb7525269 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Fri, 12 Jul 2024 15:36:56 +0200 Subject: [PATCH 092/202] Extend compiletime.testing.typechecks with certain transform phases --- .../dotty/tools/dotc/CompilationUnit.scala | 8 ++ .../src/dotty/tools/dotc/core/Phases.scala | 3 + .../dotty/tools/dotc/inlines/Inlines.scala | 78 +++++++++++++++++-- .../test/dotc/run-test-pickling.blacklist | 1 + tests/run/i18150.check | 2 + tests/run/i18150.scala | 31 ++++++++ 6 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 tests/run/i18150.check create mode 100644 tests/run/i18150.scala diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 0975c94e916a..0d755797d026 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -155,6 +155,14 @@ object CompilationUnit { unit1 } + /** Create a compilation unit corresponding to an in-memory String. + * Used for `compiletime.testing.typeChecks`. + */ + def apply(name: String, source: String)(using Context): CompilationUnit = { + val src = SourceFile.virtual(name = name, content = source, maybeIncomplete = false) + new CompilationUnit(src, null) + } + /** Create a compilation unit corresponding to `source`. * If `mustExist` is true, this will fail if `source` does not exist. */ diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 015cf6fc0f2c..e3351628e43e 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -222,6 +222,7 @@ object Phases { private var mySbtExtractDependenciesPhase: Phase = uninitialized private var mySbtExtractAPIPhase: Phase = uninitialized private var myPicklerPhase: Phase = uninitialized + private var mySetRootTreePhase: Phase = uninitialized private var myInliningPhase: Phase = uninitialized private var myStagingPhase: Phase = uninitialized private var mySplicingPhase: Phase = uninitialized @@ -249,6 +250,7 @@ object Phases { final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase final def sbtExtractAPIPhase: Phase = mySbtExtractAPIPhase final def picklerPhase: Phase = myPicklerPhase + final def setRootTreePhase: Phase = mySetRootTreePhase final def inliningPhase: Phase = myInliningPhase final def stagingPhase: Phase = myStagingPhase final def splicingPhase: Phase = mySplicingPhase @@ -278,6 +280,7 @@ object Phases { myPostTyperPhase = phaseOfClass(classOf[PostTyper]) mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies]) mySbtExtractAPIPhase = phaseOfClass(classOf[sbt.ExtractAPI]) + mySetRootTreePhase = phaseOfClass(classOf[SetRootTree]) myPicklerPhase = phaseOfClass(classOf[Pickler]) myInliningPhase = phaseOfClass(classOf[Inlining]) myStagingPhase = phaseOfClass(classOf[Staging]) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index aeecd9c376e3..1eca206d1455 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -12,6 +12,9 @@ import SymDenotations.SymDenotation import config.Printers.inlining import ErrorReporting.errorTree import dotty.tools.dotc.util.{SourceFile, SourcePosition, SrcPos} +import dotty.tools.dotc.transform.* +import dotty.tools.dotc.transform.MegaPhase +import dotty.tools.dotc.transform.MegaPhase.MiniPhase import parsing.Parsers.Parser import transform.{PostTyper, Inlining, CrossVersionChecks} import staging.StagingLevel @@ -19,6 +22,7 @@ import staging.StagingLevel import collection.mutable import reporting.{NotConstant, trace} import util.Spans.Span +import dotty.tools.dotc.core.Periods.PhaseId /** Support for querying inlineable methods and for inlining calls to such methods */ object Inlines: @@ -345,10 +349,58 @@ object Inlines: // We should not be rewriting tested strings val noRewriteSettings = ctx.settings.rewrite.updateIn(ctx.settingsState.reinitializedCopy(), None) + class MegaPhaseWithCustomPhaseId(miniPhases: Array[MiniPhase], startId: PhaseId, endId: PhaseId) + extends MegaPhase(miniPhases) { + override def start: Int = startId + override def end: Int = endId + } + ConstFold(underlyingCodeArg).tpe.widenTermRefExpr match { case ConstantType(Constant(code: String)) => - val source2 = SourceFile.virtual("tasty-reflect", code) - inContext(ctx.fresh.setSettings(noRewriteSettings).setNewTyperState().setTyper(new Typer(ctx.nestingLevel + 1)).setSource(source2)) { + val unitName = "tasty-reflect" + val source2 = SourceFile.virtual(unitName, code) + // We need a dummy owner, as the actual one does not have a computed denotation yet, + // but might be inspected in a transform phase, leading to cyclic errors + val dummyOwner = newSymbol(ctx.owner, "$dummySymbol$".toTermName, Private, defn.AnyType, NoSymbol) + val newContext = + ctx.fresh + .setSettings(noRewriteSettings) + .setNewTyperState() + .setTyper(new Typer(ctx.nestingLevel + 1)) + .setSource(source2) + .withOwner(dummyOwner) + + inContext(newContext) { + // Let's reconstruct necessary transform MegaPhases, without anything + // that could cause problems here (like `CrossVersionChecks`). + // The individiual lists here should line up with Compiler.scala, i.e + // separate chunks there should also be kept separate here. + // For now we create a single MegaPhase, since there does not seem to + // be any important checks later (e.g. ForwardDepChecks could be applicable here, + // but the equivalent is also not run in the scala 2's `ctx.typechecks`, + // so let's leave it out for now). + val transformPhases: List[List[(Class[?], () => MiniPhase)]] = List( + List( + (classOf[InlineVals], () => new InlineVals), + (classOf[ElimRepeated], () => new ElimRepeated), + (classOf[RefChecks], () => new RefChecks), + ), + ) + + val mergedTransformPhases = + transformPhases.flatMap( (megaPhaseList: List[(Class[?], () => MiniPhase)]) => + val (newMegaPhasePhases, phaseIds) = + megaPhaseList + .flatMap { filteredPhase => + ctx.base.phases.find(phase => filteredPhase._1.isInstance(phase)).map { a => + (filteredPhase._2(), a.id) + } + } + .unzip + if newMegaPhasePhases.isEmpty then None + else Some(MegaPhaseWithCustomPhaseId(newMegaPhasePhases.toArray, phaseIds.head, phaseIds.last)) + ) + val tree2 = new Parser(source2).block() if ctx.reporter.allErrors.nonEmpty then ctx.reporter.allErrors.map((ErrorKind.Parser, _)) @@ -357,10 +409,24 @@ object Inlines: ctx.base.postTyperPhase match case postTyper: PostTyper if ctx.reporter.allErrors.isEmpty => val tree4 = atPhase(postTyper) { postTyper.newTransformer.transform(tree3) } - ctx.base.inliningPhase match - case inlining: Inlining if ctx.reporter.allErrors.isEmpty => - atPhase(inlining) { inlining.newTransformer.transform(tree4) } - case _ => + ctx.base.setRootTreePhase match + case setRootTree => + val tree5 = + val compilationUnit = CompilationUnit(unitName, code) + compilationUnit.tpdTree = tree4 + compilationUnit.untpdTree = tree2 + var units = List(compilationUnit) + atPhase(setRootTree)(setRootTree.runOn(units).head.tpdTree) + + ctx.base.inliningPhase match + case inlining: Inlining if ctx.reporter.allErrors.isEmpty => + val tree6 = atPhase(inlining) { inlining.newTransformer.transform(tree5) } + if mergedTransformPhases.nonEmpty then + var transformTree = tree6 + for (phase <- mergedTransformPhases if ctx.reporter.allErrors.isEmpty) { + transformTree = atPhase(phase.end + 1)(phase.transformUnit(transformTree)) + } + case _ => case _ => ctx.reporter.allErrors.map((ErrorKind.Typer, _)) } diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index 31304e061bc7..c880a4b78f23 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -48,4 +48,5 @@ named-tuples-strawman-2.scala # typecheckErrors method unpickling typeCheckErrors.scala +i18150.scala diff --git a/tests/run/i18150.check b/tests/run/i18150.check new file mode 100644 index 000000000000..ea0b653719cd --- /dev/null +++ b/tests/run/i18150.check @@ -0,0 +1,2 @@ +List(Error(illegal inheritance: self type Banana of class Banana does not conform to self type Apple +of parent trait RecursiveSelfTypeEntity,class Banana extends RecursiveSelfTypeEntity[Apple]:,6,Typer)) diff --git a/tests/run/i18150.scala b/tests/run/i18150.scala new file mode 100644 index 000000000000..2a94f7d29549 --- /dev/null +++ b/tests/run/i18150.scala @@ -0,0 +1,31 @@ +object Test: + def main(args: Array[String]): Unit = + val result = + scala.compiletime.testing.typeCheckErrors( + "trait RecursiveSelfTypeEntity[E <: RecursiveSelfTypeEntity[E]]: \n" + + " self: E => \n" + + " def create(): E \n" + + " def read(id: Long): Option[E] \n" + + " def update(f: E => E): E \n" + + " def delete(id: Long): Unit \n" + + "\n" + + "class Apple extends RecursiveSelfTypeEntity[Apple]: \n" + + " override def create(): Apple = ??? \n" + + " override def read(id: Long): Option[Apple] = ??? \n" + + " override def update(f: Apple => Apple): Apple = ??? \n" + + " override def delete(id: Long): Unit = ??? \n" + + " \n" + + "class Orange extends RecursiveSelfTypeEntity[Orange]: \n" + + " override def create(): Orange = ??? \n" + + " override def read(id: Long): Option[Orange] = ??? \n" + + " override def update(f: Orange => Orange): Orange = ??? \n" + + " override def delete(id: Long): Unit = ??? \n" + + " \n" + + "class Banana extends RecursiveSelfTypeEntity[Apple]: \n" + + " override def create(): Apple = ??? \n" + + " override def read(id: Long): Option[Apple] = ??? \n" + + " override def update(f: Apple => Apple): Apple = ??? \n" + + " override def delete(id: Long): Unit = ???\n" + ) + assert(!result.isEmpty, "Should fail type check, but it didn't.") + println(result) From 25d9611290aded3a2d695c39959080096b99d842 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Mon, 2 Dec 2024 16:52:54 +0100 Subject: [PATCH 093/202] Apply review comments --- .../dotty/tools/dotc/inlines/Inlines.scala | 69 +++++++++---------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 1eca206d1455..85b1234461c8 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -355,6 +355,36 @@ object Inlines: override def end: Int = endId } + // Let's reconstruct necessary transform MegaPhases, without anything + // that could cause problems here (like `CrossVersionChecks`). + // The individiual lists here should line up with Compiler.scala, i.e + // separate chunks there should also be kept separate here. + // For now we create a single MegaPhase, since there does not seem to + // be any important checks later (e.g. ForwardDepChecks could be applicable here, + // but the equivalent is also not run in the scala 2's `ctx.typechecks`, + // so let's leave it out for now). + lazy val reconstructedTransformPhases = + val transformPhases: List[List[(Class[?], () => MiniPhase)]] = List( + List( + (classOf[InlineVals], () => new InlineVals), + (classOf[ElimRepeated], () => new ElimRepeated), + (classOf[RefChecks], () => new RefChecks), + ), + ) + + transformPhases.flatMap( (megaPhaseList: List[(Class[?], () => MiniPhase)]) => + val (newMegaPhasePhases, phaseIds) = + megaPhaseList.flatMap { + case (filteredPhaseClass, miniphaseConstructor) => + ctx.base.phases + .find(phase => filteredPhaseClass.isInstance(phase)) + .map(phase => (miniphaseConstructor(), phase.id)) + } + .unzip + if newMegaPhasePhases.isEmpty then None + else Some(MegaPhaseWithCustomPhaseId(newMegaPhasePhases.toArray, phaseIds.head, phaseIds.last)) + ) + ConstFold(underlyingCodeArg).tpe.widenTermRefExpr match { case ConstantType(Constant(code: String)) => val unitName = "tasty-reflect" @@ -371,36 +401,6 @@ object Inlines: .withOwner(dummyOwner) inContext(newContext) { - // Let's reconstruct necessary transform MegaPhases, without anything - // that could cause problems here (like `CrossVersionChecks`). - // The individiual lists here should line up with Compiler.scala, i.e - // separate chunks there should also be kept separate here. - // For now we create a single MegaPhase, since there does not seem to - // be any important checks later (e.g. ForwardDepChecks could be applicable here, - // but the equivalent is also not run in the scala 2's `ctx.typechecks`, - // so let's leave it out for now). - val transformPhases: List[List[(Class[?], () => MiniPhase)]] = List( - List( - (classOf[InlineVals], () => new InlineVals), - (classOf[ElimRepeated], () => new ElimRepeated), - (classOf[RefChecks], () => new RefChecks), - ), - ) - - val mergedTransformPhases = - transformPhases.flatMap( (megaPhaseList: List[(Class[?], () => MiniPhase)]) => - val (newMegaPhasePhases, phaseIds) = - megaPhaseList - .flatMap { filteredPhase => - ctx.base.phases.find(phase => filteredPhase._1.isInstance(phase)).map { a => - (filteredPhase._2(), a.id) - } - } - .unzip - if newMegaPhasePhases.isEmpty then None - else Some(MegaPhaseWithCustomPhaseId(newMegaPhasePhases.toArray, phaseIds.head, phaseIds.last)) - ) - val tree2 = new Parser(source2).block() if ctx.reporter.allErrors.nonEmpty then ctx.reporter.allErrors.map((ErrorKind.Parser, _)) @@ -417,15 +417,14 @@ object Inlines: compilationUnit.untpdTree = tree2 var units = List(compilationUnit) atPhase(setRootTree)(setRootTree.runOn(units).head.tpdTree) - ctx.base.inliningPhase match case inlining: Inlining if ctx.reporter.allErrors.isEmpty => val tree6 = atPhase(inlining) { inlining.newTransformer.transform(tree5) } - if mergedTransformPhases.nonEmpty then + if ctx.reporter.allErrors.isEmpty && reconstructedTransformPhases.nonEmpty then var transformTree = tree6 - for (phase <- mergedTransformPhases if ctx.reporter.allErrors.isEmpty) { - transformTree = atPhase(phase.end + 1)(phase.transformUnit(transformTree)) - } + for phase <- reconstructedTransformPhases do + if ctx.reporter.allErrors.isEmpty then + transformTree = atPhase(phase.end + 1)(phase.transformUnit(transformTree)) case _ => case _ => ctx.reporter.allErrors.map((ErrorKind.Typer, _)) From 2d2b2ad4b27db3676389909c026791ea5f09d812 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 2 Dec 2024 16:16:47 +0000 Subject: [PATCH 094/202] Without boxing, just rerun that a named import is behind a wildcard one --- .../src/dotty/tools/dotc/typer/Typer.scala | 87 ++++++++++--------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c133d70ff9c7..7cd8e3496cd3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -241,40 +241,44 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer !owner.isEmptyPackage || ctx.owner.enclosingPackageClass.isEmptyPackage } - import BindingPrec.* - type Result = (Type, BindingPrec) - val NoResult = (NoType, NothingBound) - /** Find the denotation of enclosing `name` in given context `ctx`. - * @param prevResult A type that was found in a more deeply nested scope, - * and its precedence, or NoResult if nothing was found yet. + * @param previous A denotation that was found in a more deeply nested scope, + * or else `NoDenotation` if nothing was found yet. + * @param prevPrec The binding precedence of the previous denotation, + * or else `nothingBound` if nothing was found yet. * @param prevCtx The context of the previous denotation, * or else `NoContext` if nothing was found yet. */ - def findRefRecur(prevResult: Result, prevCtx: Context)(using Context): Result = { - val (previous, prevPrec) = prevResult + def findRefRecur(previous: Type, prevPrec: BindingPrec, prevCtx: Context)(using Context): Type = { + import BindingPrec.* /** Check that any previously found result from an inner context * does properly shadow the new one from an outer context. - * @param newResult The newly found type and its precedence. + * @param found The newly found result + * @param newPrec Its precedence * @param scala2pkg Special mode where we check members of the same package, but defined * in different compilation units under Scala2. If set, and the * previous and new contexts do not have the same scope, we select * the previous (inner) definition. This models what scalac does. */ - def checkNewOrShadowed(newResult: Result, scala2pkg: Boolean = false)(using Context): Result = - val (found, newPrec) = newResult + def checkNewOrShadowed(found: Type, newPrec: BindingPrec, scala2pkg: Boolean = false)(using Context): Type = if !previous.exists || TypeComparer.isSameRef(previous, found) then - newResult + found else if (prevCtx.scope eq ctx.scope) && newPrec.beats(prevPrec) then // special cases: definitions beat imports, and named imports beat // wildcard imports, provided both are in contexts with same scope - newResult + found + else if newPrec == WildImport && ctx.outersIterator.exists: ctx => + ctx.isImportContext && namedImportRef(ctx.importInfo.uncheckedNN).exists + then + // Don't let two ambiguous wildcard imports rule over + // a winning named import. See pos/i18529. + found else if !scala2pkg && !previous.isError && !found.isError then fail(AmbiguousReference(name, newPrec, prevPrec, prevCtx, isExtension = previous.termSymbol.is(ExtensionMethod) && found.termSymbol.is(ExtensionMethod))) - prevResult + previous /** Assemble and check alternatives to an imported reference. This implies: * - If we expand an extension method (i.e. altImports != null), @@ -287,13 +291,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer * shadowed. This order of checking is necessary since an outer package-level * definition might trump two conflicting inner imports, so no error should be * issued in that case. See i7876.scala. - * @param prevResult the previously found reference (which is an import), and - * the precedence of the reference (either NamedImport or WildImport) + * @param previous the previously found reference (which is an import) + * @param prevPrec the precedence of the reference (either NamedImport or WildImport) * @param prevCtx the context in which the reference was found * @param using_Context the outer context of `precCtx` */ - def checkImportAlternatives(prevResult: Result, prevCtx: Context)(using Context): Result = - val (previous, prevPrec) = prevResult + def checkImportAlternatives(previous: Type, prevPrec: BindingPrec, prevCtx: Context)(using Context): Type = def addAltImport(altImp: TermRef) = if !TypeComparer.isSameRef(previous, altImp) @@ -308,20 +311,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if prevPrec == WildImport then // Discard all previously found references and continue with `altImp` altImports.clear() - checkImportAlternatives((altImp, NamedImport), ctx)(using ctx.outer) + checkImportAlternatives(altImp, NamedImport, ctx)(using ctx.outer) else addAltImport(altImp) - checkImportAlternatives(prevResult, prevCtx)(using ctx.outer) + checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer) case _ => if prevPrec == WildImport then wildImportRef(curImport) match case altImp: TermRef => addAltImport(altImp) case _ => - checkImportAlternatives(prevResult, prevCtx)(using ctx.outer) + checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer) else - val foundResult = findRefRecur(prevResult, prevCtx) - if foundResult._1 eq previous then checkNewOrShadowed(foundResult)(using prevCtx) - else foundResult + val found = findRefRecur(previous, prevPrec, prevCtx) + if found eq previous then checkNewOrShadowed(found, prevPrec)(using prevCtx) + else found end checkImportAlternatives def selection(imp: ImportInfo, name: Name, checkBounds: Boolean): Type = @@ -411,10 +414,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer !noImports && (prevPrec.ordinal < prec.ordinal || prevPrec == prec && (prevCtx.scope eq ctx.scope)) - @tailrec def loop(lastCtx: Context)(using Context): Result = - if (ctx.scope eq EmptyScope) prevResult + @tailrec def loop(lastCtx: Context)(using Context): Type = + if (ctx.scope eq EmptyScope) previous else { - var result: Result = NoResult + var result: Type = NoType val curOwner = ctx.owner /** Is curOwner a package object that should be skipped? @@ -513,7 +516,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer effectiveOwner.thisType.select(name, defDenot).makePackageObjPrefixExplicit } if !curOwner.is(Package) || isDefinedInCurrentUnit(defDenot) then - result = checkNewOrShadowed((found, Definition)) // no need to go further out, we found highest prec entry + result = checkNewOrShadowed(found, Definition) // no need to go further out, we found highest prec entry found match case found: NamedType if curOwner.isClass && isInherited(found.denot) && !ctx.compilationUnit.isJava => @@ -521,28 +524,29 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => else if migrateTo3 && !foundUnderScala2.exists then - foundUnderScala2 = checkNewOrShadowed((found, Definition), scala2pkg = true)._1 + foundUnderScala2 = checkNewOrShadowed(found, Definition, scala2pkg = true) if (defDenot.symbol.is(Package)) - result = checkNewOrShadowed((previous orElse found, PackageClause)) + result = checkNewOrShadowed(previous orElse found, PackageClause) else if (prevPrec.ordinal < PackageClause.ordinal) - result = findRefRecur((found, PackageClause), ctx)(using ctx.outer) + result = findRefRecur(found, PackageClause, ctx)(using ctx.outer) } - if result._1.exists then result + if result.exists then result else { // find import val outer = ctx.outer val curImport = ctx.importInfo + def updateUnimported() = + if (curImport.nn.unimported ne NoSymbol) unimported += curImport.nn.unimported if (curOwner.is(Package) && curImport != null && curImport.isRootImport && previous.exists) - prevResult // no more conflicts possible in this case - else if (isPossibleImport(NamedImport) && curImport != null && (curImport ne outer.importInfo)) { - def updateUnimported() = if curImport.unimported ne NoSymbol then unimported += curImport.unimported - val namedImp = namedImportRef(curImport) + previous // no more conflicts possible in this case + else if (isPossibleImport(NamedImport) && (curImport nen outer.importInfo)) { + val namedImp = namedImportRef(curImport.uncheckedNN) if (namedImp.exists) - checkImportAlternatives((namedImp, NamedImport), ctx)(using outer) - else if (isPossibleImport(WildImport) && !curImport.importSym.isCompleting) { - val wildImp = wildImportRef(curImport) + checkImportAlternatives(namedImp, NamedImport, ctx)(using outer) + else if (isPossibleImport(WildImport) && !curImport.nn.importSym.isCompleting) { + val wildImp = wildImportRef(curImport.uncheckedNN) if (wildImp.exists) - checkImportAlternatives((wildImp, WildImport), ctx)(using outer) + checkImportAlternatives(wildImp, WildImport, ctx)(using outer) else { updateUnimported() loop(ctx)(using outer) @@ -561,8 +565,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer loop(NoContext) } - val (foundRef, foundPrec) = findRefRecur(NoResult, NoContext) - foundRef + findRefRecur(NoType, BindingPrec.NothingBound, NoContext) } /** If `tree`'s type is a `TermRef` identified by flow typing to be non-null, then From 44be1f976e9d37ef18a5c776b5abc23f8bd773b7 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Tue, 3 Dec 2024 14:45:00 +0100 Subject: [PATCH 095/202] chore: trigger cc tests when changing one of the cc tests --- .github/workflows/test-cc.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test-cc.yml b/.github/workflows/test-cc.yml index 39d21b5b952f..68113163ff93 100644 --- a/.github/workflows/test-cc.yml +++ b/.github/workflows/test-cc.yml @@ -10,6 +10,10 @@ on: - scala2-library-cc/** - scala2-library-cc-tasty/** - compiler/src/dotty/tools/dotc/cc/** + ## Capture Checking Tests + - tests/pos-custom-args/captures/** + - tests/run-custom-args/captures/** + - tests/neg-custom-args/captures/** env: DOTTY_CI_RUN: true From 67024a914784d5a912ce1ac2715cfd332eab7f35 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 3 Dec 2024 15:28:09 +0100 Subject: [PATCH 096/202] Only trust the type application part for case class unapplies --- .../tools/dotc/transform/TypeTestsCasts.scala | 11 +++++------ tests/neg/i22051.scala | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 tests/neg/i22051.scala diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index c1dd6bc6509e..a8c8ec8ce1d8 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -56,7 +56,7 @@ object TypeTestsCasts { * 9. if `X` is `T1 | T2`, checkable(T1, P) && checkable(T2, P). * 10. otherwise, "" */ - def whyUncheckable(X: Type, P: Type, span: Span)(using Context): String = atPhase(Phases.refchecksPhase.next) { + def whyUncheckable(X: Type, P: Type, span: Span, trustTypeApplication: Boolean)(using Context): String = atPhase(Phases.refchecksPhase.next) { extension (inline s1: String) inline def &&(inline s2: String): String = if s1 == "" then s2 else s1 extension (inline b: Boolean) inline def |||(inline s: String): String = if b then "" else s @@ -143,7 +143,7 @@ object TypeTestsCasts { case defn.ArrayOf(tpE) => recur(tpE, tpT) case _ => recur(defn.AnyType, tpT) } - case tpe @ AppliedType(tycon, targs) => + case tpe @ AppliedType(tycon, targs) if !trustTypeApplication => X.widenDealias match { case OrType(tp1, tp2) => // This case is required to retrofit type inference, @@ -366,8 +366,7 @@ object TypeTestsCasts { if (sym.isTypeTest) { val argType = tree.args.head.tpe val isTrusted = tree.hasAttachment(PatternMatcher.TrustedTypeTestKey) - if !isTrusted then - checkTypePattern(expr.tpe, argType, expr.srcPos) + checkTypePattern(expr.tpe, argType, expr.srcPos, isTrusted) transformTypeTest(expr, argType, flagUnrelated = enclosingInlineds.isEmpty) // if test comes from inlined code, dont't flag it even if it always false } @@ -392,10 +391,10 @@ object TypeTestsCasts { def checkBind(tree: Bind)(using Context) = checkTypePattern(defn.ThrowableType, tree.body.tpe, tree.srcPos) - private def checkTypePattern(exprTpe: Type, castTpe: Type, pos: SrcPos)(using Context) = + private def checkTypePattern(exprTpe: Type, castTpe: Type, pos: SrcPos, trustTypeApplication: Boolean = false)(using Context) = val isUnchecked = exprTpe.widenTermRefExpr.hasAnnotation(defn.UncheckedAnnot) if !isUnchecked then - val whyNot = whyUncheckable(exprTpe, castTpe, pos.span) + val whyNot = whyUncheckable(exprTpe, castTpe, pos.span, trustTypeApplication) if whyNot.nonEmpty then report.uncheckedWarning(UncheckedTypePattern(castTpe, whyNot), pos) diff --git a/tests/neg/i22051.scala b/tests/neg/i22051.scala new file mode 100644 index 000000000000..ba6805a4e166 --- /dev/null +++ b/tests/neg/i22051.scala @@ -0,0 +1,19 @@ +//> using options -Werror + +def boundary[T](body: (T => RuntimeException) => T): T = + case class Break(value: T) extends RuntimeException + try body(Break.apply) + catch case Break(t) => t // error: pattern matching on local classes is unsound currently + +def test = + boundary[Int]: EInt => + val v: String = boundary[String]: EString => + throw EInt(3) + v.length // a runtime error: java.lang.ClassCastException + +def boundaryCorrectBehaviour[T](body: (T => RuntimeException) => T): T = + object local: + // A correct implementation, but is still treated as a local class right now + case class Break(value: T) extends RuntimeException + try body(local.Break.apply) + catch case local.Break(t) => t // error From a7b23d6f1890ce31cf52b72848b559d7873c4213 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 3 Dec 2024 16:11:36 +0100 Subject: [PATCH 097/202] Move test to warn --- tests/{neg => warn}/i22051.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) rename tests/{neg => warn}/i22051.scala (77%) diff --git a/tests/neg/i22051.scala b/tests/warn/i22051.scala similarity index 77% rename from tests/neg/i22051.scala rename to tests/warn/i22051.scala index ba6805a4e166..e1e902e16379 100644 --- a/tests/neg/i22051.scala +++ b/tests/warn/i22051.scala @@ -1,9 +1,7 @@ -//> using options -Werror - def boundary[T](body: (T => RuntimeException) => T): T = case class Break(value: T) extends RuntimeException try body(Break.apply) - catch case Break(t) => t // error: pattern matching on local classes is unsound currently + catch case Break(t) => t // warn: pattern matching on local classes is unsound currently def test = boundary[Int]: EInt => @@ -16,4 +14,4 @@ def boundaryCorrectBehaviour[T](body: (T => RuntimeException) => T): T = // A correct implementation, but is still treated as a local class right now case class Break(value: T) extends RuntimeException try body(local.Break.apply) - catch case local.Break(t) => t // error + catch case local.Break(t) => t // warn From 75f1ec78e68739bb9064081f8651840b2fce9b74 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Tue, 3 Dec 2024 16:15:56 +0000 Subject: [PATCH 098/202] Add test cases project for presentation compiler --- build.sbt | 1 + .../test/dotty/tools/pc/base/BasePCSuite.scala | 8 ++++---- project/Build.scala | 9 +++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index 9d29bfcb6d6a..9425c4eed1e9 100644 --- a/build.sbt +++ b/build.sbt @@ -36,6 +36,7 @@ val `dist-linux-aarch64` = Build.`dist-linux-aarch64` val `community-build` = Build.`community-build` val `sbt-community-build` = Build.`sbt-community-build` val `scala3-presentation-compiler` = Build.`scala3-presentation-compiler` +val `scala3-presentation-compiler-testcases` = Build.`scala3-presentation-compiler-testcases` val sjsSandbox = Build.sjsSandbox val sjsJUnitTests = Build.sjsJUnitTests diff --git a/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala b/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala index 1158e433e732..a26c31ef084d 100644 --- a/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala @@ -25,9 +25,9 @@ import org.junit.runner.RunWith import scala.meta.pc.CompletionItemPriority object TestResources: - val scalaLibrary = BuildInfo.ideTestsDependencyClasspath.map(_.toPath).toSeq + val classpath = BuildInfo.ideTestsDependencyClasspath.map(_.toPath).toSeq val classpathSearch = - ClasspathSearch.fromClasspath(scalaLibrary, ExcludedPackagesHandler.default) + ClasspathSearch.fromClasspath(classpath, ExcludedPackagesHandler.default) @RunWith(classOf[ReusableClassRunner]) abstract class BasePCSuite extends PcAssertions: @@ -38,11 +38,11 @@ abstract class BasePCSuite extends PcAssertions: val executorService: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor() val testingWorkspaceSearch = TestingWorkspaceSearch( - TestResources.scalaLibrary.map(_.toString) + TestResources.classpath.map(_.toString) ) lazy val presentationCompiler: PresentationCompiler = - val myclasspath: Seq[Path] = TestResources.scalaLibrary + val myclasspath: Seq[Path] = TestResources.classpath val scalacOpts = scalacOptions(myclasspath) val search = new MockSymbolSearch( testingWorkspaceSearch, diff --git a/project/Build.scala b/project/Build.scala index b2aca3705d83..f36171aabbcd 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1425,7 +1425,7 @@ object Build { lazy val `scala3-presentation-compiler` = project.in(file("presentation-compiler")) .withCommonSettings(Bootstrapped) - .dependsOn(`scala3-compiler-bootstrapped`, `scala3-library-bootstrapped`) + .dependsOn(`scala3-compiler-bootstrapped`, `scala3-library-bootstrapped`, `scala3-presentation-compiler-testcases`) .settings(presentationCompilerSettings) .settings(scala3PresentationCompilerBuildInfo) .settings( @@ -1436,6 +1436,7 @@ object Build { def scala3PresentationCompilerBuildInfo = Seq( ideTestsDependencyClasspath := { + val testCasesLib = (`scala3-presentation-compiler-testcases` / Compile / classDirectory).value val dottyLib = (`scala3-library-bootstrapped` / Compile / classDirectory).value val scalaLib = (`scala3-library-bootstrapped` / Compile / dependencyClasspath) @@ -1443,7 +1444,7 @@ object Build { .map(_.data) .filter(_.getName.matches("scala-library.*\\.jar")) .toList - dottyLib :: scalaLib + testCasesLib :: dottyLib :: scalaLib // Nil }, Compile / buildInfoPackage := "dotty.tools.pc.buildinfo", @@ -1503,6 +1504,10 @@ object Build { ) } + lazy val `scala3-presentation-compiler-testcases` = project.in(file("presentation-compiler-testcases")) + .dependsOn(`scala3-compiler-bootstrapped`) + .settings(commonBootstrappedSettings) + lazy val `scala3-language-server` = project.in(file("language-server")). dependsOn(dottyCompiler(Bootstrapped)). settings(commonBootstrappedSettings). From 39f54dfdaccd518a4ddd54ca228a836495b62d99 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Tue, 3 Dec 2024 16:22:20 +0000 Subject: [PATCH 099/202] Add test cases for #20560 --- .../src/tests/macros/20560.scala | 16 ++++++++++++++++ .../tools/pc/tests/hover/HoverTermSuite.scala | 15 +++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 presentation-compiler-testcases/src/tests/macros/20560.scala diff --git a/presentation-compiler-testcases/src/tests/macros/20560.scala b/presentation-compiler-testcases/src/tests/macros/20560.scala new file mode 100644 index 000000000000..f72fc473f452 --- /dev/null +++ b/presentation-compiler-testcases/src/tests/macros/20560.scala @@ -0,0 +1,16 @@ +package tests.macros + +import scala.quoted.{Expr, Quotes} + +object Macro20560: + transparent inline def loadJavaSqlDriver: Int = ${ loadJavaSqlDriverImpl } + + private def loadJavaSqlDriverImpl(using Quotes): Expr[42] = + Class.forName("java.sql.Driver") + '{42} + + transparent inline def loadJavaSqlInexisting: Int = ${ loadJavaSqlInexistingImpl } + + private def loadJavaSqlInexistingImpl(using Quotes): Expr[42] = + Class.forName("java.sql.Inexisting") + '{42} 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 3e7a2549cbe0..ec1431187e56 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala @@ -596,6 +596,21 @@ class HoverTermSuite extends BaseHoverSuite: |""".stripMargin ) + @Test def `i20560`= + check( + "val re@@s = tests.macros.Macro20560.loadJavaSqlDriver", + """```scala + |val res: Int + |``` + |""".stripMargin + ) + + @Test def `i20560-2`= + check( + "val re@@s = tests.macros.Macro20560.loadJavaSqlInexisting", + "", // crashes in the Macro; no type info + ) + @Test def `import-rename` = check( """ From 898f95226f6c0766293cfd223f2996fe7d70d503 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Tue, 3 Dec 2024 19:27:32 +0100 Subject: [PATCH 100/202] Do not return java outline dummy constructor in `primaryConstructor` Java outline parser phase for various reasons adds a dummy constructor to java classes compiling simultenously from scalac. Since they provide no information to the user and are overall misleading (with always having the same fake flags and parameters), we filter them out and return the first constructor that can be found in the source. This is also what happened up to this point when running the macro with a java classfile on the classpath instead, since those dummy constructors cannot be found there. --- .../quoted/runtime/impl/QuotesImpl.scala | 21 ++++++++++++++++++- tests/run-macros/i20052.check | 5 +++++ tests/run-macros/i20052/JavaClass.java | 4 ++++ tests/run-macros/i20052/JavaClassEmpty.java | 1 + tests/run-macros/i20052/JavaClassParam.java | 3 +++ tests/run-macros/i20052/JavaClassPrivate.java | 3 +++ .../i20052/JavaClassStartsWithPrivate.java | 4 ++++ tests/run-macros/i20052/Macro.scala | 16 ++++++++++++++ tests/run-macros/i20052/Test_2.scala | 6 ++++++ 9 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 tests/run-macros/i20052.check create mode 100644 tests/run-macros/i20052/JavaClass.java create mode 100644 tests/run-macros/i20052/JavaClassEmpty.java create mode 100644 tests/run-macros/i20052/JavaClassParam.java create mode 100644 tests/run-macros/i20052/JavaClassPrivate.java create mode 100644 tests/run-macros/i20052/JavaClassStartsWithPrivate.java create mode 100644 tests/run-macros/i20052/Macro.scala create mode 100644 tests/run-macros/i20052/Test_2.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 22be293c3562..fd5b635e11e2 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2822,7 +2822,26 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler self.typeRef.info.decls.toList def paramSymss: List[List[Symbol]] = self.denot.paramSymss - def primaryConstructor: Symbol = self.denot.primaryConstructor + def primaryConstructor: Symbol = + val initialPrimary = self.denot.primaryConstructor + // Java outline parser creates a dummyConstructor. We want to avoid returning it here, + // instead returning the first non-dummy one, which is what happens when a java classfile + // is read from classpath instead of using the java outline parser. + // We check if the constructor is dummy if it has the same parameters as defined in JavaParsers.scala, + // incliding the private[this] flags and parameter shape with scala.Unit argument. + val isJavaDummyConstructor = + val paramSymss = initialPrimary.paramSymss + initialPrimary.flags.is(Flags.JavaDefined | Flags.Local | Flags.Method | Flags.Private | Flags.PrivateLocal) + && { + paramSymss match + case List(List(typeTree)) if self.typeRef.memberType(typeTree).typeSymbol == defn.UnitClass => true + case _ => false + } + if isJavaDummyConstructor then + declarations.filter(sym => sym != initialPrimary && sym.isConstructor).headOption.getOrElse(Symbol.noSymbol) + else + initialPrimary + def allOverriddenSymbols: Iterator[Symbol] = self.denot.allOverriddenSymbols def overridingSymbol(ofclazz: Symbol): Symbol = if ofclazz.isClass then self.denot.overridingSymbol(ofclazz.asClass) diff --git a/tests/run-macros/i20052.check b/tests/run-macros/i20052.check new file mode 100644 index 000000000000..ca45222cf4cf --- /dev/null +++ b/tests/run-macros/i20052.check @@ -0,0 +1,5 @@ +method (Flags.JavaDefined | Flags.Method) List(List((x$0,scala.Int))) +method (Flags.JavaDefined | Flags.Method) List(List()) +method (Flags.JavaDefined | Flags.Method | Flags.Private) List(List()) +method (Flags.JavaDefined | Flags.Method | Flags.Private) List(List()) +method (Flags.JavaDefined | Flags.Method) List(List((A,_ >: scala.Nothing <: .)), List((x$0,A))) diff --git a/tests/run-macros/i20052/JavaClass.java b/tests/run-macros/i20052/JavaClass.java new file mode 100644 index 000000000000..368bbf47830a --- /dev/null +++ b/tests/run-macros/i20052/JavaClass.java @@ -0,0 +1,4 @@ +public class JavaClass { + public JavaClass(int a) {} + public JavaClass(float a) {} +} diff --git a/tests/run-macros/i20052/JavaClassEmpty.java b/tests/run-macros/i20052/JavaClassEmpty.java new file mode 100644 index 000000000000..22710a8b7337 --- /dev/null +++ b/tests/run-macros/i20052/JavaClassEmpty.java @@ -0,0 +1 @@ +public class JavaClassEmpty {} diff --git a/tests/run-macros/i20052/JavaClassParam.java b/tests/run-macros/i20052/JavaClassParam.java new file mode 100644 index 000000000000..ddbb7b0a6a5f --- /dev/null +++ b/tests/run-macros/i20052/JavaClassParam.java @@ -0,0 +1,3 @@ +public class JavaClassParam { + public JavaClassParam(A a) {} +} diff --git a/tests/run-macros/i20052/JavaClassPrivate.java b/tests/run-macros/i20052/JavaClassPrivate.java new file mode 100644 index 000000000000..d2f0b3cda023 --- /dev/null +++ b/tests/run-macros/i20052/JavaClassPrivate.java @@ -0,0 +1,3 @@ +class JavaClassPrivate { + private JavaClassPrivate() {} +} diff --git a/tests/run-macros/i20052/JavaClassStartsWithPrivate.java b/tests/run-macros/i20052/JavaClassStartsWithPrivate.java new file mode 100644 index 000000000000..d27724424f34 --- /dev/null +++ b/tests/run-macros/i20052/JavaClassStartsWithPrivate.java @@ -0,0 +1,4 @@ +class JavaClassStartsWithPrivate { + private JavaClassStartsWithPrivate() {} + public JavaClassStartsWithPrivate(int a) {} +} diff --git a/tests/run-macros/i20052/Macro.scala b/tests/run-macros/i20052/Macro.scala new file mode 100644 index 000000000000..e38bb63257a3 --- /dev/null +++ b/tests/run-macros/i20052/Macro.scala @@ -0,0 +1,16 @@ +import scala.quoted.* + +object Macro { + inline def logPrimaryConstructor[A]: String = ${ logPrimaryConstructorImpl[A] } + + def logPrimaryConstructorImpl[A](using Type[A], Quotes): Expr[String] = { + import quotes.reflect.* + + val primaryConstructor = TypeRepr.of[A].typeSymbol.primaryConstructor + val flags = primaryConstructor.flags.show + val paramSymss = primaryConstructor.paramSymss + val clauses = paramSymss.map(_.map(param => (param.name, TypeRepr.of[A].memberType(param).show))) + val str = s"${primaryConstructor} (${primaryConstructor.flags.show}) ${clauses}" + Expr(str) + } +} diff --git a/tests/run-macros/i20052/Test_2.scala b/tests/run-macros/i20052/Test_2.scala new file mode 100644 index 000000000000..ef4fc8bd1887 --- /dev/null +++ b/tests/run-macros/i20052/Test_2.scala @@ -0,0 +1,6 @@ +@main def Test() = + println(Macro.logPrimaryConstructor[JavaClass]) + println(Macro.logPrimaryConstructor[JavaClassEmpty]) + println(Macro.logPrimaryConstructor[JavaClassPrivate]) + println(Macro.logPrimaryConstructor[JavaClassStartsWithPrivate]) + println(Macro.logPrimaryConstructor[JavaClassParam[Int]]) From 115de9d85e83ef0e0f0622db21dbe399c3e7d191 Mon Sep 17 00:00:00 2001 From: kasiaMarek Date: Wed, 4 Dec 2024 13:49:27 +0100 Subject: [PATCH 101/202] fix match error in keyword completions --- .../pc/completions/KeywordsCompletions.scala | 1 + .../pc/tests/completion/CompletionSuite.scala | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/KeywordsCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/KeywordsCompletions.scala index abbf43174bc7..5dd3c8dead4d 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/KeywordsCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/KeywordsCompletions.scala @@ -216,6 +216,7 @@ object KeywordsCompletions: case untpd.TypeDef(_, template: Template) => checkForPossibleKeywords(template) case untpd.ModuleDef(_, template: Template) => checkForPossibleKeywords(template) case template: Template => checkForPossibleKeywords(template) + case _ => TemplateKeywordAvailability.default }.getOrElse(TemplateKeywordAvailability.default) 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 ab28baea994b..4a30f9b06efa 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -2150,3 +2150,21 @@ class CompletionSuite extends BaseCompletionSuite: |""".stripMargin, assertSingleItem = false ) + + @Test def `metals-i6861` = + check( + """|trait Builder[Alg]: + | def withTraces: String + | + |trait BuilderFactory: + | def transformRouter(f: [Alg] => Builder[Alg] => String): BuilderFactory + | def build: Unit + | + |def demo = + | (??? : BuilderFactory) + | .transformRouter([Alg] => _.withTraces) + | .build@@ + |""".stripMargin, + """|build: Unit + |""".stripMargin, + ) From de44d078ba68c1e0d503343a97b53195f5858ca1 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 4 Dec 2024 13:35:57 +0000 Subject: [PATCH 102/202] Introduce bindTypeSymbols utility in tpd.TreeOps --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 4 ++++ .../dotty/tools/dotc/inlines/InlineReducer.scala | 16 +++------------- .../dotty/tools/dotc/typer/TypeAssigner.scala | 8 +------- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 55021bf50ace..77e3387c5ce0 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1137,6 +1137,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { /** Replace Ident nodes references to the underlying tree that defined them */ def underlying(using Context): Tree = MapToUnderlying().transform(tree) + /** Collect all the TypeSymbol's of the type Bind nodes in the tree. */ + def bindTypeSymbols(using Context): List[TypeSymbol] = + tree.collectSubTrees { case b: Bind if b.isType => b.symbol.asType } + // --- Higher order traversal methods ------------------------------- /** Apply `f` to each subtree of this tree */ diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala index 26fd52fb7138..0c321570bad5 100644 --- a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -216,31 +216,21 @@ class InlineReducer(inliner: Inliner)(using Context): type TypeBindsMap = SimpleIdentityMap[TypeSymbol, java.lang.Boolean] def getTypeBindsMap(pat: Tree, tpt: Tree): TypeBindsMap = { - val getBinds = new TreeAccumulator[Set[TypeSymbol]] { - def apply(syms: Set[TypeSymbol], t: Tree)(using Context): Set[TypeSymbol] = { - val syms1 = t match { - case t: Bind if t.symbol.isType => - syms + t.symbol.asType - case _ => syms - } - foldOver(syms1, t) - } - } - // Extractors can contain Bind nodes in type parameter lists, // for that case tree looks like this: // UnApply[t @ t](pats)(implicits): T[t] // Test case is pos/inline-caseclass.scala. + // // Alternatively, for explicitly specified type binds in type annotations like in // case A(B): A[t] // the tree will look like this: // Unapply[t](pats)(implicits) : T[t @ t] // and the binds will be found in the type tree instead // Test case is pos-macros/i15971 - val tptBinds = getBinds(Set.empty[TypeSymbol], tpt) + val tptBinds = tpt.bindTypeSymbols.toSet val binds: Set[TypeSymbol] = pat match { case UnApply(TypeApply(_, tpts), _, _) => - getBinds(Set.empty[TypeSymbol], tpts) ++ tptBinds + tpts.flatMap(_.bindTypeSymbols).toSet ++ tptBinds case _ => tptBinds } diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 8751bd7dc9bb..6ce0f5f2517c 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -429,13 +429,7 @@ trait TypeAssigner { def assignType(tree: untpd.CaseDef, pat: Tree, body: Tree)(using Context): CaseDef = { val ownType = if (body.isType) { - val getParams = new TreeAccumulator[mutable.ListBuffer[TypeSymbol]] { - def apply(ps: mutable.ListBuffer[TypeSymbol], t: Tree)(using Context) = t match { - case t: Bind if t.symbol.isType => foldOver(ps += t.symbol.asType, t) - case _ => foldOver(ps, t) - } - } - val params1 = getParams(new mutable.ListBuffer[TypeSymbol](), pat).toList + val params1 = pat.bindTypeSymbols val params2 = pat.tpe match case AppliedType(tycon, args) => val tparams = tycon.typeParamSymbols From 2f6e60d5819873763609943962360c2f85f338bb Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 4 Dec 2024 13:36:55 +0000 Subject: [PATCH 103/202] Add type avoidance to inferred MT bound lubbing --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/pos/i21256.scala | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i21256.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6bb5d1ee70ff..3434fa9f48fc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2649,7 +2649,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val lub = cases1.foldLeft(defn.NothingType: Type): (acc, case1) => if !acc.exists then NoType else if case1.body.tpe.isProvisional then NoType - else acc | case1.body.tpe + else acc | TypeOps.avoid(case1.body.tpe, case1.pat.bindTypeSymbols) if lub.exists then if !lub.isAny then val msg = em"Match type upper bound inferred as $lub, where previously it was defaulted to Any" diff --git a/tests/pos/i21256.scala b/tests/pos/i21256.scala new file mode 100644 index 000000000000..e51484c73ef7 --- /dev/null +++ b/tests/pos/i21256.scala @@ -0,0 +1,5 @@ +object Test { + type MTWithBind[X] = X match { + case List[t] => t + } +} From fd7f451eeccfa8adb260d9b2cf86f47acfc1bc43 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 4 Dec 2024 13:38:05 +0000 Subject: [PATCH 104/202] Consider S a pseudo match alias Fixes tests/pos/i18211.scala, where type avoidance would cause `S[? <: Int]` which would otherwise be consider an unreducible wild and lead to Any which isn't <: Int. --- compiler/src/dotty/tools/dotc/core/Types.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 95c3b025b3ce..c5937074f4bc 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4633,6 +4633,7 @@ object Types extends TypeUtils { */ def isUnreducibleWild(using Context): Boolean = tycon.isLambdaSub && hasWildcardArg && !isMatchAlias + && !(args.sizeIs == 1 && defn.isCompiletime_S(tycon.typeSymbol)) // S is a pseudo Match Alias def tryCompiletimeConstantFold(using Context): Type = if myEvalRunId == ctx.runId then myEvalued From 045b92f8c40cd6c173a02b8e7e0f989810178a5e Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 4 Dec 2024 16:07:15 +0000 Subject: [PATCH 105/202] Exclude testPickling difference --- compiler/test/dotc/pos-test-pickling.blacklist | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index ebdd414ea7f2..f35d527edf62 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -19,6 +19,7 @@ i12299a.scala i13871.scala i15181.scala i15922.scala +i15926.scala t5031_2.scala i16997.scala i7414.scala From 984e6df9fe3e4307561b9cc869b1ad21edc150c7 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 5 Dec 2024 09:47:45 +0000 Subject: [PATCH 106/202] Fix ASF call in ResolveSuper --- .../tools/dotc/transform/ResolveSuper.scala | 4 +-- tests/pos/i22062.scala | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i22062.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index dd3f41be5a8e..6bc5db79fee5 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -109,8 +109,8 @@ object ResolveSuper { sym = other.symbol // Having a matching denotation is not enough: it should also be a subtype // of the superaccessor's type, see i5433.scala for an example where this matters - val otherTp = other.asSeenFrom(base.typeRef).info - val accTp = acc.asSeenFrom(base.typeRef).info + val otherTp = other.asSeenFrom(base.thisType).info + val accTp = acc.asSeenFrom(base.thisType).info // Since the super class can be Java defined, // we use relaxed overriding check for explicit nulls if one of the symbols is Java defined. // This forces `Null` to be a subtype of non-primitive value types during override checking. diff --git a/tests/pos/i22062.scala b/tests/pos/i22062.scala new file mode 100644 index 000000000000..73289deac0a4 --- /dev/null +++ b/tests/pos/i22062.scala @@ -0,0 +1,32 @@ +class Label +class Component + +trait RenderableCellsCompanion: + type Renderer[-A] <: CellRenderer[A] + type DefaultRenderer[-A] <: Label & Renderer[A] + + trait CellRendererCompanion: + type CellInfo + def labeled[A](): DefaultRenderer[A] + protected trait LabelRenderer[-A] extends CellRenderer[A]: + override abstract def componentFor(info: companion.CellInfo): Component = super.componentFor(info) + + trait CellRenderer[-A]: + val companion: CellRendererCompanion + def componentFor(cellInfo: companion.CellInfo): Component + +sealed trait TreeRenderers extends RenderableCellsCompanion: + this: Tree.type => + + trait Renderer[-A] extends CellRenderer[A]: + final override val companion = Renderer + + object Renderer extends CellRendererCompanion: + final override class CellInfo + override def labeled[A]() = new DefaultRenderer[A] with LabelRenderer[A] {} + + class DefaultRenderer[-A] extends Label with Renderer[A]: + override def componentFor(info: Renderer.CellInfo): Component = ??? + +class Tree extends Component +object Tree extends TreeRenderers From 7d9771d983afdec4344d35642bcdf6adecd58a4b Mon Sep 17 00:00:00 2001 From: Jan Chyb <48855024+jchyb@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:01:08 +0100 Subject: [PATCH 107/202] Do not bring forward symbols created in transform and backend phases (#21865) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the i21844 issue minimization, first the `Macro.scala` and `SuperClassWIthLazyVal.scala` compilation units are compiled, then in the next run `SubClass.scala` is compiled. While compiling `SuperClassWIthLazyVal.scala`, in the `LazyVals` transform phase, the lzyINIT initialization fields are created. In the next run, while compiling `SubClass.scala`, in the `GenBCode` phase, the compiler would try to access the lzyINIT symbol, leading to a stale symbols crash. While that symbol was a part of the SuperClass, it by design is not generated for the Subclass - if we were to completely split those files into 2 separate compilations, that symbol would be created only for the classfile, but it would not be included in tasty, so the second compilation would not try to access it. This observation inspires the proposed fix, where if the symbol was first created in or after the first transform phase (so after the pickler phases), we do not try to bring forward this denotation, instead returning NoDenotation. In this PR, we also remove the fix proposed in i21559, as it is no longer necessary with the newly added condition. There, since one of the problematic Symbols created in `LazyVals` was moved elsewhere in `MoveStatics`, we checked if that symbol could be found in a companion object. I was not able to create any reproduction where a user defined static would produce this problem, so I believe it’s safe to remove that. --- compiler/src/dotty/tools/dotc/core/Denotations.scala | 10 ++++------ .../src/dotty/tools/dotc/core/SymDenotations.scala | 4 ---- tests/pos-macros/i21844/Macro.scala | 6 ++++++ tests/pos-macros/i21844/SubClass.scala | 3 +++ tests/pos-macros/i21844/SuperClassWithLazyVal.scala | 2 ++ 5 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 tests/pos-macros/i21844/Macro.scala create mode 100644 tests/pos-macros/i21844/SubClass.scala create mode 100644 tests/pos-macros/i21844/SuperClassWithLazyVal.scala diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 816b28177333..85ff51bc19de 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -2,7 +2,7 @@ package dotty.tools package dotc package core -import SymDenotations.{ SymDenotation, ClassDenotation, NoDenotation, LazyType, stillValid, movedToCompanionClass, acceptStale, traceInvalid } +import SymDenotations.{ SymDenotation, ClassDenotation, NoDenotation, LazyType, stillValid, acceptStale, traceInvalid } import Contexts.* import Names.* import NameKinds.* @@ -742,6 +742,8 @@ object Denotations { * the old version otherwise. * - If the symbol did not have a denotation that was defined at the current phase * return a NoDenotation instead. + * - If the symbol was first defined in one of the transform phases (after pickling), it should not + * be visible in new runs, so also return a NoDenotation. */ private def bringForward()(using Context): SingleDenotation = { this match { @@ -755,11 +757,7 @@ object Denotations { } if (!symbol.exists) return updateValidity() if (!coveredInterval.containsPhaseId(ctx.phaseId)) return NoDenotation - // Moved to a companion class, likely at a later phase (in MoveStatics) - this match { - case symd: SymDenotation if movedToCompanionClass(symd) => return NoDenotation - case _ => - } + if (coveredInterval.firstPhaseId >= Phases.firstTransformPhase.id) return NoDenotation if (ctx.debug) traceInvalid(this) staleSymbolError } diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index f54b8a62fa25..e14e5cf0a728 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2680,10 +2680,6 @@ object SymDenotations { stillValidInOwner(denot) } - def movedToCompanionClass(denot: SymDenotation)(using Context): Boolean = - val ownerCompanion = denot.maybeOwner.companionClass - stillValid(ownerCompanion) && ownerCompanion.unforcedDecls.contains(denot.name, denot.symbol) - private[SymDenotations] def stillValidInOwner(denot: SymDenotation)(using Context): Boolean = try val owner = denot.maybeOwner.denot stillValid(owner) diff --git a/tests/pos-macros/i21844/Macro.scala b/tests/pos-macros/i21844/Macro.scala new file mode 100644 index 000000000000..31a2c3a5e76f --- /dev/null +++ b/tests/pos-macros/i21844/Macro.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +object Macro: + inline def foo = ${ fooImpl } + def fooImpl(using Quotes): Expr[Int] = + '{ 123 } diff --git a/tests/pos-macros/i21844/SubClass.scala b/tests/pos-macros/i21844/SubClass.scala new file mode 100644 index 000000000000..54b0d970e515 --- /dev/null +++ b/tests/pos-macros/i21844/SubClass.scala @@ -0,0 +1,3 @@ +class SubClass extends SuperClass +object SubClass: + val foo: Int = Macro.foo diff --git a/tests/pos-macros/i21844/SuperClassWithLazyVal.scala b/tests/pos-macros/i21844/SuperClassWithLazyVal.scala new file mode 100644 index 000000000000..60c270f2b277 --- /dev/null +++ b/tests/pos-macros/i21844/SuperClassWithLazyVal.scala @@ -0,0 +1,2 @@ +class SuperClass: + lazy val xyz: Int = 123 From cb73df2809e1894ff0ce1183b92c63b11cfef38f Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 5 Dec 2024 13:47:05 +0100 Subject: [PATCH 108/202] Update CLA check server url domain from lightbend.com to contibute.akka.io --- project/scripts/check-cla.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/project/scripts/check-cla.sh b/project/scripts/check-cla.sh index e4e489830f11..4ded66f3d3c3 100755 --- a/project/scripts/check-cla.sh +++ b/project/scripts/check-cla.sh @@ -5,16 +5,16 @@ echo "Pull request submitted by $AUTHOR"; if [[ "$AUTHOR" == "github-actions[bot]" || "$AUTHOR" == "dependabot[bot]" ]] ; then echo "CLA check for $AUTHOR successful"; else - signed=$(curl -s "https://www.lightbend.com/contribute/cla/scala/check/$AUTHOR" | jq -r ".signed"); + signed=$(curl -s "https://contribute.akka.io/contribute/cla/scala/check/$AUTHOR" | jq -r ".signed"); if [ "$signed" = "true" ] ; then echo "CLA check for $AUTHOR successful"; else echo "CLA check for $AUTHOR failed"; echo "Please sign the Scala CLA to contribute to the Scala compiler."; - echo "Go to https://www.lightbend.com/contribute/cla/scala and then"; + echo "Go to https://contribute.akka.io/contribute/cla/scala and then"; echo "comment on the pull request to ask for a new check."; echo ""; - echo "Check if CLA is signed: https://www.lightbend.com/contribute/cla/scala/check/$AUTHOR"; + echo "Check if CLA is signed: https://contribute.akka.io/contribute/cla/scala/check/$AUTHOR"; exit 1; fi; fi; From 191535b8723af53da834a72f99d71df134690370 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 5 Dec 2024 13:47:37 +0100 Subject: [PATCH 109/202] Follow redirects when checkting CLA --- project/scripts/check-cla.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/scripts/check-cla.sh b/project/scripts/check-cla.sh index 4ded66f3d3c3..dbb148d3c652 100755 --- a/project/scripts/check-cla.sh +++ b/project/scripts/check-cla.sh @@ -5,7 +5,7 @@ echo "Pull request submitted by $AUTHOR"; if [[ "$AUTHOR" == "github-actions[bot]" || "$AUTHOR" == "dependabot[bot]" ]] ; then echo "CLA check for $AUTHOR successful"; else - signed=$(curl -s "https://contribute.akka.io/contribute/cla/scala/check/$AUTHOR" | jq -r ".signed"); + signed=$(curl -L -s "https://contribute.akka.io/contribute/cla/scala/check/$AUTHOR" | jq -r ".signed"); if [ "$signed" = "true" ] ; then echo "CLA check for $AUTHOR successful"; else From 483920640f016c7ccdb31144df7ca602d99297fa Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 5 Dec 2024 16:45:53 +0100 Subject: [PATCH 110/202] Widen singleton types when computing fields from .Fields Fixes #22018 --- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/pos/i22018.scala | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i22018.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6bb5d1ee70ff..1a3f5c2d97c3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -868,7 +868,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer && pt != LhsProto then val pre = if !TypeOps.isLegalPrefix(qual.tpe) then SkolemType(qual.tpe) else qual.tpe - val fieldsType = pre.select(tpnme.Fields).dealias.simplified + val fieldsType = pre.select(tpnme.Fields).widenDealias.simplified val fields = fieldsType.namedTupleElementTypes typr.println(i"try dyn select $qual, $selName, $fields") fields.find(_._1 == selName) match diff --git a/tests/pos/i22018.scala b/tests/pos/i22018.scala new file mode 100644 index 000000000000..14f4733732be --- /dev/null +++ b/tests/pos/i22018.scala @@ -0,0 +1,18 @@ +import scala.language.experimental.namedTuples + +class SelectableNT[A <: NamedTuple.AnyNamedTuple](val nt: A) extends Selectable: + type Fields = A + def selectDynamic(x: String) = ??? + +object Test: + + val a = (name = "foo", age = 1) + + val sa = SelectableNT(a) + sa.name // ok + + type B = a.type + val b: B = a + + val sb = SelectableNT(b) + sb.name // fails \ No newline at end of file From 585dda9587bc95afb936eb18751fbf850cf3209c Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sat, 21 Sep 2024 19:54:39 +0200 Subject: [PATCH 111/202] Refactor NotNullInfo to record every reference which is retracted once. --- .../dotty/tools/dotc/typer/Nullables.scala | 32 +++++++--- .../src/dotty/tools/dotc/typer/Typer.scala | 15 ++++- tests/explicit-nulls/neg/i21380c.scala | 6 +- tests/explicit-nulls/neg/i21619.scala | 62 +++++++++++++++++++ 4 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 tests/explicit-nulls/neg/i21619.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 3f071dad2d03..74623ed7b4e9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -52,15 +52,19 @@ object Nullables: val hiTree = if(hiTpe eq hi.typeOpt) hi else TypeTree(hiTpe) TypeBoundsTree(lo, hiTree, alias) - /** A set of val or var references that are known to be not null, plus a set of - * variable references that are not known (anymore) to be not null + /** A set of val or var references that are known to be not null, + * a set of variable references that are not known (anymore) to be not null, + * plus a set of variables that are known to be not null at any point. */ - case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef]): + case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef], onceRetracted: Set[TermRef]): assert((asserted & retracted).isEmpty) + assert(retracted.subsetOf(onceRetracted)) def isEmpty = this eq NotNullInfo.empty - def retractedInfo = NotNullInfo(Set(), retracted) + def retractedInfo = NotNullInfo(Set(), retracted, onceRetracted) + + def onceRetractedInfo = NotNullInfo(Set(), onceRetracted, onceRetracted) /** The sequential combination with another not-null info */ def seq(that: NotNullInfo): NotNullInfo = @@ -68,19 +72,29 @@ object Nullables: else if that.isEmpty then this else NotNullInfo( this.asserted.union(that.asserted).diff(that.retracted), - this.retracted.union(that.retracted).diff(that.asserted)) + this.retracted.union(that.retracted).diff(that.asserted), + this.onceRetracted.union(that.onceRetracted)) /** The alternative path combination with another not-null info. Used to merge * the nullability info of the two branches of an if. */ def alt(that: NotNullInfo): NotNullInfo = - NotNullInfo(this.asserted.intersect(that.asserted), this.retracted.union(that.retracted)) + NotNullInfo( + this.asserted.intersect(that.asserted), + this.retracted.union(that.retracted), + this.onceRetracted.union(that.onceRetracted)) + + def withOnceRetracted(that: NotNullInfo): NotNullInfo = + if that.isEmpty then this + else NotNullInfo(this.asserted, this.retracted, this.onceRetracted.union(that.onceRetracted)) object NotNullInfo: - val empty = new NotNullInfo(Set(), Set()) + val empty = new NotNullInfo(Set(), Set(), Set()) def apply(asserted: Set[TermRef], retracted: Set[TermRef]): NotNullInfo = - if asserted.isEmpty && retracted.isEmpty then empty - else new NotNullInfo(asserted, retracted) + apply(asserted, retracted, retracted) + def apply(asserted: Set[TermRef], retracted: Set[TermRef], onceRetracted: Set[TermRef]): NotNullInfo = + if asserted.isEmpty && onceRetracted.isEmpty then empty + else new NotNullInfo(asserted, retracted, onceRetracted) end NotNullInfo /** A pair of not-null sets, depending on whether a condition is `true` or `false` */ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6bb5d1ee70ff..588e4188c7bc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1552,8 +1552,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def thenPathInfo = cond1.notNullInfoIf(true).seq(result.thenp.notNullInfo) def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo) result.withNotNullInfo( - if result.thenp.tpe.isRef(defn.NothingClass) then elsePathInfo - else if result.elsep.tpe.isRef(defn.NothingClass) then thenPathInfo + if result.thenp.tpe.isRef(defn.NothingClass) then + elsePathInfo.withOnceRetracted(thenPathInfo) + else if result.elsep.tpe.isRef(defn.NothingClass) then + thenPathInfo.withOnceRetracted(elsePathInfo) else thenPathInfo.alt(elsePathInfo) ) end typedIf @@ -2350,10 +2352,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer }: @unchecked val cases2 = cases2x.asInstanceOf[List[CaseDef]] - var nni = expr2.notNullInfo.retractedInfo + // Since we don't know at which point the the exception is thrown in the body, + // we have to collect any reference that is once retracted. + var nni = expr2.notNullInfo.onceRetractedInfo + // It is possible to have non-exhaustive cases, and some exceptions are thrown and not caught. + // Therefore, the code in the finallizer and after the try block can only rely on the retracted + // info from the cases' body. if cases2.nonEmpty then nni = nni.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_))) + val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nni)) nni = nni.seq(finalizer1.notNullInfo) + assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nni) } diff --git a/tests/explicit-nulls/neg/i21380c.scala b/tests/explicit-nulls/neg/i21380c.scala index f86a5638e4c8..de3cd5bafd6b 100644 --- a/tests/explicit-nulls/neg/i21380c.scala +++ b/tests/explicit-nulls/neg/i21380c.scala @@ -32,9 +32,9 @@ def test4: Int = case npe: NullPointerException => x = "" case _ => x = "" x.length // error - // Although the catch block here is exhaustive, - // it is possible that the exception is thrown and not caught. - // Therefore, the code after the try block can only rely on the retracted info. + // Although the catch block here is exhaustive, it is possible to have non-exhaustive cases, + // and some exceptions are thrown and not caught. Therefore, the code in the finallizer and + // after the try block can only rely on the retracted info from the cases' body. def test5: Int = var x: String | Null = null diff --git a/tests/explicit-nulls/neg/i21619.scala b/tests/explicit-nulls/neg/i21619.scala new file mode 100644 index 000000000000..1c93af707b73 --- /dev/null +++ b/tests/explicit-nulls/neg/i21619.scala @@ -0,0 +1,62 @@ +def test1: String = + var x: String | Null = null + x = "" + var i: Int = 1 + try + i match + case _ => + x = null + throw new Exception() + x = "" + catch + case e: Exception => + x.replace("", "") // error + +def test2: String = + var x: String | Null = null + x = "" + var i: Int = 1 + try + i match + case _ => + x = null + throw new Exception() + x = "" + catch + case e: Exception => + x = "e" + x.replace("", "") // error + +def test3: String = + var x: String | Null = null + x = "" + var i: Int = 1 + try + i match + case _ => + x = null + throw new Exception() + x = "" + catch + case e: Exception => + finally + x = "f" + x.replace("", "") // ok + +def test4: String = + var x: String | Null = null + x = "" + var i: Int = 1 + try + try + if i == 1 then + x = null + throw new Exception() + else + x = "" + catch + case _ => + x = "" + catch + case _ => + x.replace("", "") // error \ No newline at end of file From bcc9e68c778da3faa4dcb8b6c0258a48befff819 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sun, 22 Sep 2024 17:03:39 +0200 Subject: [PATCH 112/202] Use a different rule for NotNullInfo --- .../dotty/tools/dotc/typer/Nullables.scala | 44 ++++++------------- .../src/dotty/tools/dotc/typer/Typer.scala | 34 +++++++------- tests/explicit-nulls/neg/i21619.scala | 19 +++++++- 3 files changed, 50 insertions(+), 47 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 74623ed7b4e9..62d2ccfb7200 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -53,48 +53,35 @@ object Nullables: TypeBoundsTree(lo, hiTree, alias) /** A set of val or var references that are known to be not null, - * a set of variable references that are not known (anymore) to be not null, - * plus a set of variables that are known to be not null at any point. + * plus a set of variable references that are once assigned to null. */ - case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef], onceRetracted: Set[TermRef]): - assert((asserted & retracted).isEmpty) - assert(retracted.subsetOf(onceRetracted)) - + case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef]): def isEmpty = this eq NotNullInfo.empty - def retractedInfo = NotNullInfo(Set(), retracted, onceRetracted) - - def onceRetractedInfo = NotNullInfo(Set(), onceRetracted, onceRetracted) + def retractedInfo = NotNullInfo(Set(), retracted) /** The sequential combination with another not-null info */ def seq(that: NotNullInfo): NotNullInfo = if this.isEmpty then that else if that.isEmpty then this else NotNullInfo( - this.asserted.union(that.asserted).diff(that.retracted), - this.retracted.union(that.retracted).diff(that.asserted), - this.onceRetracted.union(that.onceRetracted)) + this.asserted.diff(that.retracted).union(that.asserted), + this.retracted.union(that.retracted)) /** The alternative path combination with another not-null info. Used to merge * the nullability info of the two branches of an if. */ def alt(that: NotNullInfo): NotNullInfo = - NotNullInfo( - this.asserted.intersect(that.asserted), - this.retracted.union(that.retracted), - this.onceRetracted.union(that.onceRetracted)) + NotNullInfo(this.asserted.intersect(that.asserted), this.retracted.union(that.retracted)) - def withOnceRetracted(that: NotNullInfo): NotNullInfo = - if that.isEmpty then this - else NotNullInfo(this.asserted, this.retracted, this.onceRetracted.union(that.onceRetracted)) + def withRetracted(that: NotNullInfo): NotNullInfo = + NotNullInfo(this.asserted, this.retracted.union(that.retracted)) object NotNullInfo: - val empty = new NotNullInfo(Set(), Set(), Set()) + val empty = new NotNullInfo(Set(), Set()) def apply(asserted: Set[TermRef], retracted: Set[TermRef]): NotNullInfo = - apply(asserted, retracted, retracted) - def apply(asserted: Set[TermRef], retracted: Set[TermRef], onceRetracted: Set[TermRef]): NotNullInfo = - if asserted.isEmpty && onceRetracted.isEmpty then empty - else new NotNullInfo(asserted, retracted, onceRetracted) + if asserted.isEmpty && retracted.isEmpty then empty + else new NotNullInfo(asserted, retracted) end NotNullInfo /** A pair of not-null sets, depending on whether a condition is `true` or `false` */ @@ -247,16 +234,13 @@ object Nullables: * or retractions in `info` supersede infos in existing entries of `infos`. */ def extendWith(info: NotNullInfo) = - if info.isEmpty - || info.asserted.forall(infos.impliesNotNull(_)) - && !info.retracted.exists(infos.impliesNotNull(_)) - then infos + if info.isEmpty then infos else info :: infos /** Retract all references to mutable variables */ def retractMutables(using Context) = - val mutables = infos.foldLeft(Set[TermRef]())((ms, info) => - ms.union(info.asserted.filter(_.symbol.is(Mutable)))) + val mutables = infos.foldLeft(Set[TermRef]()): + (ms, info) => ms.union(info.asserted.filter(_.symbol.is(Mutable))) infos.extendWith(NotNullInfo(Set(), mutables)) end extension diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 588e4188c7bc..8a72076527b9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1553,9 +1553,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo) result.withNotNullInfo( if result.thenp.tpe.isRef(defn.NothingClass) then - elsePathInfo.withOnceRetracted(thenPathInfo) + elsePathInfo.withRetracted(thenPathInfo) else if result.elsep.tpe.isRef(defn.NothingClass) then - thenPathInfo.withOnceRetracted(elsePathInfo) + thenPathInfo.withRetracted(elsePathInfo) else thenPathInfo.alt(elsePathInfo) ) end typedIf @@ -2150,9 +2150,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(using Context): Tree = { val cases1 = harmonic(harmonize, pt)(typedCases(cases, sel, wideSelType, pt.dropIfProto)) .asInstanceOf[List[CaseDef]] - var nni = sel.notNullInfo - if cases1.nonEmpty then nni = nni.seq(cases1.map(_.notNullInfo).reduce(_.alt(_))) - assignType(cpy.Match(tree)(sel, cases1), sel, cases1).withNotNullInfo(nni) + var nnInfo = sel.notNullInfo + if cases1.nonEmpty then nnInfo = nnInfo.seq(cases1.map(_.notNullInfo).reduce(_.alt(_))) + assignType(cpy.Match(tree)(sel, cases1), sel, cases1).withNotNullInfo(nnInfo) } def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType0: Type, pt: Type)(using Context): List[CaseDef] = @@ -2334,7 +2334,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val capabilityProof = caughtExceptions.reduce(OrType(_, _, true)) untpd.Block(makeCanThrow(capabilityProof), expr) - def typedTry(tree: untpd.Try, pt: Type)(using Context): Try = { + def typedTry(tree: untpd.Try, pt: Type)(using Context): Try = + var nnInfo = NotNullInfo.empty val expr2 :: cases2x = harmonic(harmonize, pt) { // We want to type check tree.expr first to comput NotNullInfo, but `addCanThrowCapabilities` // uses the types of patterns in `tree.cases` to determine the capabilities. @@ -2346,25 +2347,26 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val casesEmptyBody1 = tree.cases.mapconserve(cpy.CaseDef(_)(body = EmptyTree)) val casesEmptyBody2 = typedCases(casesEmptyBody1, EmptyTree, defn.ThrowableType, WildcardType) val expr1 = typed(addCanThrowCapabilities(tree.expr, casesEmptyBody2), pt.dropIfProto) - val casesCtx = ctx.addNotNullInfo(expr1.notNullInfo.retractedInfo) + + // Since we don't know at which point the the exception is thrown in the body, + // we have to collect any reference that is once retracted. + nnInfo = expr1.notNullInfo.retractedInfo + + val casesCtx = ctx.addNotNullInfo(nnInfo) val cases1 = typedCases(tree.cases, EmptyTree, defn.ThrowableType, pt.dropIfProto)(using casesCtx) expr1 :: cases1 }: @unchecked val cases2 = cases2x.asInstanceOf[List[CaseDef]] - // Since we don't know at which point the the exception is thrown in the body, - // we have to collect any reference that is once retracted. - var nni = expr2.notNullInfo.onceRetractedInfo // It is possible to have non-exhaustive cases, and some exceptions are thrown and not caught. // Therefore, the code in the finallizer and after the try block can only rely on the retracted // info from the cases' body. - if cases2.nonEmpty then nni = nni.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_))) - - val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nni)) - nni = nni.seq(finalizer1.notNullInfo) + if cases2.nonEmpty then + nnInfo = nnInfo.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_))) - assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nni) - } + val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nnInfo)) + nnInfo = nnInfo.seq(finalizer1.notNullInfo) + assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nnInfo) def typedTry(tree: untpd.ParsedTry, pt: Type)(using Context): Try = val cases: List[untpd.CaseDef] = tree.handler match diff --git a/tests/explicit-nulls/neg/i21619.scala b/tests/explicit-nulls/neg/i21619.scala index 1c93af707b73..244f993fd4e1 100644 --- a/tests/explicit-nulls/neg/i21619.scala +++ b/tests/explicit-nulls/neg/i21619.scala @@ -59,4 +59,21 @@ def test4: String = x = "" catch case _ => - x.replace("", "") // error \ No newline at end of file + x.replace("", "") // error + +def test5: Unit = + var x: String | Null = null + var y: String | Null = null + x = "" + y = "" + var i: Int = 1 + try + i match + case _ => + x = null + throw new Exception() + x = "" + catch + case _ => + val z1: String = x.replace("", "") // error + val z2: String = y.replace("", "") \ No newline at end of file From 05c630acf9dd8b2ecb98f34b1f40bfc8ddb55ce8 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 11 Oct 2024 06:24:24 +0200 Subject: [PATCH 113/202] Consider cases with Nothing type --- .../src/dotty/tools/dotc/typer/Typer.scala | 24 ++++++++++++------- tests/explicit-nulls/neg/i21380b.scala | 18 ++++++++++++++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 8a72076527b9..5731b44368e6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1552,9 +1552,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def thenPathInfo = cond1.notNullInfoIf(true).seq(result.thenp.notNullInfo) def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo) result.withNotNullInfo( - if result.thenp.tpe.isRef(defn.NothingClass) then + if result.thenp.tpe.isNothingType then elsePathInfo.withRetracted(thenPathInfo) - else if result.elsep.tpe.isRef(defn.NothingClass) then + else if result.elsep.tpe.isNothingType then thenPathInfo.withRetracted(elsePathInfo) else thenPathInfo.alt(elsePathInfo) ) @@ -2141,20 +2141,28 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case1 } .asInstanceOf[List[CaseDef]] - var nni = sel.notNullInfo - if cases1.nonEmpty then nni = nni.seq(cases1.map(_.notNullInfo).reduce(_.alt(_))) - assignType(cpy.Match(tree)(sel, cases1), sel, cases1).cast(pt).withNotNullInfo(nni) + assignType(cpy.Match(tree)(sel, cases1), sel, cases1).cast(pt) + .withNotNullInfo(notNullInfoFromCases(sel.notNullInfo, cases1)) } // Overridden in InlineTyper for inline matches def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(using Context): Tree = { val cases1 = harmonic(harmonize, pt)(typedCases(cases, sel, wideSelType, pt.dropIfProto)) .asInstanceOf[List[CaseDef]] - var nnInfo = sel.notNullInfo - if cases1.nonEmpty then nnInfo = nnInfo.seq(cases1.map(_.notNullInfo).reduce(_.alt(_))) - assignType(cpy.Match(tree)(sel, cases1), sel, cases1).withNotNullInfo(nnInfo) + assignType(cpy.Match(tree)(sel, cases1), sel, cases1) + .withNotNullInfo(notNullInfoFromCases(sel.notNullInfo, cases1)) } + private def notNullInfoFromCases(initInfo: NotNullInfo, cases: List[CaseDef])(using Context): NotNullInfo = + var nnInfo = initInfo + if cases.nonEmpty then + val (nothingCases, normalCases) = cases.partition(_.body.tpe.isNothingType) + nnInfo = nothingCases.foldLeft(nnInfo): + (nni, c) => nni.withRetracted(c.notNullInfo) + if normalCases.nonEmpty then + nnInfo = nnInfo.seq(normalCases.map(_.notNullInfo).reduce(_.alt(_))) + nnInfo + def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType0: Type, pt: Type)(using Context): List[CaseDef] = var caseCtx = ctx var wideSelType = wideSelType0 diff --git a/tests/explicit-nulls/neg/i21380b.scala b/tests/explicit-nulls/neg/i21380b.scala index 83e23053547c..e4d0caa9e32f 100644 --- a/tests/explicit-nulls/neg/i21380b.scala +++ b/tests/explicit-nulls/neg/i21380b.scala @@ -18,4 +18,22 @@ def test3(i: Int) = i match case 1 if x != null => () case _ => x = " " + x.trim() // ok + +def test4(i: Int) = + var x: String | Null = null + var y: String | Null = null + i match + case 1 => x = "1" + case _ => y = " " + x.trim() // error + +def test5(i: Int): String = + var x: String | Null = null + var y: String | Null = null + i match + case 1 => x = "1" + case _ => + y = " " + return y x.trim() // ok \ No newline at end of file From d44147bc6ff5496dab9cc1569274dfb4b673ee09 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 14 Oct 2024 15:21:03 +0200 Subject: [PATCH 114/202] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Lhoták --- compiler/src/dotty/tools/dotc/typer/Nullables.scala | 7 +++++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/explicit-nulls/neg/i21380c.scala | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 62d2ccfb7200..e6d764dc1be4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -52,8 +52,11 @@ object Nullables: val hiTree = if(hiTpe eq hi.typeOpt) hi else TypeTree(hiTpe) TypeBoundsTree(lo, hiTree, alias) - /** A set of val or var references that are known to be not null, - * plus a set of variable references that are once assigned to null. + /** A set of val or var references that are known to be not null + * after the tree finishes executing normally (non-exceptionally), + * plus a set of variable references that are ever assigned to null, + * and may therefore be null if execution of the tree is interrupted + * by an exception. */ case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef]): def isEmpty = this eq NotNullInfo.empty diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5731b44368e6..392a4e18c454 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2367,7 +2367,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val cases2 = cases2x.asInstanceOf[List[CaseDef]] // It is possible to have non-exhaustive cases, and some exceptions are thrown and not caught. - // Therefore, the code in the finallizer and after the try block can only rely on the retracted + // Therefore, the code in the finalizer and after the try block can only rely on the retracted // info from the cases' body. if cases2.nonEmpty then nnInfo = nnInfo.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_))) diff --git a/tests/explicit-nulls/neg/i21380c.scala b/tests/explicit-nulls/neg/i21380c.scala index de3cd5bafd6b..9b7a721fbdf0 100644 --- a/tests/explicit-nulls/neg/i21380c.scala +++ b/tests/explicit-nulls/neg/i21380c.scala @@ -33,7 +33,7 @@ def test4: Int = case _ => x = "" x.length // error // Although the catch block here is exhaustive, it is possible to have non-exhaustive cases, - // and some exceptions are thrown and not caught. Therefore, the code in the finallizer and + // and some exceptions are thrown and not caught. Therefore, the code in the finalizer and // after the try block can only rely on the retracted info from the cases' body. def test5: Int = From f859afe8e4ace35e026600bb784664dcbcdbda98 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 1 Nov 2024 09:07:18 +0100 Subject: [PATCH 115/202] Add terminated info --- .../dotty/tools/dotc/typer/Nullables.scala | 43 ++++++++++++------- .../src/dotty/tools/dotc/typer/Typer.scala | 27 ++++-------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index e6d764dc1be4..30c65771d9c2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -53,37 +53,45 @@ object Nullables: TypeBoundsTree(lo, hiTree, alias) /** A set of val or var references that are known to be not null - * after the tree finishes executing normally (non-exceptionally), + * after the tree finishes executing normally (non-exceptionally), * plus a set of variable references that are ever assigned to null, * and may therefore be null if execution of the tree is interrupted * by an exception. */ - case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef]): + case class NotNullInfo(asserted: Set[TermRef] | Null, retracted: Set[TermRef]): def isEmpty = this eq NotNullInfo.empty def retractedInfo = NotNullInfo(Set(), retracted) + def terminatedInfo = NotNullInfo(null, retracted) + /** The sequential combination with another not-null info */ def seq(that: NotNullInfo): NotNullInfo = if this.isEmpty then that else if that.isEmpty then this - else NotNullInfo( - this.asserted.diff(that.retracted).union(that.asserted), - this.retracted.union(that.retracted)) + else + val newAsserted = + if this.asserted == null || that.asserted == null then null + else this.asserted.diff(that.retracted).union(that.asserted) + val newRetracted = this.retracted.union(that.retracted) + NotNullInfo(newAsserted, newRetracted) /** The alternative path combination with another not-null info. Used to merge - * the nullability info of the two branches of an if. + * the nullability info of the branches of an if or match. */ def alt(that: NotNullInfo): NotNullInfo = - NotNullInfo(this.asserted.intersect(that.asserted), this.retracted.union(that.retracted)) - - def withRetracted(that: NotNullInfo): NotNullInfo = - NotNullInfo(this.asserted, this.retracted.union(that.retracted)) + val newAsserted = + if this.asserted == null then that.asserted + else if that.asserted == null then this.asserted + else this.asserted.intersect(that.asserted) + val newRetracted = this.retracted.union(that.retracted) + NotNullInfo(newAsserted, newRetracted) + end NotNullInfo object NotNullInfo: val empty = new NotNullInfo(Set(), Set()) - def apply(asserted: Set[TermRef], retracted: Set[TermRef]): NotNullInfo = - if asserted.isEmpty && retracted.isEmpty then empty + def apply(asserted: Set[TermRef] | Null, retracted: Set[TermRef]): NotNullInfo = + if asserted != null && asserted.isEmpty && retracted.isEmpty then empty else new NotNullInfo(asserted, retracted) end NotNullInfo @@ -227,7 +235,7 @@ object Nullables: */ @tailrec def impliesNotNull(ref: TermRef): Boolean = infos match case info :: infos1 => - if info.asserted.contains(ref) then true + if info.asserted != null && info.asserted.contains(ref) then true else if info.retracted.contains(ref) then false else infos1.impliesNotNull(ref) case _ => @@ -243,7 +251,9 @@ object Nullables: /** Retract all references to mutable variables */ def retractMutables(using Context) = val mutables = infos.foldLeft(Set[TermRef]()): - (ms, info) => ms.union(info.asserted.filter(_.symbol.is(Mutable))) + (ms, info) => ms.union( + if info.asserted == null then Set.empty + else info.asserted.filter(_.symbol.is(Mutable))) infos.extendWith(NotNullInfo(Set(), mutables)) end extension @@ -516,7 +526,10 @@ object Nullables: && assignmentSpans.getOrElse(sym.span.start, Nil).exists(whileSpan.contains(_)) && ctx.notNullInfos.impliesNotNull(ref) - val retractedVars = ctx.notNullInfos.flatMap(_.asserted.filter(isRetracted)).toSet + val retractedVars = ctx.notNullInfos.flatMap(info => + if info.asserted == null then Set.empty + else info.asserted.filter(isRetracted) + ).toSet ctx.addNotNullInfo(NotNullInfo(Set(), retractedVars)) end whileContext diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 392a4e18c454..5ec9dbbe28b9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1551,13 +1551,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def thenPathInfo = cond1.notNullInfoIf(true).seq(result.thenp.notNullInfo) def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo) - result.withNotNullInfo( - if result.thenp.tpe.isNothingType then - elsePathInfo.withRetracted(thenPathInfo) - else if result.elsep.tpe.isNothingType then - thenPathInfo.withRetracted(elsePathInfo) - else thenPathInfo.alt(elsePathInfo) - ) + result.withNotNullInfo(thenPathInfo.alt(elsePathInfo)) end typedIf /** Decompose function prototype into a list of parameter prototypes and a result @@ -2154,14 +2148,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } private def notNullInfoFromCases(initInfo: NotNullInfo, cases: List[CaseDef])(using Context): NotNullInfo = - var nnInfo = initInfo if cases.nonEmpty then - val (nothingCases, normalCases) = cases.partition(_.body.tpe.isNothingType) - nnInfo = nothingCases.foldLeft(nnInfo): - (nni, c) => nni.withRetracted(c.notNullInfo) - if normalCases.nonEmpty then - nnInfo = nnInfo.seq(normalCases.map(_.notNullInfo).reduce(_.alt(_))) - nnInfo + initInfo.seq(cases.map(_.notNullInfo).reduce(_.alt(_))) + else initInfo def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType0: Type, pt: Type)(using Context): List[CaseDef] = var caseCtx = ctx @@ -2251,7 +2240,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedLabeled(tree: untpd.Labeled)(using Context): Labeled = { val bind1 = typedBind(tree.bind, WildcardType).asInstanceOf[Bind] val expr1 = typed(tree.expr, bind1.symbol.info) - assignType(cpy.Labeled(tree)(bind1, expr1)) + assignType(cpy.Labeled(tree)(bind1, expr1)).withNotNullInfo(expr1.notNullInfo.retractedInfo) } /** Type a case of a type match */ @@ -2301,7 +2290,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Hence no adaptation is possible, and we assume WildcardType as prototype. (from, proto) val expr1 = typedExpr(tree.expr orElse untpd.syntheticUnitLiteral.withSpan(tree.span), proto) - assignType(cpy.Return(tree)(expr1, from)) + assignType(cpy.Return(tree)(expr1, from)).withNotNullInfo(expr1.notNullInfo.terminatedInfo) end typedReturn def typedWhileDo(tree: untpd.WhileDo)(using Context): Tree = @@ -2388,15 +2377,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedThrow(tree: untpd.Throw)(using Context): Tree = val expr1 = typed(tree.expr, defn.ThrowableType) val cap = checkCanThrow(expr1.tpe.widen, tree.span) - val res = Throw(expr1).withSpan(tree.span) + var res = Throw(expr1).withSpan(tree.span) if Feature.ccEnabled && !cap.isEmpty && !ctx.isAfterTyper then // Record access to the CanThrow capabulity recovered in `cap` by wrapping // the type of the `throw` (i.e. Nothing) in a `@requiresCapability` annotation. - Typed(res, + res = Typed(res, TypeTree( AnnotatedType(res.tpe, Annotation(defn.RequiresCapabilityAnnot, cap, tree.span)))) - else res + res.withNotNullInfo(expr1.notNullInfo.terminatedInfo) def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): SeqLiteral = { val elemProto = pt.stripNull().elemType match { From 158af7deed473826b5d16ade6b9472fd89948b6d Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 6 Dec 2024 16:59:32 +0100 Subject: [PATCH 116/202] Fix deep NotNullInfo --- .../dotty/tools/dotc/typer/Applications.scala | 7 ++- .../dotty/tools/dotc/typer/Nullables.scala | 54 +++++++++++++------ .../src/dotty/tools/dotc/typer/Typer.scala | 1 - tests/explicit-nulls/neg/i21619.scala | 15 +++++- 4 files changed, 57 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 41e48f7595dc..96c38bcc80af 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1134,7 +1134,7 @@ trait Applications extends Compatibility { case _ => () else () - fun1.tpe match { + val result = fun1.tpe match { case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs()).withType(err) case TryDynamicCallType => val isInsertedApply = fun1 match { @@ -1208,6 +1208,11 @@ trait Applications extends Compatibility { else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail)) } } + + if result.tpe.isNothingType then + val nnInfo = result.notNullInfo + result.withNotNullInfo(nnInfo.terminatedInfo) + else result } /** Convert expression like diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 30c65771d9c2..2193866893f6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -319,11 +319,29 @@ object Nullables: if !info.isEmpty then tree.putAttachment(NNInfo, info) tree + /* Collect the nullability info from parts of `tree` */ + def collectNotNullInfo(using Context): NotNullInfo = tree match + case Typed(expr, _) => + expr.notNullInfo + case Apply(fn, args) => + val argsInfo = args.map(_.notNullInfo) + val fnInfo = fn.notNullInfo + argsInfo.foldLeft(fnInfo)(_ seq _) + case TypeApply(fn, _) => + fn.notNullInfo + case _ => + // Other cases are handled specially in typer. + NotNullInfo.empty + /* The nullability info of `tree` */ def notNullInfo(using Context): NotNullInfo = - stripInlined(tree).getAttachment(NNInfo) match + val tree1 = stripInlined(tree) + tree1.getAttachment(NNInfo) match case Some(info) if !ctx.erasedTypes => info - case _ => NotNullInfo.empty + case _ => + val nnInfo = tree1.collectNotNullInfo + tree1.withNotNullInfo(nnInfo) + nnInfo /* The nullability info of `tree`, assuming it is a condition that evaluates to `c` */ def notNullInfoIf(c: Boolean)(using Context): NotNullInfo = @@ -404,21 +422,23 @@ object Nullables: end extension extension (tree: Assign) - def computeAssignNullable()(using Context): tree.type = tree.lhs match - case TrackedRef(ref) => - val rhstp = tree.rhs.typeOpt - if ctx.explicitNulls && ref.isNullableUnion then - if rhstp.isNullType || rhstp.isNullableUnion then - // If the type of rhs is nullable (`T|Null` or `Null`), then the nullability of the - // lhs variable is no longer trackable. We don't need to check whether the type `T` - // is correct here, as typer will check it. - tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) - else - // If the initial type is nullable and the assigned value is non-null, - // we add it to the NotNull. - tree.withNotNullInfo(NotNullInfo(Set(ref), Set())) - else tree - case _ => tree + def computeAssignNullable()(using Context): tree.type = + var nnInfo = tree.rhs.notNullInfo + tree.lhs match + case TrackedRef(ref) if ctx.explicitNulls && ref.isNullableUnion => + nnInfo = nnInfo.seq: + val rhstp = tree.rhs.typeOpt + if rhstp.isNullType || rhstp.isNullableUnion then + // If the type of rhs is nullable (`T|Null` or `Null`), then the nullability of the + // lhs variable is no longer trackable. We don't need to check whether the type `T` + // is correct here, as typer will check it. + NotNullInfo(Set(), Set(ref)) + else + // If the initial type is nullable and the assigned value is non-null, + // we add it to the NotNull. + NotNullInfo(Set(ref), Set()) + case _ => + tree.withNotNullInfo(nnInfo) end extension private val analyzedOps = Set(nme.EQ, nme.NE, nme.eq, nme.ne, nme.ZAND, nme.ZOR, nme.UNARY_!) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5ec9dbbe28b9..1e461a5e1cb7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1201,7 +1201,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer untpd.unsplice(tree.expr).putAttachment(AscribedToUnit, ()) typed(tree.expr, underlyingTreeTpe.tpe.widenSkolem) assignType(cpy.Typed(tree)(expr1, tpt), underlyingTreeTpe) - .withNotNullInfo(expr1.notNullInfo) } if (untpd.isWildcardStarArg(tree)) { diff --git a/tests/explicit-nulls/neg/i21619.scala b/tests/explicit-nulls/neg/i21619.scala index 244f993fd4e1..d7af27e3fe64 100644 --- a/tests/explicit-nulls/neg/i21619.scala +++ b/tests/explicit-nulls/neg/i21619.scala @@ -76,4 +76,17 @@ def test5: Unit = catch case _ => val z1: String = x.replace("", "") // error - val z2: String = y.replace("", "") \ No newline at end of file + val z2: String = y.replace("", "") + +def test6 = { + var x: String | Null = "" + var y: String = "" + x = "" + y = if (false) x else 1 match { + case _ => { + x = null + y + } + } + x.replace("", "") // error +} \ No newline at end of file From 00430c042a9031059aefe638caba7c7e2e8c49f5 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sat, 7 Dec 2024 04:28:02 +0100 Subject: [PATCH 117/202] Treat asserted set of terminated NotNullInfo as universal set; fix test --- .../src/dotty/tools/dotc/core/Contexts.scala | 6 ++--- .../dotty/tools/dotc/typer/Nullables.scala | 22 ++++++++++--------- .../src/dotty/tools/dotc/typer/Typer.scala | 2 ++ .../pos/after-termination.scala | 17 ++++++++++++++ .../unsafe-common/unsafe-overload.scala | 12 +++++----- 5 files changed, 40 insertions(+), 19 deletions(-) create mode 100644 tests/explicit-nulls/pos/after-termination.scala diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index d69c7408d0b1..7f5779bb6127 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -777,13 +777,13 @@ object Contexts { extension (c: Context) def addNotNullInfo(info: NotNullInfo) = - c.withNotNullInfos(c.notNullInfos.extendWith(info)) + if c.explicitNulls then c.withNotNullInfos(c.notNullInfos.extendWith(info)) else c def addNotNullRefs(refs: Set[TermRef]) = - c.addNotNullInfo(NotNullInfo(refs, Set())) + if c.explicitNulls then c.addNotNullInfo(NotNullInfo(refs, Set())) else c def withNotNullInfos(infos: List[NotNullInfo]): Context = - if c.notNullInfos eq infos then c else c.fresh.setNotNullInfos(infos) + if !c.explicitNulls || (c.notNullInfos eq infos) then c else c.fresh.setNotNullInfos(infos) def relaxedOverrideContext: Context = c.withModeBits(c.mode &~ Mode.SafeNulls | Mode.RelaxedOverriding) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 2193866893f6..310ca999f4c5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -235,7 +235,7 @@ object Nullables: */ @tailrec def impliesNotNull(ref: TermRef): Boolean = infos match case info :: infos1 => - if info.asserted != null && info.asserted.contains(ref) then true + if info.asserted == null || info.asserted.contains(ref) then true else if info.retracted.contains(ref) then false else infos1.impliesNotNull(ref) case _ => @@ -315,8 +315,8 @@ object Nullables: extension (tree: Tree) /* The `tree` with added nullability attachment */ - def withNotNullInfo(info: NotNullInfo): tree.type = - if !info.isEmpty then tree.putAttachment(NNInfo, info) + def withNotNullInfo(info: NotNullInfo)(using Context): tree.type = + if ctx.explicitNulls && !info.isEmpty then tree.putAttachment(NNInfo, info) tree /* Collect the nullability info from parts of `tree` */ @@ -335,13 +335,15 @@ object Nullables: /* The nullability info of `tree` */ def notNullInfo(using Context): NotNullInfo = - val tree1 = stripInlined(tree) - tree1.getAttachment(NNInfo) match - case Some(info) if !ctx.erasedTypes => info - case _ => - val nnInfo = tree1.collectNotNullInfo - tree1.withNotNullInfo(nnInfo) - nnInfo + if !ctx.explicitNulls then NotNullInfo.empty + else + val tree1 = stripInlined(tree) + tree1.getAttachment(NNInfo) match + case Some(info) if !ctx.erasedTypes => info + case _ => + val nnInfo = tree1.collectNotNullInfo + tree1.withNotNullInfo(nnInfo) + nnInfo /* The nullability info of `tree`, assuming it is a condition that evaluates to `c` */ def notNullInfoIf(c: Boolean)(using Context): NotNullInfo = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1e461a5e1cb7..cea47817bb88 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2849,6 +2849,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) postProcessInfo(vdef1, sym) vdef1.setDefTree + val nnInfo = rhs1.notNullInfo + vdef1.withNotNullInfo(if sym.is(Lazy) then nnInfo.retractedInfo else nnInfo) } private def retractDefDef(sym: Symbol)(using Context): Tree = diff --git a/tests/explicit-nulls/pos/after-termination.scala b/tests/explicit-nulls/pos/after-termination.scala new file mode 100644 index 000000000000..00a57e371281 --- /dev/null +++ b/tests/explicit-nulls/pos/after-termination.scala @@ -0,0 +1,17 @@ +class C(val x: Int, val next: C | Null) + +def test1(x: String | Null, c: C | Null): Int = + return 0 + // We know that the following code is unreachable, + // so we can treat `x`, `c`, and any variable/path non-nullable. + x.length + c.next.x + +def test2(x: String | Null, c: C | Null): Int = + throw new Exception() + x.length + c.next.x + +def fail(): Nothing = ??? + +def test3(x: String | Null, c: C | Null): Int = + fail() + x.length + c.next.x diff --git a/tests/explicit-nulls/unsafe-common/unsafe-overload.scala b/tests/explicit-nulls/unsafe-common/unsafe-overload.scala index e7e551f1bda1..21af320806d8 100644 --- a/tests/explicit-nulls/unsafe-common/unsafe-overload.scala +++ b/tests/explicit-nulls/unsafe-common/unsafe-overload.scala @@ -16,8 +16,8 @@ class S { val o: O = ??? locally { - def h1(hh: String => String) = ??? - def h2(hh: Array[String] => Array[String]) = ??? + def h1(hh: String => String): Unit = ??? + def h2(hh: Array[String] => Array[String]): Unit = ??? def f1(x: String | Null): String | Null = ??? def f2(x: Array[String | Null]): Array[String | Null] = ??? @@ -29,10 +29,10 @@ class S { } locally { - def h1(hh: String | Null => String | Null) = ??? - def h2(hh: Array[String | Null] => Array[String | Null]) = ??? + def h1(hh: String | Null => String | Null): Unit = ??? + def h2(hh: Array[String | Null] => Array[String | Null]): Unit = ??? def g1(x: String): String = ??? - def g2(x: Array[String]): Array[String] = ??? + def g2(x: Array[String]): Array[String] = ??? h1(g1) // error h1(o.g) // error @@ -51,7 +51,7 @@ class S { locally { def g1(x: String): String = ??? - def g2(x: Array[String]): Array[String] = ??? + def g2(x: Array[String]): Array[String] = ??? o.i(g1) // error o.i(g2) // error From 4892e8c3e743dcd7fb2a1b5989ad3bb5baaeb8b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 9 Dec 2024 10:37:50 +0100 Subject: [PATCH 118/202] Spec: Integrate the specification for match types. --- docs/_spec/03-types.md | 354 ++++++++++++++++++++++++++++- docs/_spec/04-basic-definitions.md | 2 + 2 files changed, 355 insertions(+), 1 deletion(-) diff --git a/docs/_spec/03-types.md b/docs/_spec/03-types.md index 6bc7886c5677..4b1293258495 100644 --- a/docs/_spec/03-types.md +++ b/docs/_spec/03-types.md @@ -9,10 +9,12 @@ chapter: 3 ```ebnf Type ::= FunType | TypeLambda + | MatchType | InfixType FunType ::= FunTypeArgs ‘=>’ Type | TypeLambdaParams '=>' Type TypeLambda ::= TypeLambdaParams ‘=>>’ Type +MatchType ::= InfixType ‘match’ <<< TypeCaseClauses >>> InfixType ::= RefinedType | RefinedTypeOrWildcard id [nl] RefinedTypeOrWildcard {id [nl] RefinedTypeOrWildcard} RefinedType ::= AnnotType {[nl] Refinement} @@ -51,6 +53,9 @@ TypeLambdaParam ::= {Annotation} (id | ‘_’) [TypeParamClause] TypeBou TypeParamClause ::= ‘[’ VariantTypeParam {‘,’ VariantTypeParam} ‘]’ VariantTypeParam ::= {Annotation} [‘+’ | ‘-’] (id | ‘_’) [TypeParamClause] TypeBounds +TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } +TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi] + RefineDef ::= ‘val’ ValDef | ‘def’ DefDef | ‘type’ {nl} TypeDef @@ -91,6 +96,7 @@ Type ::= ‘AnyKind‘ | RecursiveThis | UnionType | IntersectionType + | MatchType | SkolemType TypeLambda ::= ‘[‘ TypeParams ‘]‘ ‘=>>‘ Type @@ -127,6 +133,14 @@ RecursiveThis ::= recid ‘.‘ ‘this‘ UnionType ::= Type ‘|‘ Type IntersectionType ::= Type ‘&‘ Type +MatchType ::= Type ‘match‘ ‘<:‘ Type ‘{‘ {TypeCaseClause} ‘}‘ +TypeCaseClause ::= ‘case‘ TypeCasePattern ‘=>‘ Type +TypeCasePattern ::= TypeCapture + | TypeCaseAppliedPattern + | Type +TypeCaseAppliedPattern ::= Type ‘[‘ TypeCasePattern { ‘,‘ TypeCasePattern } ‘]‘ +TypeCapture ::= (id | ‘_‘) TypeBounds + SkolemType ::= ‘∃‘ skolemid ‘:‘ Type TypeOrMethodic ::= Type @@ -141,7 +155,7 @@ MethodTypeParam ::= id ‘:‘ Type PolyType ::= ‘[‘ PolyTypeParams ‘]‘ TypeOrMethodic PolyTypeParams ::= PolyTypeParam {‘,‘ PolyTypeParam} -PolyTypeParam ::= ‘id‘ TypeBounds +PolyTypeParam ::= id TypeBounds TypeAliasOrBounds ::= TypeAlias | TypeBounds @@ -273,6 +287,36 @@ The conversion from the concrete syntax to the abstract syntax works as follows: 3. Create nested [refined types](#refined-types), one for every refined definition. 4. Unless ´\alpha´ was never actually used, wrap the result in a [recursive type](#recursive-types) `{ ´\alpha´ => ´...´ }`. +### Concrete Match Types + +```ebnf +MatchType ::= InfixType ‘match’ <<< TypeCaseClauses >>> +TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } +TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi] +``` + +In the concrete syntax of match types, patterns are arbitrary `InfixType`s, and there is no explicit notion of type capture. +In the abstract syntax, however, captures are made explicit and can only appear as arguments to `TypeCaseAppliedPattern`s. + +If the concrete pattern is `_`, its conversion is the internal type `scala.Any`. +If it is a concrete `InfixType`, it is first converted to an internal type ´P´. +If ´P´ is not a `ParameterizedType`, then use ´P´ as the internal pattern. +Otherwise, ´P´ is recursively converted into a `TypeCasePattern` as follows: + +1. If ´P´ is a `WildcardTypeArg` of the form `? >: ´L´ <: ´H´`, return a `TypeCapture` of the form `_ >: ´L´ <: ´H´`. +2. If ´P´ is a direct type designator `´t´` whose name starts with a lowercase and was not written using backticks, return a `TypeCapture` `´t´ >: ´L´ <: ´H´` where `>: ´L´ <: ´H´` is the declared type definition of `´t´`. +3. If ´P´ is a `ParameterizedType` of the form `´T´[´T_1´, ..., ´T_n´]`: + 1. Recursively convert each ´T_i´ into a pattern ´P_i´. + 2. If ´P_i´ is a `Type` for all ´i´, return ´P´. + 3. Otherwise, return the `TypeCaseAppliedPattern` `´T´[´P_1´, ..., ´P_n´]`. +4. Otherwise, return ´P´. + +This conversion ensures that every `TypeCaseAppliedPattern` recursively contains at least one `TypeCapture`. +Moreover, at the top level, the pattern is never a `TypeCapture`: all `TypeCapture`s are nested within a `TypeCaseAppliedPattern`. + +The bound of the internal `MatchType` is always `<: scala.Any` by default. +It can be overridden in a [type member definition](./04-basic-definitions.html#type-member-definitions). + ### Concrete Type Lambdas ```ebnf @@ -807,6 +851,310 @@ class B extends C[B] with D with E The join of ´A | B´ is ´C[A | B] & D´ +### Match Types + +```ebnf +MatchType ::= Type ‘match‘ ‘<:‘ Type ‘{‘ {TypeCaseClause} ‘}‘ +TypeCaseClause ::= ‘case‘ TypeCasePattern ‘=>‘ Type +TypeCasePattern ::= TypeCapture + | TypeCaseAppliedPattern + | Type +TypeCaseAppliedPattern ::= Type ‘[‘ TypeCasePattern { ‘,‘ TypeCasePattern } ‘]‘ +TypeCapture ::= (id | ‘_‘) TypeBounds +``` + +A match type contains a scrutinee, a list of case clauses, and an upper bound. +The scrutinee and the upper bound must both be proper types. +A match type can be *reduced* to the body of a case clause if the scrutinee matches its pattern, and if it is *provably disjoint* from every earlier pattern. + +#### Legal patterns + +A `TypeCasePattern` is a legal pattern if and only if one of the following is true: + +* It is a `Type`, or +* It is a `TypeCaseAppliedPattern` of the form `´P´[´P_1´, ..., ´P_n´]` where ´P´ is a "pattern-legal type constructor" and for each ´i´, either: + * ´P_i´ is a `TypeCapture`, or + * ´P_i´ is a `Type`, or + * ´P_i´ is a `TypeCaseAppliedPattern`, the type constructor ´P´ is *covariant* in its ´i´th type parameter, and ´P_i´ is recursively a legal pattern. + +A type ´P´ is a "pattern-legal type constructor" if one of the following is true: + +* It is a *class* type constructor, or +* It is the `scala.compiletime.ops.int.S` type constructor, or +* It is an *abstract* type constructor, or +* It is a type lambda with a refined result type of the form `[´a´ >: ´L´ <: ´H´] =>> ´B´ { type ´T´ = ´a´ }` where: + * ´B´ contains no occurrence of ´a´, + * there exists a type member ´T´ in ´B´, and + * the bounds `>: ´L´ <: ´H´` are not any more restrictive than those of ´T´ in ´B´, i.e., `´L´ <: ´(∃ \alpha : B).T´ <: ´H´`. +* It is a type lambda of the form `[´\pm a_1 >: L_1 <: H_1´, ..., ´\pm a_n >: L_n <: H_n´] =>> ´U´` such that: + * Its bounds contain all possible values of its arguments, and + * When applied to the type arguments, it beta-reduces to a new legal `MatchTypeAppliedPattern` that contains exactly one instance of every type capture present in the type arguments. +* It is a concrete type designator with underlying type definition `= ´U´` and ´U´ is recursively a "pattern-legal type constructor". + +##### Examples of legal patterns + +Given the following definitions: + +```scala +class Inv[A] +class Cov[+A] +class Contra[-A] + +class Base { + type Y +} + +type YExtractor[t] = Base { type Y = t } +type ZExtractor[t] = Base { type Z = t } + +type IsSeq[t <: Seq[Any]] = t +``` + +Here are examples of legal patterns: + +```scala +// TypeWithoutCapture's +case Any => // also the desugaring of `case _ =>` when the _ is at the top-level +case Int => +case List[Int] => +case Array[String] => + +// Class type constructors with direct captures +case scala.collection.immutable.List[t] => // not Predef.List; it is a type alias +case Array[t] => +case Contra[t] => +case Either[s, t] => +case Either[s, Contra[Int]] => +case h *: t => +case Int *: t => + +// The S type constructor +case S[n] => + +// An abstract type constructor +// given a [F[_]] or `type F[_] >: L <: H` in scope +case F[t] => + +// Nested captures in covariant position +case Cov[Inv[t]] => +case Cov[Cov[t]] => +case Cov[Contra[t]] => +case Array[h] *: t => // sugar for *:[Array[h], t] +case g *: h *: EmptyTuple => + +// Type aliases +case List[t] => // which is Predef.List, itself defined as `type List[+A] = scala.collection.immutable.List[A]` + +// Refinements (through a type alias) +case YExtractor[t] => +``` + +The following patterns are *not* legal: + +```scala +// Type capture nested two levels below a non-covariant type constructor +case Inv[Cov[t]] => +case Inv[Inv[t]] => +case Contra[Cov[t]] => + +// Type constructor with bounds that do not contain all possible instantiations +case IsSeq[t] => + +// Type refinement where the refined type member is not a member of the parent +case ZExtractor[t] => +``` + +#### Matching + +Given a scrutinee `X` and a match type case `case P => R` with type captures `ts`, matching proceeds in three steps: + +1. Compute instantiations for the type captures `ts'`, and check that they are *specific* enough. +2. If successful, check that `X <:< [ts := ts']P`. +3. If successful, reduce to `[ts := ts']R`. + +The instantiations are computed by the recursive function `matchPattern(X, P, variance, scrutIsWidenedAbstract)`. +At the top level, `variance = 1` and `scrutIsWidenedAbstract = false`. + +`matchPattern` behaves according to what kind is `P`: + +- If `P` is a `TypeWithoutCapture`: + - Do nothing (always succeed). +- If `P` is a `WildcardCapture` `ti = _`: + - If `X` is of the form `_ >: L <: H`, instantiate `ti := H` (anything between `L` and `H` would work here), + - Otherwise, instantiate `ti := X`. +- If `P` is a `TypeCapture` `ti`: + - If `X` is of the form `_ >: L <: H`, + - If `scrutIsWidenedAbstract` is `true`, fail as not specific. + - Otherwise, if `variance = 1`, instantiate `ti := H`. + - Otherwise, if `variance = -1`, instantiate `ti := L`. + - Otherwise, fail as not specific. + - Otherwise, if `variance = 0` or `scrutIsWidenedAbstract` is `false`, instantiate `ti := X`. + - Otherwise, fail as not specific. +- If `P` is a `MatchTypeAppliedPattern` of the form `T[Qs]`: + - Assert: `variance = 1` (from the definition of legal patterns). + - If `T` is a class type constructor of the form `p.C`: + - If `baseType(X, C)` is not defined, fail as not matching. + - Otherwise, it is of the form `q.C[Us]`. + - If `p =:= q` is false, fail as not matching. + - Let `innerScrutIsWidenedAbstract` be true if either `scrutIsWidenedAbstract` or `X` is not a concrete type. + - For each pair of `(Ui, Qi)`, compute `matchPattern(Ui, Qi, vi, innerScrutIsWidenedAbstract)` where `vi` is the variance of the `i`th type parameter of `T`. + - If `T` is `scala.compiletime.ops.int.S`: + - If `n = natValue(X)` is undefined or `n <= 0`, fail as not matching. + - Otherwise, compute `matchPattern(n - 1, Q1, 1, scrutIsWidenedAbstract)`. + - If `T` is an abstract type constructor: + - If `X` is not of the form `F[Us]` or `F =:= T` is false, fail as not matching. + - Otherwise, for each pair of `(Ui, Qi)`, compute `matchPattern(Ui, Qi, vi, scrutIsWidenedAbstract)` where `vi` is the variance of the `i`th type parameter of `T`. + - If `T` is a refined type of the form `Base { type Y = ti }`: + - Let `q` be `X` if `X` is a stable type, or the skolem type `∃α:X` otherwise. + - If `q` does not have a type member `Y`, fail as not matching (that implies that `X <:< Base` is false, because `Base` must have a type member `Y` for the pattern to be legal). + - If `q` is not a skolem type: + - Compute `matchPattern(ti, q.Y, 0, scrutIsWidenedAbstract)`. + - Otherwise, if `q.Y` is a type alias with underlying type definition `= U`: + - Let `U'` be the result of perform type avoidance on `U` to remove references to `q`. + - If successful, compute `matchPattern(ti, U', 0, scrutIsWidenedAbstract)`. + - Otherwise, fail as not specific. + - If `T` is a concrete type alias to a type lambda: + - Let `P'` be the beta-reduction of `P`. + - Compute `matchPattern(P', X, variance, scrutIsWidenedAbstract)`. + +#### Disjointness + +A scrutinee ´X´ is *provably disjoint* from a pattern ´P´ iff it is provably disjoint from the type ´P'´ obtained by replacing every type capture in ´P´ by a wildcard type argument with the same bounds. + +We note ´X ⋔ Y´ to say that ´X´ and ´Y´ are provably disjoint. +Intuitively, that notion is based on the following properties of the Scala language: + +- Single inheritance of classes +- Final classes cannot be extended +- Sealed traits have a known set of direct children +- Constant types with distinct values are nonintersecting +- Singleton paths to distinct `enum` case values are nonintersecting + +However, a precise definition of provably-disjoint is complicated and requires some helpers. +We start with the notion of "simple types", which are a minimal subset of Scala internal types that capture the concepts mentioned above. + +A "simple type" is one of: + +- `Nothing` +- `AnyKind` +- ´p.C[...X_i]´ a possibly parameterized class type, where ´p´ and ´...X_i´ are arbitrary types (not just simple types) +- ´c´ a literal type +- ´p.C.x´ where `C` is an `enum` class and `x` is one of its value `case`s +- ´X_1 & X_2´ where ´X_1´ and ´X_2´ are both simple types +- ´X_1 | X_2´ where ´X_1´ and ´X_2´ are both simple types +- `´[...a_i]´ =>> ´X_1´` where ´X_1´ is a simple type + +We define ´⌈X⌉´ a function from a full Scala type to a simple type. +Intuitively, it returns the "smallest" simple type that is a supertype of `X`. +It is defined as follows: + +- ´⌈X⌉ = X´ if ´X´ is a simple type +- ´⌈X⌉ = ⌈U⌉´ if ´X´ is a stable type but not a simple type and its underlying type is ´U´ +- ´⌈X⌉ = ⌈H⌉´ if ´X´ is a non-class type designator with upper bound ´H´ +- ´⌈X⌉ = ⌈η(X)⌉´ if ´X´ is a polymorphic class type designator, where ´η(X)´ is its eta-expansion +- ´⌈X⌉ = ⌈Y⌉´ if ´X´ is a match type that reduces to ´Y´ +- ´⌈X⌉ = ⌈H⌉´ if ´X´ is a match type that does not reduce and ´H´ is its upper bound +- ´⌈X[...T_i]⌉ = ⌈Y⌉´ where `Y` is the beta-reduction of ´X[...T_i]´ if ´X´ is a type lambda +- ´⌈X[...T_i]⌉ = ⌈⌈X⌉[...T_i]⌉´ if ´X´ is neither a type lambda nor a class type designator +- ´⌈X @a⌉ = ⌈X⌉´ +- `´⌈X´ { ´R´ }´⌉ = ⌈X⌉´` +- `´⌈´{ ´α´ => ´X´ }´ = ⌈X⌉⌉´` +- ´⌈X_1 & X_2⌉ = ⌈X_1⌉ & ⌈X_2⌉´ +- ´⌈X_1 | X_2⌉ = ⌈X_1⌉ | ⌈X_2⌉´ +- `´⌈[...a_i]´ =>> ´X_1⌉ = [...a_i]´ =>> ´⌈X_1⌉´` + +The following properties hold about ´⌈X⌉´ (we have paper proofs for those): + +- ´X <: ⌈X⌉´ for all type ´X´. +- If ´S <: T´, and the subtyping derivation does not use the "lower-bound rule" of ´<:´ anywhere, then ´⌈S⌉ <: ⌈T⌉´. + +The "lower-bound rule" states that ´S <: T´ if ´T = q.X´ and ´q.X´ is a non-class type designator and ´S <: L´ where ´L´ is the lower bound of the underlying type definition of ´q.X´". +That rule is known to break transitivy of subtyping in Scala already. + +Second, we define the relation ´⋔´ on *classes* (including traits and hidden classes of objects) as: + +- ´C ⋔ D´ if `´C ∉´ baseClasses´(D)´` and ´D´ is `final` +- ´C ⋔ D´ if `´D ∉´ baseClasses´(C)´` and ´C´ is `final` +- ´C ⋔ D´ if there exists `class`es `´C' ∈´ baseClasses´(C)´` and `´D' ∈´ baseClasses´(D)´` such that `´C' ∉´ baseClasses´(D')´` and `´D' ∉´ baseClasses´(C')´`. +- ´C ⋔ D´ if ´C´ is `sealed` without anonymous child and ´C_i ⋔ D´ for all direct children ´C_i´ of ´C´ +- ´C ⋔ D´ if ´D´ is `sealed` without anonymous child and ´C ⋔ D_i´ for all direct children ´D_i´ of ´D´ + +We can now define ´⋔´ for *types*. + +For arbitrary types ´X´ and ´Y´, we define ´X ⋔ Y´ as ´⌈X⌉ ⋔ ⌈Y⌉´. + +Two simple types ´S´ and ´T´ are provably disjoint if there is a finite derivation tree for ´S ⋔ T´ using the following rules. +Most rules go by pair, which makes the whole relation symmetric: + +- `Nothing` is disjoint from everything (including itself): + - `Nothing ´⋔ T´` + - `´S ⋔´ Nothing` +- A union type is disjoint from another type if both of its parts are disjoint from that type: + - ´S ⋔ T_1 | T_2´ if ´S ⋔ T_1´ and ´S ⋔ T_2´ + - ´S_1 | S_2 ⋔ T´ if ´S_1 ⋔ T´ and ´S_2 ⋔ T´ +- An intersection type is disjoint from another type if at least one of its parts is disjoint from that type: + - ´S ⋔ T_1 & T_2´ if ´S ⋔ T_1´ or ´S ⋔ T_2´ + - ´S_1 & S_2 ⋔ T´ if ´S_1 ⋔ T´ or ´S_1 ⋔ T´ +- A type lambda is disjoint from any other type that is not a type lambda with the same number of parameters: + - `´[...a_i]´ =>> ´S_1 ⋔ q.D.y´` + - `´[...a_i]´ =>> ´S_1 ⋔ d´` + - `´[...a_i]´ =>> ´S_1 ⋔ q.D[...T_i]´` + - `´p.C.x ⋔ [...b_i]´ =>> ´T_1´` + - `´c ⋔ [...b_i]´ =>> ´T_1´` + - `´p.C[...S_i] ⋔ [...b_i]´ =>> ´T_1´` + - `´[a_1, ..., a_n]´ =>> ´S_1 ⋔ [b_1, ..., b_m]´ =>> ´T_1´` if ´m \neq n´ +- Two type lambdas with the same number of type parameters are disjoint if their result types are disjoint: + - `´[a_1, ..., a_n]´ =>> ´S_1 ⋔ [b_1, ..., b_n]´ =>> ´T_1´` if ´S_1 ⋔ T_1´ +- An `enum` value case is disjoint from any other `enum` value case (identified by either not being in the same `enum` class, or having a different name): + - ´p.C.x ⋔ q.D.y´ if ´C \neq D´ or ´x \neq y´ +- Two literal types are disjoint if they are different: + - ´c ⋔ d´ if ´c \neq d´ +- An `enum` value case is always disjoint from a literal type: + - ´c ⋔ q.D.y´ + - ´p.C.x ⋔ d´ +- An `enum` value case or a constant is disjoint from a class type if it does not extend that class (because it's essentially final): + - ´p.C.x ⋔ q.D[...T_i]´ if `baseType´(p.C.x, D)´` is not defined + - ´p.C[...S_i] ⋔ q.D.y´ if `baseType´(q.D.y, C)´` is not defined + - ´c ⋔ q.D[...T_i]´ if `baseType´(c, D)´` is not defined + - ´p.C[...S_i] ⋔ d´ if `baseType´(d, C)´` is not defined +- Two class types are disjoint if the classes themselves are disjoint, or if there exists a common super type with conflicting type arguments. + - ´p.C[...S_i] ⋔ q.D[...T_i]´ if ´C ⋔ D´ + - ´p.C[...S_i] ⋔ q.D[...T_i]´ if there exists a class ´E´ such that `baseType´(p.C[...S_i], E) = a.E[...A_i]´` and `baseType´(q.D[...T_i], E) = b.E[...B_i]´` and there exists a pair ´(A_i, B_i)´ such that + - ´A_i ⋔ B_i´ and it is in covariant position and there exists a field of that type parameter in ´E´, or + - ´A_i ⋔ B_i´ and it is in invariant position, and: + - there exists a field of that type parameter in ´E´, or + - ´A_i´ cannot be instantiated to `Nothing`, or + - ´B_i´ cannot be instantiated to `Nothing`. + +It is worth noting that this definition disregards prefixes entirely. +´p.C´ and ´q.C´ are never provably disjoint, even if ´p´ could be proven disjoint from ´q´. +It also disregards type members. + +We have a proof sketch of the following property for ´⋔´: + +* If ´S <: T´ and ´T ⋔ U´, then ´S ⋔ U´. + +This is a very desirable property. +It means that if we make the scrutinee of a match type more precise (a subtype) through substitution, and the match type previously reduced, then the match type will still reduce to the same case. + +Note: if ´⋔´ were a "true" disjointness relationship, and not a *provably*-disjoint relationship, that property would trivially hold based on elementary set theoretic properties. +It would amount to proving that if ´S ⊆ T´ and ´T ⋂ U = ∅´, then ´S ⋂ U = ∅´. + +#### Reduction + +The result of reducing `´X´ match { case ´P_1´ => ´R_1; ...;´ case ´P_n´ => ´R_n´ }` can be a type, undefined, or a compile error. + +For ´n \geq 1´, it is specified as: + +* If ´X´ matches ´P_1´ with type capture instantiations `´[...t_i \to t_i']´`: + * If ´X ⋔ P_1´, do not reduce. + * Otherwise, reduce as `´[...t_i \to t_i']R_1´`. +* Otherwise, + * If ´X ⋔ P1´, the result of reducing `´X´ match { case ´P_2´ => ´R_2; ...;´ case ´P_n´ => ´R_n´ }` (i.e., proceed with subsequent cases). + * Otherwise, do not reduce. + +The reduction of an "empty" match type `´X´ match { }` (which cannot be written in user programs) is a compile error. + ### Skolem Types ```ebnf @@ -1141,6 +1489,10 @@ Note that the conditions are not all mutually exclusive. - `´T =´ { ´\beta´ => ´T_1´ }` and ´S´ is a proper type but not a recursive type and ´p' <: [\beta := p]T_1´ where: - ´p´ is ´S´ if ´S´ is a stable type and ´∃ \alpha : S´ otherwise, and - ´p'´ is the result of replacing any top-level recursive type `{ ´\gamma´ => ´Z´ }` in ´p´ with ´[\gamma := p]Z´ (TODO specify this better). +- `´(X´ match <: ´H´ { ... }´) <: T´` if `´X´ match ...` reduces to ´S_1´ and ´S_1 <: T´ +- `´S <: (X´ match <: ´H´ { ... }´)´` if `´X´ match ...` reduces to ´T_1´ and ´S <: T_1´ +- `´(X´ match <: ´H´ { ... }´) <: T´` if ´H <: T´ +- `´(X´ match <: ´H_X´ { case ´P_1´ => ´A_1´; ...; case ´P_n´ => ´A_n´ }´) <: (Y´ match <: ´H_Y´ { case ´Q_1´ => ´B_1´; ...; ´Q_n´ => ´B_n´ }´)´` if ´X =:= Y´ and ´P_i =:= Q_i´ for each ´i´ and ´A_i <: B_i´ for each ´i´ - `´S = (´=> ´S_1)´` and `´T = (´=> ´T_1)´` and ´S_1 <: T_1´. - `´S =´ scala.Null` and: - ´T = q.C[T_1, ..., T_n]´ with ´n \geq 0´ and ´C´ does not derive from `scala.AnyVal` and ´C´ is not the hidden class of an `object`, or diff --git a/docs/_spec/04-basic-definitions.md b/docs/_spec/04-basic-definitions.md index 28eb2d43a627..3fa58fd32bd0 100644 --- a/docs/_spec/04-basic-definitions.md +++ b/docs/_spec/04-basic-definitions.md @@ -220,6 +220,7 @@ TypeDcl ::= id [TypeParamClause] [‘>:’ Type] [‘<:’ Type] Def ::= ‘type’ {nl} TypeDef | ‘opaque‘ ‘type‘ {nl} OpaqueTypeDef TypeDef ::= id [TypeParamClause] ‘=’ Type + | id [TypeParamClause] ‘<:’ Type ‘=’ MatchType OpaqueTypeDef ::= id [TypeParamClause] [‘>:’ Type] [‘<:’ Type] ‘=’ Type ``` @@ -229,6 +230,7 @@ A possibly parameterized _abstract type member_ definition `type ´t´[´\mathit If omitted, ´L´ and ´H´ are implied to be `Nothing` and `scala.Any`, respectively. A possibly parameterized _type alias_ definition `type ´t´[´\mathit{tps}\,´] = ´T´` defines ´t´ to be a concrete type member. +The alternative `type ´t´[´\mathit{tps}\,´] <: ´H´ = ´T´ match { ´Cs´ }` desugars to `type ´t´[´\mathit{tps}\,´] = (´T´ match { ´Cs´ } <: ´H´)`, i.e., a match type with an upper bound, which cannot otherwise be expressed in the concrete syntax. A possibly parameterized _opaque type alias_ definition `opaque type ´t´[´\mathit{tps}\,´] >: ´L´ <: ´H´ = ´T´` defines ´t´ to be an opaque type alias with public bounds `>: ´L´ <: ´H´` and a private alias `= ´T´`. From 705c33ca9ca4ed01e8b11c7928468fbd2a267aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ferreira?= Date: Tue, 10 Dec 2024 11:24:02 +0000 Subject: [PATCH 119/202] Limit exposure to ConcurrentModificationException when sys props are replaced or mutated port of https://github.com/scala/scala/commit/f6859f28bb49193fde83e6020a6a89ce926a91e8 --- .../src/dotty/tools/dotc/config/PathResolver.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/PathResolver.scala b/compiler/src/dotty/tools/dotc/config/PathResolver.scala index 29e6e35855c8..67be0e3587cb 100644 --- a/compiler/src/dotty/tools/dotc/config/PathResolver.scala +++ b/compiler/src/dotty/tools/dotc/config/PathResolver.scala @@ -36,9 +36,16 @@ object PathResolver { /** Values found solely by inspecting environment or property variables. */ object Environment { - private def searchForBootClasspath = ( - systemProperties find (_._1 endsWith ".boot.class.path") map (_._2) getOrElse "" - ) + private def searchForBootClasspath = { + import scala.jdk.CollectionConverters.* + val props = System.getProperties + // This formulation should be immune to ConcurrentModificationExceptions when system properties + // we're unlucky enough to witness a partially published result of System.setProperty or direct + // mutation of the System property map. stringPropertyNames internally uses the Enumeration interface, + // rather than Iterator, and this disables the fail-fast ConcurrentModificationException. + val propNames = props.stringPropertyNames() + propNames.asScala collectFirst { case k if k endsWith ".boot.class.path" => props.getProperty(k) } getOrElse "" + } /** Environment variables which java pays attention to so it * seems we do as well. From 200c038a818ed41d8a07a18b540abd0748a99f12 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 10 Dec 2024 12:24:34 +0100 Subject: [PATCH 120/202] Comment on the empty cases in notNullInfoFromCases. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index cea47817bb88..2c513a41a039 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2147,9 +2147,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } private def notNullInfoFromCases(initInfo: NotNullInfo, cases: List[CaseDef])(using Context): NotNullInfo = - if cases.nonEmpty then - initInfo.seq(cases.map(_.notNullInfo).reduce(_.alt(_))) - else initInfo + if cases.isEmpty then + // Empty cases is not allowed for match tree in the source code, + // but it can be generated by inlining: `tests/pos/i19198.scala`. + initInfo + else cases.map(_.notNullInfo).reduce(_.alt(_)) def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType0: Type, pt: Type)(using Context): List[CaseDef] = var caseCtx = ctx From 31690d45237fb6aab7e0474ee115d7bdfe8a0892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ferreira?= Date: Tue, 10 Dec 2024 13:32:39 +0000 Subject: [PATCH 121/202] improve javaBootClassPath lazy evaluation --- compiler/src/dotty/tools/dotc/config/PathResolver.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/PathResolver.scala b/compiler/src/dotty/tools/dotc/config/PathResolver.scala index 67be0e3587cb..f60727e6bba2 100644 --- a/compiler/src/dotty/tools/dotc/config/PathResolver.scala +++ b/compiler/src/dotty/tools/dotc/config/PathResolver.scala @@ -53,7 +53,8 @@ object PathResolver { def classPathEnv: String = envOrElse("CLASSPATH", "") def sourcePathEnv: String = envOrElse("SOURCEPATH", "") - def javaBootClassPath: String = propOrElse("sun.boot.class.path", searchForBootClasspath) + //using propOrNone/getOrElse instead of propOrElse so that searchForBootClasspath is lazy evaluated + def javaBootClassPath: String = propOrNone("sun.boot.class.path") getOrElse searchForBootClasspath def javaExtDirs: String = propOrEmpty("java.ext.dirs") def scalaHome: String = propOrEmpty("scala.home") From ecf9be9532b9bd4729f4a6ec7358ef59ff96246a Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 10 Dec 2024 16:46:19 +0100 Subject: [PATCH 122/202] Fix Chocolatey publish workflow --- .github/workflows/publish-chocolatey.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-chocolatey.yml b/.github/workflows/publish-chocolatey.yml index 3b31728a50ba..88a8a7913188 100644 --- a/.github/workflows/publish-chocolatey.yml +++ b/.github/workflows/publish-chocolatey.yml @@ -35,5 +35,5 @@ jobs: with: name: scala.nupkg - name: Publish the package to Chocolatey - run: choco push scala.nupkg --source https://push.chocolatey.org/ --api-key ${{ secrets.API-KEY }} + run: choco push scala.${{inputs.version}}.nupkg --source https://push.chocolatey.org/ --api-key ${{ secrets.API-KEY }} \ No newline at end of file From 70cc1a19da85f502fc58c8f0ed4fbe6ff9444e7d Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Tue, 10 Dec 2024 15:38:45 -0500 Subject: [PATCH 123/202] fix: update `scala-cli.jar` path Signed-off-by: Rui Chen --- dist/libexec/cli-common-platform | 2 +- dist/libexec/cli-common-platform.bat | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/libexec/cli-common-platform b/dist/libexec/cli-common-platform index a5906e882bb4..e56f5221dbf2 100644 --- a/dist/libexec/cli-common-platform +++ b/dist/libexec/cli-common-platform @@ -1,3 +1,3 @@ #!/usr/bin/env bash -SCALA_CLI_CMD_BASH=("\"$JAVACMD\"" "-jar \"$PROG_HOME/bin/scala-cli.jar\"") +SCALA_CLI_CMD_BASH=("\"$JAVACMD\"" "-jar \"$PROG_HOME/libexec/scala-cli.jar\"") diff --git a/dist/libexec/cli-common-platform.bat b/dist/libexec/cli-common-platform.bat index 99103266c1d9..45b09f3460e6 100644 --- a/dist/libexec/cli-common-platform.bat +++ b/dist/libexec/cli-common-platform.bat @@ -2,4 +2,4 @@ @rem we need to escape % in the java command path, for some reason this doesnt work in common.bat set "_JAVACMD=!_JAVACMD:%%=%%%%!" -set SCALA_CLI_CMD_WIN="%_JAVACMD%" "-jar" "%_PROG_HOME%\bin\scala-cli.jar" \ No newline at end of file +set SCALA_CLI_CMD_WIN="%_JAVACMD%" "-jar" "%_PROG_HOME%\libexec\scala-cli.jar" From 004cfc5ed76ea34245ca30c9cc3872e86f9e6d5e Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 11 Dec 2024 01:32:40 -0500 Subject: [PATCH 124/202] refactor: improve Given search preference warning **Problem** It wasn't clear what action users was suppose to take to suppress the new-from-3.6 Given search preference warning. **Solution** 1. This refactors the code to give the warning an error code E205. 2. In case of warnings, tell the user to choose -source 3.5 vs 3.7, or use nowarn annotation. --- .../tools/dotc/reporting/ErrorMessageID.scala | 1 + .../dotty/tools/dotc/reporting/messages.scala | 38 +++++++++++++++++++ .../dotty/tools/dotc/typer/Implicits.scala | 22 ++--------- tests/neg/given-triangle.check | 6 +-- tests/warn/i21036a.check | 11 ++++-- tests/warn/i21036b.check | 9 +++-- tests/warn/i21036c.scala | 7 ++++ 7 files changed, 66 insertions(+), 28 deletions(-) create mode 100644 tests/warn/i21036c.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 2c3774b59a9a..d3467fe70c52 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -218,6 +218,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case QuotedTypeMissingID // errorNumber: 202 case DeprecatedAssignmentSyntaxID // errorNumber: 203 case DeprecatedInfixNamedArgumentSyntaxID // errorNumber: 204 + case GivenSearchPriorityID // errorNumber: 205 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 b396aa62f599..75aa553827f2 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3361,3 +3361,41 @@ class DeprecatedInfixNamedArgumentSyntax()(using Context) extends SyntaxMsg(Depr + Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`) def explain(using Context) = "" + +class GivenSearchPriorityWarning( + pt: Type, + cmp: Int, + prev: Int, + winner: TermRef, + loser: TermRef, + isLastOldVersion: Boolean +)(using Context) extends Message(GivenSearchPriorityID): + def kind = MessageKind.PotentialIssue + def choice(nth: String, c: Int) = + if c == 0 then "none - it's ambiguous" + else s"the $nth alternative" + val (change, whichChoice) = + if isLastOldVersion + then ("will change in the future release", "Current choice ") + else ("has changed", "Previous choice") + def warningMessage: String = + i"""Given search preference for $pt between alternatives + | ${loser} + |and + | ${winner} + |$change. + |$whichChoice : ${choice("first", prev)} + |Choice from Scala 3.7 : ${choice("second", cmp)}""" + def migrationHints: String = + i"""Suppress this warning by choosing -source 3.5, -source 3.7, or + |by using @annotation.nowarn("id=205")""" + def ambiguousNote: String = + i""" + | + |Note: $warningMessage""" + def msg(using Context) = + i"""$warningMessage + | + |$migrationHints""" + + def explain(using Context) = "" diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 228206d8fb1e..193cc443b4ae 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -549,10 +549,10 @@ object Implicits: /** An ambiguous implicits failure */ class AmbiguousImplicits(val alt1: SearchSuccess, val alt2: SearchSuccess, val expectedType: Type, val argument: Tree, val nested: Boolean = false) extends SearchFailureType: - private[Implicits] var priorityChangeWarnings: List[Message] = Nil + private[Implicits] var priorityChangeWarnings: List[GivenSearchPriorityWarning] = Nil def priorityChangeWarningNote(using Context): String = - priorityChangeWarnings.map(msg => s"\n\nNote: $msg").mkString + priorityChangeWarnings.map(_.ambiguousNote).mkString def msg(using Context): Message = var str1 = err.refStr(alt1.ref) @@ -1312,7 +1312,7 @@ trait Implicits: // A map that associates a priority change warning (between -source 3.6 and 3.7) // with the candidate refs mentioned in the warning. We report the associated // message if one of the critical candidates is part of the result of the implicit search. - val priorityChangeWarnings = mutable.ListBuffer[(/*critical:*/ List[TermRef], Message)]() + val priorityChangeWarnings = mutable.ListBuffer[(/*critical:*/ List[TermRef], GivenSearchPriorityWarning)]() val sv = Feature.sourceVersion val isLastOldVersion = sv.stable == SourceVersion.`3.6` @@ -1353,21 +1353,7 @@ trait Implicits: cmp match case 1 => (alt2, alt1) case -1 => (alt1, alt2) - def choice(nth: String, c: Int) = - if c == 0 then "none - it's ambiguous" - else s"the $nth alternative" - val (change, whichChoice) = - if isLastOldVersion - then ("will change", "Current choice ") - else ("has changed", "Previous choice") - val msg = - em"""Given search preference for $pt between alternatives - | ${loser.ref} - |and - | ${winner.ref} - |$change. - |$whichChoice : ${choice("first", prev)} - |New choice from Scala 3.7: ${choice("second", cmp)}""" + val msg = GivenSearchPriorityWarning(pt, cmp, prev, winner.ref, loser.ref, isLastOldVersion) val critical = alt1.ref :: alt2.ref :: Nil priorityChangeWarnings += ((critical, msg)) if isLastOldVersion then prev else cmp diff --git a/tests/neg/given-triangle.check b/tests/neg/given-triangle.check index f366c18e78f0..8a05ed4b3129 100644 --- a/tests/neg/given-triangle.check +++ b/tests/neg/given-triangle.check @@ -7,6 +7,6 @@ | (given_B : B) |and | (given_A : A) - |will change. - |Current choice : the first alternative - |New choice from Scala 3.7: the second alternative + |will change in the future release. + |Current choice : the first alternative + |Choice from Scala 3.7 : the second alternative diff --git a/tests/warn/i21036a.check b/tests/warn/i21036a.check index 63d611a6e246..6ce5b94d123f 100644 --- a/tests/warn/i21036a.check +++ b/tests/warn/i21036a.check @@ -1,10 +1,13 @@ --- Warning: tests/warn/i21036a.scala:7:17 ------------------------------------------------------------------------------ +-- [E205] Potential Issue Warning: tests/warn/i21036a.scala:7:17 ------------------------------------------------------- 7 |val y = summon[A] // warn | ^ | Given search preference for A between alternatives | (b : B) | and | (a : A) - | will change. - | Current choice : the first alternative - | New choice from Scala 3.7: the second alternative + | will change in the future release. + | Current choice : the first alternative + | Choice from Scala 3.7 : the second alternative + | + | Suppress this warning by choosing -source 3.5, -source 3.7, or + | by using @annotation.nowarn("id=205") diff --git a/tests/warn/i21036b.check b/tests/warn/i21036b.check index dfa19a0e9bb1..da0639438c86 100644 --- a/tests/warn/i21036b.check +++ b/tests/warn/i21036b.check @@ -1,4 +1,4 @@ --- Warning: tests/warn/i21036b.scala:7:17 ------------------------------------------------------------------------------ +-- [E205] Potential Issue Warning: tests/warn/i21036b.scala:7:17 ------------------------------------------------------- 7 |val y = summon[A] // warn | ^ | Given search preference for A between alternatives @@ -6,5 +6,8 @@ | and | (a : A) | has changed. - | Previous choice : the first alternative - | New choice from Scala 3.7: the second alternative + | Previous choice : the first alternative + | Choice from Scala 3.7 : the second alternative + | + | Suppress this warning by choosing -source 3.5, -source 3.7, or + | by using @annotation.nowarn("id=205") diff --git a/tests/warn/i21036c.scala b/tests/warn/i21036c.scala new file mode 100644 index 000000000000..4015cc8a84bb --- /dev/null +++ b/tests/warn/i21036c.scala @@ -0,0 +1,7 @@ +trait A +trait B extends A +given b: B = ??? +given a: A = ??? + +@annotation.nowarn("id=205") +val y = summon[A] // don't warn \ No newline at end of file From 74417916fd97990a77c80462758ab240e52aa586 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 11 Dec 2024 17:54:57 +0100 Subject: [PATCH 125/202] Update reference, MiMa previous version and sync TASTy version (#22187) * Update reference version to 3.6.3-RC1 (from 3.6.0) * Update mima previous binary verison to 3.6.2 (instead of unofficial 3.6.1) * Set TASTy version to `28.7-experimental-1` - it should have been set when branching of 3.6.3. * We now document better how and when tasty version should be set * Add additional runtime test to ensure we don't emit invalid TASTy version during Release / NIGHTLY releases and the expected version set in build matches version defined in TastyFormat --- project/Build.scala | 63 +++++++++++++++++-- tasty/src/dotty/tools/tasty/TastyFormat.scala | 2 +- .../tools/tasty/BuildTastyVersionTest.scala | 26 ++++++++ 3 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala diff --git a/project/Build.scala b/project/Build.scala index f36171aabbcd..db3f149cbab6 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -93,11 +93,12 @@ object Build { /** Version of the Scala compiler used to build the artifacts. * Reference version should track the latest version pushed to Maven: - * - In main branch it should be the last RC version (using experimental TASTy required for non-bootstrapped tests) + * - In main branch it should be the last RC version * - In release branch it should be the last stable release - * 3.6.0-RC1 was released as 3.6.0 - it's having and experimental TASTy version + * + * Warning: Change of this variable needs to be consulted with `expectedTastyVersion` */ - val referenceVersion = "3.6.0" + val referenceVersion = "3.6.3-RC1" /** Version of the Scala compiler targeted in the current release cycle * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes @@ -105,6 +106,8 @@ object Build { * * Should only be referred from `dottyVersion` or settings/tasks requiring simplified version string, * eg. `compatMode` or Windows native distribution version. + * + * Warning: Change of this variable might require updating `expectedTastyVersion` */ val developedVersion = "3.6.4" @@ -116,6 +119,25 @@ object Build { * During final, stable release is set exactly to `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: + * - https://github.com/scala/scala3/issues/13447#issuecomment-912447107 + * - https://github.com/scala/scala3/issues/14306#issuecomment-1069333516 + * - https://github.com/scala/scala3/pull/19321 + * + * Simplified rules, given 3.$minor.$patch = $developedVersion + * - Major version is always 28 + * - TASTY minor version: + * - in main (NIGHTLY): {if $patch == 0 then $minor else ${minor + 1}} + * - in release branch is always equal to $minor + * - TASTY experimental version: + * - in main (NIGHTLY) is always experimental + * - in release candidate branch is experimental if {patch == 0} + * - in stable release is always non-experimetnal + */ + val expectedTastyVersion = "28.7-experimental-1" + checkReleasedTastyVersion() /** Final version of Scala compiler, controlled by environment variables. */ val dottyVersion = { @@ -149,9 +171,9 @@ object Build { * For a developedVersion `3.M.P` the mimaPreviousDottyVersion should be set to: * - `3.M.0` if `P > 0` * - `3.(M-1).0` if `P = 0` - * 3.6.1 is an exception from this rule - 3.6.0 was a broken release + * 3.6.2 is an exception from this rule - 3.6.0 was a broken release, 3.6.1 was hotfix (unstable) release */ - val mimaPreviousDottyVersion = "3.6.1" + val mimaPreviousDottyVersion = "3.6.2" /** LTS version against which we check binary compatibility. * @@ -2424,6 +2446,9 @@ object Build { settings(disableDocSetting). settings( versionScheme := Some("semver-spec"), + Test / envVars ++= Map( + "EXPECTED_TASTY_VERSION" -> expectedTastyVersion, + ), if (mode == Bootstrapped) Def.settings( commonMiMaSettings, mimaForwardIssueFilters := MiMaFilters.TastyCore.ForwardsBreakingChanges, @@ -2473,6 +2498,34 @@ object Build { case Bootstrapped => commonBootstrappedSettings }) } + + /* Tests TASTy version invariants during NIGHLY, RC or Stable releases */ + def checkReleasedTastyVersion(): Unit = { + lazy val (scalaMinor, scalaPatch, scalaIsRC) = baseVersion.split("\\.|-").take(4) match { + case Array("3", minor, patch) => (minor.toInt, patch.toInt, false) + case Array("3", minor, patch, _) => (minor.toInt, patch.toInt, true) + case other => sys.error(s"Invalid Scala base version string: $baseVersion") + } + lazy val (tastyMinor, tastyIsExperimental) = expectedTastyVersion.split("\\.|-").take(4) match { + case Array("28", minor) => (minor.toInt, false) + case Array("28", minor, "experimental", _) => (minor.toInt, true) + case other => sys.error(s"Invalid TASTy version string: $expectedTastyVersion") + } + + if(isNightly) { + assert(tastyIsExperimental, "TASTY needs to be experimental in nightly builds") + val expectedTastyMinor = if(scalaPatch == 0) scalaMinor else scalaMinor + 1 + assert(tastyMinor == expectedTastyMinor, "Invalid TASTy minor version") + } + + if(isRelease) { + assert(scalaMinor == tastyMinor, "Minor versions of TASTY vesion and Scala version should match in release builds") + if (scalaIsRC && scalaPatch == 0) + assert(tastyIsExperimental, "TASTy should be experimental when releasing a new minor version RC") + else + assert(!tastyIsExperimental, "Stable version cannot use experimental TASTY") + } + } } object ScaladocConfigs { diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 8f5f9d57a8a5..8ff590fefec5 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 = 6 + final val MinorVersion: Int = 7 /** Natural Number. The `ExperimentalVersion` allows for * experimentation with changes to TASTy without committing diff --git a/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala b/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala new file mode 100644 index 000000000000..d2e62e1f9eb0 --- /dev/null +++ b/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala @@ -0,0 +1,26 @@ +package dotty.tools.tasty + +import org.junit.Assert._ +import org.junit.Test + +import TastyBuffer._ + +// Tests ensuring TASTY version emitted by compiler is matching expected TASTY version +class BuildTastyVersionTest { + + val CurrentTastyVersion = TastyVersion(TastyFormat.MajorVersion, TastyFormat.MinorVersion, TastyFormat.ExperimentalVersion) + + // Needs to be defined in build Test/envVars + val ExpectedTastyVersionEnvVar = "EXPECTED_TASTY_VERSION" + + @Test def testBuildTastyVersion(): Unit = { + val expectedVersion = sys.env.get(ExpectedTastyVersionEnvVar) + .getOrElse(fail(s"Env variable $ExpectedTastyVersionEnvVar not defined")) + .match { + case s"$major.$minor-experimental-$experimental" => TastyVersion(major.toInt, minor.toInt, experimental.toInt) + case s"$major.$minor" if minor.forall(_.isDigit) => TastyVersion(major.toInt, minor.toInt, 0) + case other => fail(s"Invalid TASTY version string: $other") + } + assertEquals(CurrentTastyVersion, expectedVersion) + } +} From 93ef8107c78d9a311f01716224130d7a7a5bd784 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Thu, 12 Dec 2024 10:17:29 +0100 Subject: [PATCH 126/202] Remove tests/pos-with-compiler-cc from VSCode ignored files --- .vscode-template/settings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode-template/settings.json b/.vscode-template/settings.json index 257da27b118f..8cf2d29e3bae 100644 --- a/.vscode-template/settings.json +++ b/.vscode-template/settings.json @@ -9,7 +9,6 @@ "**/*.class": true, "**/*.tasty": true, "**/target/": true, - "community-build/community-projects": true, - "tests/pos-with-compiler-cc/dotc/**/*.scala": true + "community-build/community-projects": true } } From 5b3d82a41aafcaccab99bad95aa5a035a5dacabb Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 12 Dec 2024 15:21:33 +0100 Subject: [PATCH 127/202] Fix layout of released SDK archives, restore intermiediete top-level directory (#22199) Fixes #22194 Restores top-level directory `scala3-${version}` that is present in artifacts published before Scala 3.6, removed during hotfix 3.6.1 release. We now follow the [Well formed SDK archives layout](https://github.com/sdkman/sdkman-cli/wiki/Well-formed-SDK-archives). Removing the top-level directory even though at first glance looked like an improvement was in fact introducing problems to multiple package managers and build tools. --- .github/workflows/ci.yaml | 12 +++--------- project/Build.scala | 9 ++++++++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a2006e16c7e8..cc1eb5d40d97 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -801,19 +801,13 @@ jobs: distDir="$3" # Build binaries - ./project/scripts/sbt "${sbtProject}/Universal/stage" + ./project/scripts/sbt "all ${sbtProject}/Universal/packageBin ${sbtProject}/Universal/packageZipTarball" - outputPath="${distDir}/target/universal/stage" artifactName="scala3-${{ env.RELEASE_TAG }}${distroSuffix}" - zipArchive="${artifactName}.zip" - tarGzArchive="${artifactName}.tar.gz" - - cwd=$(pwd) - (cd $outputPath && zip -r ${zipArchive} . && mv ${zipArchive} "${cwd}/") - tar -czf ${tarGzArchive} -C "$outputPath" . # Caluclate SHA for each of archive files - for file in "${zipArchive}" "${tarGzArchive}"; do + for file in "${artifactName}.zip" "${artifactName}.tar.gz"; do + mv ${distDir}/target/universal/$file $file sha256sum "${file}" > "${file}.sha256" done } diff --git a/project/Build.scala b/project/Build.scala index db3f149cbab6..5aec4a4231a6 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2257,7 +2257,14 @@ object Build { // ======== Universal / stage := (Universal / stage).dependsOn(republish).value, Universal / packageBin := (Universal / packageBin).dependsOn(republish).value, - Universal / packageZipTarball := (Universal / packageZipTarball).dependsOn(republish).value, + Universal / packageZipTarball := (Universal / packageZipTarball).dependsOn(republish) + .map { archiveFile => + // Rename .tgz to .tar.gz for consistency with previous versions + val renamedFile = archiveFile.getParentFile() / archiveFile.getName.replaceAll("\\.tgz$", ".tar.gz") + IO.move(archiveFile, renamedFile) + renamedFile + } + .value, // ======== Universal / mappings ++= directory(dist.base / "bin"), Universal / mappings ++= directory(republishRepo.value / "maven2"), From 6b9f9f797e78f32b135b7cc6bc941e10d073dd7f Mon Sep 17 00:00:00 2001 From: kasiaMarek Date: Fri, 6 Dec 2024 18:37:53 +0100 Subject: [PATCH 128/202] Add type parameters derived from enum to default param getters of enum cases --- .../src/dotty/tools/dotc/ast/Desugar.scala | 19 ++++++++++++------- tests/pos/i22137.scala | 5 +++++ 2 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 tests/pos/i22137.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 56c153498f87..a95e64e24b85 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -694,15 +694,15 @@ object desugar { val originalTparams = constr1.leadingTypeParams val originalVparamss = asTermOnly(constr1.trailingParamss) lazy val derivedEnumParams = enumClass.typeParams.map(derivedTypeParamWithVariance) - val impliedTparams = - if (isEnumCase) { + val enumTParams = + if isEnumCase then val tparamReferenced = typeParamIsReferenced( - enumClass.typeParams, originalTparams, originalVparamss, parents) - if (originalTparams.isEmpty && (parents.isEmpty || tparamReferenced)) + enumClass.typeParams, originalTparams, originalVparamss, parents) + if originalTparams.isEmpty && (parents.isEmpty || tparamReferenced) then derivedEnumParams.map(tdef => tdef.withFlags(tdef.mods.flags | PrivateLocal)) - else originalTparams - } - else originalTparams + else Nil + else Nil + val impliedTparams = enumTParams ++ originalTparams if mods.is(Trait) then for vparams <- originalVparamss; vparam <- vparams do @@ -735,6 +735,11 @@ object desugar { derived.withAnnotations(Nil) val constr = cpy.DefDef(constr1)(paramss = joinParams(constrTparams, constrVparamss)) + if enumTParams.nonEmpty then + defaultGetters = defaultGetters.map: + case ddef: DefDef => + val tParams = enumTParams.map(tparam => toMethParam(tparam, KeepAnnotations.All)) + cpy.DefDef(ddef)(paramss = joinParams(tParams, ddef.trailingParamss)) val (normalizedBody, enumCases, enumCompanionRef) = { // Add constructor type parameters and evidence implicit parameters diff --git a/tests/pos/i22137.scala b/tests/pos/i22137.scala new file mode 100644 index 000000000000..b52dd9171146 --- /dev/null +++ b/tests/pos/i22137.scala @@ -0,0 +1,5 @@ +enum Parser[+Value]: + case Success(value: Value, issues: Seq[Failure] = Seq.empty) extends Parser[Value] + case Failure(exception: Throwable) extends Parser[Nothing] + +val v = Parser.Success(1) \ No newline at end of file From 0589be3356a274700bf7f69d709eb539c2d75f8b Mon Sep 17 00:00:00 2001 From: Seth Tisue Date: Thu, 12 Dec 2024 18:07:29 -0800 Subject: [PATCH 129/202] REPL: JLine: follow recommendation to use JNI, not JNA as per the https://github.com/jline/jline3 readme fixes #22201 --- dist/libexec/common-shared | 4 +--- project/Build.scala | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dist/libexec/common-shared b/dist/libexec/common-shared index 8c85993a5283..fa1e62c09241 100644 --- a/dist/libexec/common-shared +++ b/dist/libexec/common-shared @@ -28,7 +28,7 @@ function onExit() { # to reenable echo if we are interrupted before completing. trap onExit INT TERM EXIT -unset cygwin mingw msys darwin conemu +unset cygwin mingw msys darwin # COLUMNS is used together with command line option '-pageWidth'. if command -v tput >/dev/null 2>&1; then @@ -57,8 +57,6 @@ esac unset CYGPATHCMD if [[ ${cygwin-} || ${mingw-} || ${msys-} ]]; then - # ConEmu terminal is incompatible with jna-5.*.jar - [[ (${CONEMUANSI-} || ${ConEmuANSI-}) ]] && conemu=true # cygpath is used by various windows shells: cygwin, git-sdk, gitbash, msys, etc. CYGPATHCMD=`which cygpath 2>/dev/null` case "$TERM" in diff --git a/project/Build.scala b/project/Build.scala index 5aec4a4231a6..9ce706e9fe8f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -765,7 +765,7 @@ object Build { Dependencies.compilerInterface, "org.jline" % "jline-reader" % "3.27.0", // used by the REPL "org.jline" % "jline-terminal" % "3.27.0", - "org.jline" % "jline-terminal-jna" % "3.27.0", // needed for Windows + "org.jline" % "jline-terminal-jni" % "3.27.0", // needed for Windows ("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13), ), From e5e4c4039f7e141209fdb7f845a2a6cfcb77821b Mon Sep 17 00:00:00 2001 From: Seth Tisue Date: Thu, 12 Dec 2024 18:32:15 -0800 Subject: [PATCH 130/202] JLine 3.27.1 (was 3.27.0) --- project/Build.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 9ce706e9fe8f..98e26858bf79 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -763,9 +763,9 @@ object Build { libraryDependencies ++= Seq( "org.scala-lang.modules" % "scala-asm" % "9.7.0-scala-2", // used by the backend Dependencies.compilerInterface, - "org.jline" % "jline-reader" % "3.27.0", // used by the REPL - "org.jline" % "jline-terminal" % "3.27.0", - "org.jline" % "jline-terminal-jni" % "3.27.0", // needed for Windows + "org.jline" % "jline-reader" % "3.27.1", // used by the REPL + "org.jline" % "jline-terminal" % "3.27.1", + "org.jline" % "jline-terminal-jni" % "3.27.1", // needed for Windows ("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13), ), From 5e54eba09f9a3ff71031324b00ba577c7255039b Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 14 Dec 2024 18:07:24 +0100 Subject: [PATCH 131/202] Merge -Xno-decode-stacktraces with -Xno-enrich-error-messages The two mean very similar things and -Xno-enrich-error-messages is better documented, so more people will know to reach for it. Keep -Xno-decode-stacktraces as an alias for -Xno-enrich-error-messages for now in order to maintain backwards compatibility --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 3 +-- compiler/src/dotty/tools/dotc/core/TypeErrors.scala | 2 +- compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 6ef33d24f8be..0cec6aeaa661 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -353,8 +353,7 @@ private sealed trait XSettings: val XreadComments: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xread-docs", "Read documentation from tasty.") /** Area-specific debug output */ - val XnoDecodeStacktraces: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.") - val XnoEnrichErrorMessages: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xno-enrich-error-messages", "Show raw error messages, instead of enriching them with contextual information.") + val XnoEnrichErrorMessages: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xno-enrich-error-messages", "Show raw error messages, instead of enriching them with contextual information.", aliases = List("Xno-decode-stacktraces")) val XdebugMacros: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xdebug-macros", "Show debug info when quote pattern match fails") /** Pipeline compilation options */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 1c9696da67d1..40927dd3f40f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -137,7 +137,7 @@ object handleRecursive: e def apply(op: String, details: => String, exc: Throwable, weight: Int = 1)(using Context): Nothing = - if ctx.settings.XnoDecodeStacktraces.value then + if ctx.settings.XnoEnrichErrorMessages.value then throw exc else exc match case _: RecursionOverflow => diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 7fd6444746ce..4cc67bee224c 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -392,7 +392,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { } catch case ex: Throwable => - if !ctx.settings.XnoDecodeStacktraces.value + if !ctx.settings.XnoEnrichErrorMessages.value && handleRecursive.underlyingStackOverflowOrNull(ex) != null then throw StackSizeExceeded(mdef) else From e926badd7e7c9d5cf5473559769ad8dce0302c55 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 14 Dec 2024 18:09:18 +0100 Subject: [PATCH 132/202] Update Maintenance.md New: Anna Herlihy for named tuples --- MAINTENANCE.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MAINTENANCE.md b/MAINTENANCE.md index 05b90cc90b86..47339e17eee5 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -69,7 +69,7 @@ The following is the list of all the principal areas of the compiler and the int - Parser: @odersky, @hamzaremmal, @KacperFKorban - Typer: @odersky, @smarter, (@dwijnand), @noti0nal, @EugeneFlesselle, @KacperFKorban, @bracevac - Erasure: @smarter, @odersky -- Enums: +- Enums: - Derivation & Mirrors: (@dwijnand), @EugeneFlesselle - Export: @odersky - Pattern Matching: @dwijnand, @sjrd, @noti0na1 @@ -77,7 +77,7 @@ The following is the list of all the principal areas of the compiler and the int - Metaprogramming (Quotes, Reflect, Staging): @jchyb, @hamzaremmal - Match types: @sjrd, @dwijnand, @Linyxus, @EugeneFlesselle - GADT: @dwijnand, @Linyxus -- Initialization checker: +- Initialization checker: - Transforms: @sjrd, @odersky, @smarter - Tailrec: @sjrd, @mbovel - JS backend: @sjrd @@ -87,7 +87,7 @@ The following is the list of all the principal areas of the compiler and the int - Safe nulls (experimental): @noti0na1 - Capture checker (experimental): @odersky, @Linyxus, @bracevac, @noti0na1 - Modularity (experimental): @KacperFKorban -- Named Tuples (experimental): @odersky +- Named Tuples (experimental): @odersky, @aherlihy ### Tooling - REPL: @dwijnand @@ -95,9 +95,9 @@ The following is the list of all the principal areas of the compiler and the int - IDE: @tgodzik, (@kasiaMarek) - Scaladoc: @Florian3k - SemanticDB: @natsukagami, (@tanishiking) -- Coverage: @KacperFKorban +- Coverage: @KacperFKorban - Linting (especially unused warnings) / Reporting UX: @KacperFKorban -- Presentation Compiler: @rochala, @tgodzik, @kasiaMarek, @natsukagami +- Presentation Compiler: @rochala, @tgodzik, @kasiaMarek, @natsukagami - Debug Adapter: @adpi2, (@tgodzik) - Scastie: @rochala From 9d36dd19666259e1a060358d42f5b1ee1119884e Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 14 Dec 2024 19:44:51 +0100 Subject: [PATCH 133/202] Fix ScalaSettingsTest --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 2 +- .../test/dotty/tools/dotc/config/ScalaSettingsTests.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 0cec6aeaa661..6a8a88a429e5 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -486,7 +486,7 @@ private sealed trait YSettings: @deprecated(message = "Lifted to -X, Scheduled for removal.", since = "3.5.0") val YreadComments: Setting[Boolean] = BooleanSetting(ForkSetting, "Yread-docs", "Read documentation from tasty.", deprecation = Deprecation.renamed("-Xread-docs")) @deprecated(message = "Lifted to -X, Scheduled for removal.", since = "3.5.0") - val YnoDecodeStacktraces: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.", deprecation = Deprecation.renamed("-Xno-decode-stacktraces")) + val YnoDecodeStacktraces: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.", deprecation = Deprecation.renamed("-Xno-enrich-error-messages")) @deprecated(message = "Lifted to -X, Scheduled for removal.", since = "3.5.0") val YnoEnrichErrorMessages: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-enrich-error-messages", "Show raw error messages, instead of enriching them with contextual information.", deprecation = Deprecation.renamed("-Xno-enrich-error-messages")) @deprecated(message = "Lifted to -X, Scheduled for removal.", since = "3.5.0") diff --git a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala index a412848eaa98..07834684d33b 100644 --- a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala @@ -105,7 +105,7 @@ class ScalaSettingsTests: createTestCase(settings.YdropComments , settings.XdropComments), createTestCase(settings.YcookComments , settings.XcookComments), createTestCase(settings.YreadComments , settings.XreadComments), - createTestCase(settings.YnoDecodeStacktraces , settings.XnoDecodeStacktraces), + createTestCase(settings.YnoDecodeStacktraces , settings.XnoEnrichErrorMessages), createTestCase(settings.YnoEnrichErrorMessages, settings.XnoEnrichErrorMessages), createTestCase(settings.YdebugMacros , settings.XdebugMacros), // createTestCase(settings.YjavaTasty , settings.XjavaTasty), @@ -134,7 +134,7 @@ class ScalaSettingsTests: createTestCase(settings.YdropComments , settings.XdropComments), createTestCase(settings.YcookComments , settings.XcookComments), createTestCase(settings.YreadComments , settings.XreadComments), - createTestCase(settings.YnoDecodeStacktraces , settings.XnoDecodeStacktraces), + createTestCase(settings.YnoDecodeStacktraces , settings.XnoEnrichErrorMessages), createTestCase(settings.YnoEnrichErrorMessages, settings.XnoEnrichErrorMessages), createTestCase(settings.YdebugMacros , settings.XdebugMacros), // createTestCase(settings.YjavaTasty , settings.XjavaTasty), @@ -175,7 +175,7 @@ class ScalaSettingsTests: createTestCase(settings.YdropComments , settings.XdropComments), createTestCase(settings.YcookComments , settings.XcookComments), createTestCase(settings.YreadComments , settings.XreadComments), - createTestCase(settings.YnoDecodeStacktraces , settings.XnoDecodeStacktraces), + createTestCase(settings.YnoDecodeStacktraces , settings.XnoEnrichErrorMessages), createTestCase(settings.YnoEnrichErrorMessages, settings.XnoEnrichErrorMessages), createTestCase(settings.YdebugMacros , settings.XdebugMacros), // createTestCase(settings.YjavaTasty , settings.XjavaTasty), From da176c326fbad4468a7acdcc6fb5df2a80f0c46b Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 16 Dec 2024 11:59:46 +0100 Subject: [PATCH 134/202] Only count associated files of direct members of package objects in dropStale (#22190) possible fix for #17394 --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 4 +++- tests/pos/i17394/_1.scala | 9 +++++++++ tests/pos/i17394/_2.scala | 4 ++++ tests/pos/i17394b/_1.scala | 9 +++++++++ tests/pos/i17394b/_2.scala | 5 +++++ 5 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i17394/_1.scala create mode 100644 tests/pos/i17394/_2.scala create mode 100644 tests/pos/i17394b/_1.scala create mode 100644 tests/pos/i17394b/_2.scala diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index e14e5cf0a728..be651842d9b0 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2539,7 +2539,9 @@ object SymDenotations { ) if compiledNow.exists then compiledNow else - val assocFiles = multi.aggregate(d => Set(d.symbol.associatedFile.nn), _ union _) + val assocFiles = multi + .filterWithPredicate(_.symbol.maybeOwner.isPackageObject) + .aggregate(d => Set(d.symbol.associatedFile.nn), _ union _) if assocFiles.size == 1 then multi // they are all overloaded variants from the same file else diff --git a/tests/pos/i17394/_1.scala b/tests/pos/i17394/_1.scala new file mode 100644 index 000000000000..fdab17ec3f19 --- /dev/null +++ b/tests/pos/i17394/_1.scala @@ -0,0 +1,9 @@ +package example: + def xd: Int = ??? + +package bar: + trait A: + def foo: String = ??? + +package object example extends bar.A: + def foo(x: String): String = ??? diff --git a/tests/pos/i17394/_2.scala b/tests/pos/i17394/_2.scala new file mode 100644 index 000000000000..5ac6a31a66b2 --- /dev/null +++ b/tests/pos/i17394/_2.scala @@ -0,0 +1,4 @@ +import example.* + +@main def main = + val _ = foo diff --git a/tests/pos/i17394b/_1.scala b/tests/pos/i17394b/_1.scala new file mode 100644 index 000000000000..fdab17ec3f19 --- /dev/null +++ b/tests/pos/i17394b/_1.scala @@ -0,0 +1,9 @@ +package example: + def xd: Int = ??? + +package bar: + trait A: + def foo: String = ??? + +package object example extends bar.A: + def foo(x: String): String = ??? diff --git a/tests/pos/i17394b/_2.scala b/tests/pos/i17394b/_2.scala new file mode 100644 index 000000000000..8c86ca45e215 --- /dev/null +++ b/tests/pos/i17394b/_2.scala @@ -0,0 +1,5 @@ +//> using options -Wunused:imports + +import example.{given, *} + +@main def main = () From ecccfc9631a97c94a0c23e7beab181ff84462a7c Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 16 Dec 2024 14:04:38 +0000 Subject: [PATCH 135/202] Add hint when found cc language import in repl --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 89f88faebc17..8e5de37a4e20 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3706,7 +3706,11 @@ object Parsers { in.languageImportContext = in.languageImportContext.importContext(imp, NoSymbol) for case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors do if Feature.handleGlobalLanguageImport(prefix, imported) && !outermost then - syntaxError(em"this language import is only allowed at the toplevel", id.span) + val hint = + if ctx.mode.is(Mode.Interactive) then + f"\nTo use this language feature, include the flag `-language:$prefix.$imported` when starting the REPL" + else "" + syntaxError(em"this language import is only allowed at the toplevel$hint", id.span) if allSourceVersionNames.contains(imported) && prefix.isEmpty then if !outermost then syntaxError(em"source version import is only allowed at the toplevel", id.span) From 958371ccb20ad55612d241f71a43e3063dec6328 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 16 Dec 2024 14:06:00 +0000 Subject: [PATCH 136/202] Refine the hint --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 8e5de37a4e20..eb277edcfbce 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3706,11 +3706,15 @@ object Parsers { in.languageImportContext = in.languageImportContext.importContext(imp, NoSymbol) for case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors do if Feature.handleGlobalLanguageImport(prefix, imported) && !outermost then + val location = + if ctx.mode.is(Mode.Interactive) then + "in the REPL" + else "at the toplevel" val hint = if ctx.mode.is(Mode.Interactive) then f"\nTo use this language feature, include the flag `-language:$prefix.$imported` when starting the REPL" else "" - syntaxError(em"this language import is only allowed at the toplevel$hint", id.span) + syntaxError(em"this language import is only allowed $location$hint", id.span) if allSourceVersionNames.contains(imported) && prefix.isEmpty then if !outermost then syntaxError(em"source version import is only allowed at the toplevel", id.span) From 45f0d60e997b8553d56d52367469464d9769f398 Mon Sep 17 00:00:00 2001 From: Seth Tisue Date: Thu, 12 Dec 2024 14:31:45 -0800 Subject: [PATCH 137/202] in CI, use new standard scala/cla-checker action --- .github/PULL_REQUEST_TEMPLATE/fix-issue.md | 2 +- .github/PULL_REQUEST_TEMPLATE/other-pr.md | 2 +- .github/workflows/cla.yml | 17 ++++------------- docs/_docs/contributing/getting-started.md | 2 +- project/scripts/check-cla.sh | 20 -------------------- 5 files changed, 7 insertions(+), 36 deletions(-) delete mode 100755 project/scripts/check-cla.sh diff --git a/.github/PULL_REQUEST_TEMPLATE/fix-issue.md b/.github/PULL_REQUEST_TEMPLATE/fix-issue.md index f7cf22eb59c7..005be5daef4f 100644 --- a/.github/PULL_REQUEST_TEMPLATE/fix-issue.md +++ b/.github/PULL_REQUEST_TEMPLATE/fix-issue.md @@ -8,7 +8,7 @@ assignees: '' ## Fix #XYZ diff --git a/.github/PULL_REQUEST_TEMPLATE/other-pr.md b/.github/PULL_REQUEST_TEMPLATE/other-pr.md index fad49836df92..a9948b717932 100644 --- a/.github/PULL_REQUEST_TEMPLATE/other-pr.md +++ b/.github/PULL_REQUEST_TEMPLATE/other-pr.md @@ -8,7 +8,7 @@ assignees: '' ## Description diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index f370cb2b541c..d052dc5eb6fc 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -3,20 +3,11 @@ on: pull_request: branches-ignore: - 'language-reference-stable' - push: - branches: - - 'language-reference-stable' - merge_group: -permissions: - contents: write - pull-requests: write - jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - run: ./project/scripts/check-cla.sh - if: github.event_name == 'pull_request' - env: - AUTHOR: ${{ github.event.pull_request.user.login }} + - name: Verify CLA + uses: scala/cla-checker@v1 + with: + author: ${{ github.event.pull_request.user.login }} diff --git a/docs/_docs/contributing/getting-started.md b/docs/_docs/contributing/getting-started.md index b6e3e4fac00a..c660edb2400e 100644 --- a/docs/_docs/contributing/getting-started.md +++ b/docs/_docs/contributing/getting-started.md @@ -146,6 +146,6 @@ The main development discussion channels are: [java11]: https://www.oracle.com/java/technologies/javase-jdk11-downloads.html [adopt]: https://adoptopenjdk.net/ [compat]: https://docs.scala-lang.org/overviews/jdk-compatibility/overview.html -[scala-cla]: https://www.lightbend.com/contribute/cla/scala +[scala-cla]: https://contribute.akka.io/cla/scala [dotty-issue]: https://github.com/scala/scala3/issues [dotty-discussion]: https://github.com/scala/scala3/discussions diff --git a/project/scripts/check-cla.sh b/project/scripts/check-cla.sh deleted file mode 100755 index dbb148d3c652..000000000000 --- a/project/scripts/check-cla.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -eux - -echo "Pull request submitted by $AUTHOR"; -if [[ "$AUTHOR" == "github-actions[bot]" || "$AUTHOR" == "dependabot[bot]" ]] ; then - echo "CLA check for $AUTHOR successful"; -else - signed=$(curl -L -s "https://contribute.akka.io/contribute/cla/scala/check/$AUTHOR" | jq -r ".signed"); - if [ "$signed" = "true" ] ; then - echo "CLA check for $AUTHOR successful"; - else - echo "CLA check for $AUTHOR failed"; - echo "Please sign the Scala CLA to contribute to the Scala compiler."; - echo "Go to https://contribute.akka.io/contribute/cla/scala and then"; - echo "comment on the pull request to ask for a new check."; - echo ""; - echo "Check if CLA is signed: https://contribute.akka.io/contribute/cla/scala/check/$AUTHOR"; - exit 1; - fi; -fi; From 09375635d2869063f88cb28ec47b16284ac35d6d Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 16 Dec 2024 13:31:12 +0100 Subject: [PATCH 138/202] Also perform change in error messages --- compiler/src/dotty/tools/dotc/core/TypeErrors.scala | 2 +- compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala | 2 +- tests/neg/i2887b.check | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 40927dd3f40f..8630ea4fc822 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -117,7 +117,7 @@ extends TypeError: em"""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-decode-stacktraces. + |For the unprocessed stack trace, compile with Xno-enrich-error-messages. |A recurring operation is (inner to outer): |${opsString(mostCommon).stripMargin}""" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 4cc67bee224c..7b80c7c80a21 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -941,7 +941,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { em"""Recursion limit exceeded while pickling ${ex.mdef} |in ${ex.mdef.symbol.showLocated}. |You could try to increase the stacksize using the -Xss JVM option. - |For the unprocessed stack trace, compile with -Xno-decode-stacktraces.""", + |For the unprocessed stack trace, compile with -Xno-enrich-error-messages.""", ex.mdef.srcPos) def missing = forwardSymRefs.keysIterator diff --git a/tests/neg/i2887b.check b/tests/neg/i2887b.check index 5bd5f570fbf7..578fe3db573f 100644 --- a/tests/neg/i2887b.check +++ b/tests/neg/i2887b.check @@ -4,7 +4,7 @@ | 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-decode-stacktraces. + | For the unprocessed stack trace, compile with Xno-enrich-error-messages. | A recurring operation is (inner to outer): | | try to instantiate Z[Z] From 2e409e1f50796e2e869c32f4f551f4d0d7a97115 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 16 Dec 2024 21:09:24 +0100 Subject: [PATCH 139/202] Fix the description --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index eb277edcfbce..e9f6b01a99c3 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3706,15 +3706,15 @@ object Parsers { in.languageImportContext = in.languageImportContext.importContext(imp, NoSymbol) for case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors do if Feature.handleGlobalLanguageImport(prefix, imported) && !outermost then - val location = + val desc = if ctx.mode.is(Mode.Interactive) then - "in the REPL" - else "at the toplevel" + "not allowed in the REPL" + else "only allowed at the toplevel" val hint = if ctx.mode.is(Mode.Interactive) then f"\nTo use this language feature, include the flag `-language:$prefix.$imported` when starting the REPL" else "" - syntaxError(em"this language import is only allowed $location$hint", id.span) + syntaxError(em"this language import is $desc$hint", id.span) if allSourceVersionNames.contains(imported) && prefix.isEmpty then if !outermost then syntaxError(em"source version import is only allowed at the toplevel", id.span) From c79b2121bf11ffc2f8b668299c658bb2983bc55a Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 16 Dec 2024 21:09:35 +0100 Subject: [PATCH 140/202] Add repl tests --- .../dotty/tools/repl/ReplCompilerTests.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala index 221eb8acb9de..d32b28647c32 100644 --- a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala +++ b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala @@ -493,6 +493,24 @@ class ReplCompilerTests extends ReplTest: assertTrue(all.head.startsWith("-- [E103] Syntax Error")) assertTrue(all.exists(_.trim().startsWith("| Illegal start of statement: this modifier is not allowed here"))) + @Test def `i16250a`: Unit = initially: + val hints = List( + "this language import is not allowed in the REPL", + "To use this language feature, include the flag `-language:experimental.captureChecking` when starting the REPL" + ) + run("import language.experimental.captureChecking") + val all = lines() + assertTrue(hints.forall(hint => all.exists(_.contains(hint)))) + + @Test def `i16250b`: Unit = initially: + val hints = List( + "this language import is not allowed in the REPL", + "To use this language feature, include the flag `-language:experimental.pureFunctions` when starting the REPL" + ) + run("import language.experimental.pureFunctions") + val all = lines() + assertTrue(hints.forall(hint => all.exists(_.contains(hint)))) + object ReplCompilerTests: private val pattern = Pattern.compile("\\r[\\n]?|\\n"); From 8b7c98c5da12d68af82ef0df5f5da781b78102a7 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 16 Dec 2024 23:22:15 +0100 Subject: [PATCH 141/202] Fix typo --- compiler/src/dotty/tools/dotc/core/TypeErrors.scala | 2 +- tests/neg/i2887b.check | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 8630ea4fc822..4761beae8bd0 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -117,7 +117,7 @@ extends TypeError: em"""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. + |For the unprocessed stack trace, compile with -Xno-enrich-error-messages. |A recurring operation is (inner to outer): |${opsString(mostCommon).stripMargin}""" diff --git a/tests/neg/i2887b.check b/tests/neg/i2887b.check index 578fe3db573f..eb89f8582e5a 100644 --- a/tests/neg/i2887b.check +++ b/tests/neg/i2887b.check @@ -4,7 +4,7 @@ | 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. + | For the unprocessed stack trace, compile with -Xno-enrich-error-messages. | A recurring operation is (inner to outer): | | try to instantiate Z[Z] From 4961d1eaf219cbf920206ba1e15101a7cdc94eef Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Tue, 17 Dec 2024 13:12:35 +0100 Subject: [PATCH 142/202] chore: use sbt/setup-sbt when using ubuntu-latest image --- .github/workflows/build-sdk.yml | 1 + .github/workflows/language-reference.yaml | 1 + .github/workflows/launchers.yml | 14 +++++--------- .github/workflows/scaladoc.yaml | 1 + .github/workflows/test-cc.yml | 1 + 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-sdk.yml b/.github/workflows/build-sdk.yml index b2af623d731a..cd111df1a083 100644 --- a/.github/workflows/build-sdk.yml +++ b/.github/workflows/build-sdk.yml @@ -61,6 +61,7 @@ jobs: distribution: temurin java-version: ${{ inputs.java-version }} cache : sbt + - uses: sbt/setup-sbt@v1 - name: Build and pack the SDK (universal) run : ./project/scripts/sbt dist/Universal/stage - name: Build and pack the SDK (linux x86-64) diff --git a/.github/workflows/language-reference.yaml b/.github/workflows/language-reference.yaml index 7f87b4a453ef..d79f4d029a77 100644 --- a/.github/workflows/language-reference.yaml +++ b/.github/workflows/language-reference.yaml @@ -36,6 +36,7 @@ jobs: distribution: 'temurin' java-version: 17 cache: 'sbt' + - uses: sbt/setup-sbt@v1 - name: Generate reference documentation and test links run: | diff --git a/.github/workflows/launchers.yml b/.github/workflows/launchers.yml index ce3aac235224..4ee07e4bfcc9 100644 --- a/.github/workflows/launchers.yml +++ b/.github/workflows/launchers.yml @@ -20,6 +20,7 @@ jobs: java-version: '17' distribution: 'temurin' cache: 'sbt' + - uses: sbt/setup-sbt@v1 - name: Build and test launcher command run: ./project/scripts/native-integration/bashTests env: @@ -37,9 +38,7 @@ jobs: java-version: '17' distribution: 'temurin' cache: 'sbt' - # https://github.com/actions/runner-images/issues/9369 - - name: Install sbt - run: brew install sbt + - uses: sbt/setup-sbt@v1 - name: Build and test launcher command run: ./project/scripts/native-integration/bashTests env: @@ -58,9 +57,7 @@ jobs: java-version: '17' distribution: 'temurin' cache: 'sbt' - # https://github.com/actions/runner-images/issues/9369 - - name: Install sbt - run: brew install sbt + - uses: sbt/setup-sbt@v1 - name: Build and test launcher command run: ./project/scripts/native-integration/bashTests env: @@ -79,9 +76,7 @@ jobs: java-version: '17' distribution: 'temurin' cache: 'sbt' - # https://github.com/actions/runner-images/issues/9369 - - name: Install sbt - run: brew install sbt + - uses: sbt/setup-sbt@v1 - name: Build and test launcher command run: ./project/scripts/native-integration/bashTests env: @@ -100,6 +95,7 @@ jobs: java-version: '17' distribution: 'temurin' cache: 'sbt' + - uses: sbt/setup-sbt@v1 - name: Build the launcher command run: sbt "dist-win-x86_64/Universal/stage" - name: Run the launcher command tests diff --git a/.github/workflows/scaladoc.yaml b/.github/workflows/scaladoc.yaml index 4f6f5bbfe2fb..d2e3071e765b 100644 --- a/.github/workflows/scaladoc.yaml +++ b/.github/workflows/scaladoc.yaml @@ -37,6 +37,7 @@ jobs: java-version: 17 cache: 'sbt' + - uses: sbt/setup-sbt@v1 - name: Compile and test scala3doc-js run: ./project/scripts/sbt scaladoc-js-main/test diff --git a/.github/workflows/test-cc.yml b/.github/workflows/test-cc.yml index 39d21b5b952f..973323f49703 100644 --- a/.github/workflows/test-cc.yml +++ b/.github/workflows/test-cc.yml @@ -22,5 +22,6 @@ jobs: steps: - name: Git Checkout uses: actions/checkout@v4 + - uses: sbt/setup-sbt@v1 - name: Test with Scala 2 library with CC TASTy run: ./project/scripts/sbt ";set ThisBuild/Build.scala2Library := Build.Scala2LibraryCCTasty ;scala3-bootstrapped/test" From 72848b3576e94a5092a6927ee0ec450d15e6621d Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Tue, 17 Dec 2024 15:56:30 +0100 Subject: [PATCH 143/202] fix: add sbt/setup-sbt for the dependency graph workflow --- .github/workflows/dependency-graph.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dependency-graph.yml b/.github/workflows/dependency-graph.yml index 35af4fa0526d..6a3f8174b2d7 100644 --- a/.github/workflows/dependency-graph.yml +++ b/.github/workflows/dependency-graph.yml @@ -9,6 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: sbt/setup-sbt@v1 - uses: scalacenter/sbt-dependency-submission@v3 env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} From 4285536223705ade783502528914309ca142d959 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 17 Dec 2024 17:56:33 +0100 Subject: [PATCH 144/202] Refactor handling of rechecked types - Always store new types on rechecking - Store them in a hashmap which is associated with the rechecker of the current compilation unit - After rechecking is done, the map is forgotten, unless keepTypes is true. Under keepTypes, then map is kept in an attachment of the unit's root tree. Change in nomenclature: knownType --> nuType rememberType --> setNuType hasRememberedType --> hasNuType --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 46 +++++--- compiler/src/dotty/tools/dotc/cc/Setup.scala | 47 ++++---- .../dotty/tools/dotc/transform/Recheck.scala | 101 +++++++++--------- 3 files changed, 105 insertions(+), 89 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 23e7d8f8ecf8..830d9ad0a4d4 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -223,6 +223,22 @@ object CheckCaptures: checkNotUniversal.traverse(tpe.widen) end checkNotUniversalInUnboxedResult + trait CheckerAPI: + /** Complete symbol info of a val or a def */ + def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type + + extension [T <: Tree](tree: T) + + /** Set new type of the tree if none was installed yet. */ + def setNuType(tpe: Type): Unit + + /** The new type of the tree, or if none was installed, the original type */ + def nuType(using Context): Type + + /** Was a new type installed for this tree? */ + def hasNuType: Boolean + end CheckerAPI + class CheckCaptures extends Recheck, SymTransformer: thisPhase => @@ -243,7 +259,7 @@ class CheckCaptures extends Recheck, SymTransformer: val ccState1 = new CCState // Dotty problem: Rename to ccState ==> Crash in ExplicitOuter - class CaptureChecker(ictx: Context) extends Rechecker(ictx): + class CaptureChecker(ictx: Context) extends Rechecker(ictx), CheckerAPI: /** The current environment */ private val rootEnv: Env = inContext(ictx): @@ -261,10 +277,6 @@ class CheckCaptures extends Recheck, SymTransformer: */ private val todoAtPostCheck = new mutable.ListBuffer[() => Unit] - override def keepType(tree: Tree) = - super.keepType(tree) - || tree.isInstanceOf[Try] // type of `try` needs tp be checked for * escapes - /** Instantiate capture set variables appearing contra-variantly to their * upper approximation. */ @@ -286,8 +298,8 @@ class CheckCaptures extends Recheck, SymTransformer: */ private def interpolateVarsIn(tpt: Tree)(using Context): Unit = if tpt.isInstanceOf[InferredTypeTree] then - interpolator().traverse(tpt.knownType) - .showing(i"solved vars in ${tpt.knownType}", capt) + interpolator().traverse(tpt.nuType) + .showing(i"solved vars in ${tpt.nuType}", capt) for msg <- ccState.approxWarnings do report.warning(msg, tpt.srcPos) ccState.approxWarnings.clear() @@ -501,11 +513,11 @@ class CheckCaptures extends Recheck, SymTransformer: then ("\nThis is often caused by a local capability$where\nleaking as part of its result.", fn.srcPos) else if arg.span.exists then ("", arg.srcPos) else ("", fn.srcPos) - disallowRootCapabilitiesIn(arg.knownType, NoSymbol, + disallowRootCapabilitiesIn(arg.nuType, NoSymbol, i"Type variable $pname of $sym", "be instantiated to", addendum, pos) val param = fn.symbol.paramNamed(pname) - if param.isUseParam then markFree(arg.knownType.deepCaptureSet, pos) + if param.isUseParam then markFree(arg.nuType.deepCaptureSet, pos) end disallowCapInTypeArgs override def recheckIdent(tree: Ident, pt: Type)(using Context): Type = @@ -769,8 +781,8 @@ class CheckCaptures extends Recheck, SymTransformer: */ def checkContains(tree: TypeApply)(using Context): Unit = tree match case ContainsImpl(csArg, refArg) => - val cs = csArg.knownType.captureSet - val ref = refArg.knownType + val cs = csArg.nuType.captureSet + val ref = refArg.nuType capt.println(i"check contains $cs , $ref") ref match case ref: CaptureRef if ref.isTracked => @@ -852,7 +864,7 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => (sym, "") disallowRootCapabilitiesIn( - tree.tpt.knownType, carrier, i"Mutable $sym", "have type", addendum, sym.srcPos) + tree.tpt.nuType, carrier, i"Mutable $sym", "have type", addendum, sym.srcPos) checkInferredResult(super.recheckValDef(tree, sym), tree) finally if !sym.is(Param) then @@ -1533,7 +1545,7 @@ class CheckCaptures extends Recheck, SymTransformer: private val setup: SetupAPI = thisPhase.prev.asInstanceOf[Setup] override def checkUnit(unit: CompilationUnit)(using Context): Unit = - setup.setupUnit(unit.tpdTree, completeDef) + setup.setupUnit(unit.tpdTree, this) collectCapturedMutVars.traverse(unit.tpdTree) if ctx.settings.YccPrintSetup.value then @@ -1676,7 +1688,7 @@ class CheckCaptures extends Recheck, SymTransformer: traverseChildren(tp) if tree.isInstanceOf[InferredTypeTree] then - checker.traverse(tree.knownType) + checker.traverse(tree.nuType) end healTypeParam /** Under the unsealed policy: Arrays are like vars, check that their element types @@ -1716,10 +1728,10 @@ class CheckCaptures extends Recheck, SymTransformer: check(tree) def check(tree: Tree)(using Context) = tree match case TypeApply(fun, args) => - fun.knownType.widen match + fun.nuType.widen match case tl: PolyType => val normArgs = args.lazyZip(tl.paramInfos).map: (arg, bounds) => - arg.withType(arg.knownType.forceBoxStatus( + arg.withType(arg.nuType.forceBoxStatus( bounds.hi.isBoxedCapturing | bounds.lo.isBoxedCapturing)) checkBounds(normArgs, tl) args.lazyZip(tl.paramNames).foreach(healTypeParam(_, _, fun.symbol)) @@ -1739,7 +1751,7 @@ class CheckCaptures extends Recheck, SymTransformer: def traverse(t: Tree)(using Context) = t match case tree: InferredTypeTree => case tree: New => - case tree: TypeTree => checkAppliedTypesIn(tree.withKnownType) + case tree: TypeTree => checkAppliedTypesIn(tree.withType(tree.nuType)) case _ => traverseChildren(t) checkApplied.traverse(unit) end postCheck diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index c5c362dbe8dc..3ce68792088a 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -19,6 +19,7 @@ import printing.{Printer, Texts}, Texts.{Text, Str} import collection.mutable import CCState.* import dotty.tools.dotc.util.NoSourcePosition +import CheckCaptures.CheckerAPI /** Operations accessed from CheckCaptures */ trait SetupAPI: @@ -28,10 +29,9 @@ trait SetupAPI: /** Setup procedure to run for each compilation unit * @param tree the typed tree of the unit to check - * @param recheckDef the recheck method to run on completion of symbols with - * inferred (result-) types + * @param checker the capture checker which will run subsequently. */ - def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit + def setupUnit(tree: Tree, checker: CheckerAPI)(using Context): Unit /** Symbol is a term member of a class that was not capture checked * The info of these symbols is made fluid. @@ -378,15 +378,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tp2 end transformExplicitType - /** Transform type of tree, and remember the transformed type as the type the tree */ - private def transformTT(tree: TypeTree, boxed: Boolean)(using Context): Unit = - if !tree.hasRememberedType then - val transformed = - if tree.isInferred - then transformInferredType(tree.tpe) - else transformExplicitType(tree.tpe, tptToCheck = tree) - tree.rememberType(if boxed then box(transformed) else transformed) - /** Substitute parameter symbols in `from` to paramRefs in corresponding * method or poly types `to`. We use a single BiTypeMap to do everything. * @param from a list of lists of type or term parameter symbols of a curried method @@ -436,7 +427,17 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: atPhase(thisPhase.next)(sym.info) /** A traverser that adds knownTypes and updates symbol infos */ - def setupTraverser(recheckDef: DefRecheck) = new TreeTraverserWithPreciseImportContexts: + def setupTraverser(checker: CheckerAPI) = new TreeTraverserWithPreciseImportContexts: + import checker.* + + /** Transform type of tree, and remember the transformed type as the type the tree */ + private def transformTT(tree: TypeTree, boxed: Boolean)(using Context): Unit = + if !tree.hasNuType then + val transformed = + if tree.isInferred + then transformInferredType(tree.tpe) + else transformExplicitType(tree.tpe, tptToCheck = tree) + tree.setNuType(if boxed then box(transformed) else transformed) /** Transform the type of a val or var or the result type of a def */ def transformResultType(tpt: TypeTree, sym: Symbol)(using Context): Unit = @@ -464,7 +465,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: traverse(parent) case _ => traverseChildren(tp) - addDescription.traverse(tpt.knownType) + addDescription.traverse(tpt.nuType) end transformResultType def traverse(tree: Tree)(using Context): Unit = @@ -504,7 +505,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree @ SeqLiteral(elems, tpt: TypeTree) => traverse(elems) - tpt.rememberType(box(transformInferredType(tpt.tpe))) + tpt.setNuType(box(transformInferredType(tpt.tpe))) case tree: Block => inNestedLevel(traverseChildren(tree)) @@ -537,22 +538,22 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // with special treatment for constructors. def localReturnType = if sym.isConstructor then constrReturnType(sym.info, sym.paramSymss) - else tree.tpt.knownType + else tree.tpt.nuType // A test whether parameter signature might change. This returns true if one of - // the parameters has a remembered type. The idea here is that we store a remembered + // the parameters has a new type installee. The idea here is that we store a new // type only if the transformed type is different from the original. def paramSignatureChanges = tree.match case tree: DefDef => tree.paramss.nestedExists: - case param: ValDef => param.tpt.hasRememberedType - case param: TypeDef => param.rhs.hasRememberedType + case param: ValDef => param.tpt.hasNuType + case param: TypeDef => param.rhs.hasNuType case _ => false // A symbol's signature changes if some of its parameter types or its result type // have a new type installed here (meaning hasRememberedType is true) def signatureChanges = - tree.tpt.hasRememberedType && !sym.isConstructor || paramSignatureChanges + tree.tpt.hasNuType && !sym.isConstructor || paramSignatureChanges // Replace an existing symbol info with inferred types where capture sets of // TypeParamRefs and TermParamRefs are put in correspondence by BiTypeMaps with the @@ -616,7 +617,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: capt.println(i"forcing $sym, printing = ${ctx.mode.is(Mode.Printing)}") //if ctx.mode.is(Mode.Printing) then new Error().printStackTrace() denot.info = newInfo - recheckDef(tree, sym) + completeDef(tree, sym) updateInfo(sym, updatedInfo) case tree: Bind => @@ -833,8 +834,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: /** Run setup on a compilation unit with given `tree`. * @param recheckDef the function to run for completing a val or def */ - def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit = - setupTraverser(recheckDef).traverse(tree)(using ctx.withPhase(thisPhase)) + def setupUnit(tree: Tree, checker: CheckerAPI)(using Context): Unit = + setupTraverser(checker).traverse(tree)(using ctx.withPhase(thisPhase)) // ------ Checks to run after main capture checking -------------------------- diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index d3173cef252d..172ae337d6e6 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -28,18 +28,29 @@ import dotty.tools.dotc.cc.boxed object Recheck: import tpd.* - /** Attachment key for rechecked types of TypeTrees */ - val RecheckedType = Property.Key[Type] - - val addRecheckedTypes = new TreeMap: - override def transform(tree: Tree)(using Context): Tree = - try - val tree1 = super.transform(tree) - tree.getAttachment(RecheckedType) match - case Some(tpe) => tree1.withType(tpe) - case None => tree1 - catch - case _:TypeError => tree + /** Attachment key for a toplevel tree of a unit that contains a map + * from nodes in that tree to their rechecked "new" types + */ + val RecheckedTypes = Property.Key[util.EqHashMap[Tree, Type]] + + /** If tree carries a RecheckedTypes attachment, use the associated `nuTypes` + * map to produce a new tree that contains at each node the type in the + * map as the node's .tpe field + */ + def addRecheckedTypes(tree: Tree)(using Context): Tree = + tree.getAttachment(RecheckedTypes) match + case Some(nuTypes) => + val withNuTypes = new TreeMap: + override def transform(tree: Tree)(using Context): Tree = + try + val tree1 = super.transform(tree) + val tpe = nuTypes.lookup(tree) + if tpe != null then tree1.withType(tpe) else tree1 + catch + case _: TypeError => tree + withNuTypes.transform(tree) + case None => + tree extension (sym: Symbol)(using Context) @@ -61,30 +72,6 @@ object Recheck: val symd = sym.denot symd.validFor.firstPhaseId == phase.id + 1 && (sym.originDenotation ne symd) - extension [T <: Tree](tree: T) - - /** Remember `tpe` as the type of `tree`, which might be different from the - * type stored in the tree itself, unless a type was already remembered for `tree`. - */ - def rememberType(tpe: Type)(using Context): Unit = - if !tree.hasAttachment(RecheckedType) then rememberTypeAlways(tpe) - - /** Remember `tpe` as the type of `tree`, which might be different from the - * type stored in the tree itself - */ - def rememberTypeAlways(tpe: Type)(using Context): Unit = - if tpe ne tree.knownType then tree.putAttachment(RecheckedType, tpe) - - /** The remembered type of the tree, or if none was installed, the original type */ - def knownType: Type = - tree.attachmentOrElse(RecheckedType, tree.tpe) - - def hasRememberedType: Boolean = tree.hasAttachment(RecheckedType) - - def withKnownType(using Context): T = tree.getAttachment(RecheckedType) match - case Some(tpe) => tree.withType(tpe).asInstanceOf[T] - case None => tree - /** Map ExprType => T to () ?=> T (and analogously for pure versions). * Even though this phase runs after ElimByName, ExprTypes can still occur * as by-name arguments of applied types. See note in doc comment for @@ -172,17 +159,32 @@ abstract class Recheck extends Phase, SymTransformer: class Rechecker(@constructorOnly ictx: Context): private val ta = ictx.typeAssigner - /** If true, remember types of all tree nodes in attachments so that they - * can be retrieved with `knownType` - */ - private val keepAllTypes = inContext(ictx) { - ictx.settings.Xprint.value.containsPhase(thisPhase) - } + private val nuTypes = util.EqHashMap[Tree, Type]() + + extension [T <: Tree](tree: T) + + /** Set new type of the tree if none was installed yet and the new type is different + * from the current type. + */ + def setNuType(tpe: Type): Unit = + if nuTypes.lookup(tree) == null && (tpe ne tree.tpe) then nuTypes(tree) = tpe + + /** The new type of the tree, or if none was installed, the original type */ + def nuType(using Context): Type = + val ntpe = nuTypes.lookup(tree) + if ntpe != null then ntpe else tree.tpe + + /** Was a new type installed for this tree? */ + def hasNuType: Boolean = + nuTypes.lookup(tree) != null + end extension - /** Should type of `tree` be kept in an attachment so that it can be retrieved with - * `knownType`? By default true only is `keepAllTypes` hold, but can be overridden. + /** If true, remember the new types of nodes in this compilation unit + * as an attachment in the unit's tpdTree node. By default, this is + * enabled when -Xprint:cc is set. Can be overridden. */ - def keepType(tree: Tree): Boolean = keepAllTypes + def keepNuTypes(using Context): Boolean = + ctx.settings.Xprint.value.containsPhase(thisPhase) /** A map from NamedTypes to the denotations they had before this phase. * Needed so that we can `reset` them after this phase. @@ -343,7 +345,6 @@ abstract class Recheck extends Phase, SymTransformer: def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = val funtpe = recheck(tree.fun) - tree.fun.rememberType(funtpe) // remember type to support later bounds checks funtpe.widen match case fntpe: PolyType => assert(fntpe.paramInfos.hasSameLengthAs(tree.args)) @@ -459,7 +460,7 @@ abstract class Recheck extends Phase, SymTransformer: seqLitType(tree, TypeComparer.lub(declaredElemType :: elemTypes)) def recheckTypeTree(tree: TypeTree)(using Context): Type = - tree.knownType // allows to install new types at Setup + tree.nuType // allows to install new types at Setup def recheckAnnotated(tree: Annotated)(using Context): Type = tree.tpe match @@ -558,7 +559,7 @@ abstract class Recheck extends Phase, SymTransformer: */ def recheckFinish(tpe: Type, tree: Tree, pt: Type)(using Context): Type = val tpe1 = checkConforms(tpe, pt, tree) - if keepType(tree) then tree.rememberType(tpe1) + tree.setNuType(tpe1) tpe1 def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type = @@ -617,6 +618,7 @@ abstract class Recheck extends Phase, SymTransformer: def checkUnit(unit: CompilationUnit)(using Context): Unit = recheck(unit.tpdTree) + if keepNuTypes then unit.tpdTree.putAttachment(RecheckedTypes, nuTypes) end Rechecker @@ -624,7 +626,8 @@ abstract class Recheck extends Phase, SymTransformer: override def show(tree: untpd.Tree)(using Context): String = atPhase(thisPhase): withMode(Mode.Printing): - super.show(addRecheckedTypes.transform(tree.asInstanceOf[tpd.Tree])) + super.show: + addRecheckedTypes(tree.asInstanceOf[tpd.Tree]) end Recheck /** A class that can be used to test basic rechecking without any customaization */ From 80d00f3f0f687bb0a6977c28d5f3f429de0d7b3f Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Wed, 18 Dec 2024 07:18:47 +0900 Subject: [PATCH 145/202] replace deprecated AnyRefMap --- .../src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala | 2 +- .../tools/dotc/classpath/DirectoryClassPath.scala | 2 +- .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- .../dotc/core/unpickleScala2/Scala2Unpickler.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Namer.scala | 10 +++++----- .../src/dotty/tools/dotc/util/FreshNameCreator.scala | 2 +- library/src/scala/runtime/coverage/Invoker.scala | 6 +++--- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 4f4caf36d92a..e632def24700 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -515,7 +515,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { */ object locals { - private val slots = mutable.AnyRefMap.empty[Symbol, Local] // (local-or-param-sym -> Local(BType, name, idx, isSynth)) + private val slots = mutable.HashMap.empty[Symbol, Local] // (local-or-param-sym -> Local(BType, name, idx, isSynth)) private var nxtIdx = -1 // next available index for local-var diff --git a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala index aed5be45cb0d..2d659b532d7b 100644 --- a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala @@ -229,7 +229,7 @@ final class CtSymClassPath(ctSym: java.nio.file.Path, release: Int) extends Clas // e.g. "java.lang" -> Seq(/876/java/lang, /87/java/lang, /8/java/lang)) private val packageIndex: scala.collection.Map[String, scala.collection.Seq[Path]] = { - val index = collection.mutable.AnyRefMap[String, collection.mutable.ListBuffer[Path]]() + val index = collection.mutable.HashMap[String, collection.mutable.ListBuffer[Path]]() val isJava12OrHigher = scala.util.Properties.isJavaAtLeast("12") rootsForRelease.foreach(root => Files.walk(root).iterator().asScala.filter(Files.isDirectory(_)).foreach { p => val moduleNamePathElementCount = if (isJava12OrHigher) 1 else 0 diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index de99ce0105ea..d9ae4ddb6006 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1432,7 +1432,7 @@ class TreeUnpickler(reader: TastyReader, extendOnly(namedArgs) else // needs reordering, and possibly fill in holes for default arguments - val argsByName = mutable.AnyRefMap.from(namedArgs.map(arg => arg.name -> arg)) + val argsByName = mutable.HashMap.from(namedArgs.map(arg => arg.name -> arg)) val reconstructedArgs = formalNames.lazyZip(methType.paramInfos).map { (name, tpe) => argsByName.remove(name).getOrElse(makeDefault(name, tpe)) } diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 611fda9c1d41..3b91312740d1 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -161,7 +161,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas private val entries = new Array[AnyRef](index.length) /** A map from symbols to their associated `decls` scopes */ - private val symScopes = mutable.AnyRefMap[Symbol, Scope]() + private val symScopes = mutable.HashMap[Symbol, Scope]() /** A mapping from method types to the parameters used in constructing them */ private val paramsOfMethodType = new java.util.IdentityHashMap[MethodType, List[Symbol]] diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 87dec93f8040..eaf7380ef5c7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -66,9 +66,9 @@ class Namer { typer: Typer => /** A partial map from unexpanded member and pattern defs and to their expansions. * Populated during enterSyms, emptied during typer. */ - //lazy val expandedTree = new mutable.AnyRefMap[DefTree, Tree] + //lazy val expandedTree = new mutable.HashMap[DefTree, Tree] /*{ - override def default(tree: DefTree) = tree // can't have defaults on AnyRefMaps :-( + override def default(tree: DefTree) = tree // can't have defaults on HashMaps :-( }*/ /** A map from expanded MemberDef, PatDef or Import trees to their symbols. @@ -76,12 +76,12 @@ class Namer { typer: Typer => * with the same symbol is created (this can be when the symbol is completed * or at the latest when the tree is typechecked. */ - //lazy val symOfTree = new mutable.AnyRefMap[Tree, Symbol] + //lazy val symOfTree = new mutable.HashMap[Tree, Symbol] /** A map from expanded trees to their typed versions. * Populated when trees are typechecked during completion (using method typedAhead). */ - // lazy val typedTree = new mutable.AnyRefMap[Tree, tpd.Tree] + // lazy val typedTree = new mutable.HashMap[Tree, tpd.Tree] /** A map from method symbols to nested typers. * Populated when methods are completed. Emptied when they are typechecked. @@ -89,7 +89,7 @@ class Namer { typer: Typer => * one, so that trees that are shared between different DefDefs can be independently * used as indices. It also contains a scope that contains nested parameters. */ - lazy val nestedTyper: mutable.AnyRefMap[Symbol, Typer] = new mutable.AnyRefMap + lazy val nestedTyper: mutable.HashMap[Symbol, Typer] = new mutable.HashMap /** We are entering symbols coming from a SourceLoader */ private var lateCompile = false diff --git a/compiler/src/dotty/tools/dotc/util/FreshNameCreator.scala b/compiler/src/dotty/tools/dotc/util/FreshNameCreator.scala index f3375028c95f..f95b4bf85a96 100644 --- a/compiler/src/dotty/tools/dotc/util/FreshNameCreator.scala +++ b/compiler/src/dotty/tools/dotc/util/FreshNameCreator.scala @@ -14,7 +14,7 @@ abstract class FreshNameCreator { object FreshNameCreator { class Default extends FreshNameCreator { protected var counter: Int = 0 - protected val counters: mutable.Map[String, Int] = mutable.AnyRefMap() withDefaultValue 0 + protected val counters: mutable.Map[String, Int] = mutable.HashMap() withDefaultValue 0 /** * Create a fresh name with the given prefix. It is guaranteed diff --git a/library/src/scala/runtime/coverage/Invoker.scala b/library/src/scala/runtime/coverage/Invoker.scala index c35c6c2ec7df..b3216ec37c67 100644 --- a/library/src/scala/runtime/coverage/Invoker.scala +++ b/library/src/scala/runtime/coverage/Invoker.scala @@ -3,7 +3,7 @@ package scala.runtime.coverage import scala.annotation.internal.sharable import scala.annotation.nowarn import scala.collection.concurrent.TrieMap -import scala.collection.mutable.{BitSet, AnyRefMap} +import scala.collection.mutable.{BitSet, HashMap} import java.io.{File, FileWriter} import java.nio.file.Files @@ -12,7 +12,7 @@ object Invoker { private val runtimeUUID = java.util.UUID.randomUUID() private val MeasurementsPrefix = "scoverage.measurements." - private val threadFiles = new ThreadLocal[AnyRefMap[String, FileWriter]] + private val threadFiles = new ThreadLocal[HashMap[String, FileWriter]] private val dataDirToSet = TrieMap.empty[String, BitSet] /** We record that the given id has been invoked by appending its id to the coverage data file. @@ -38,7 +38,7 @@ object Invoker { if added then var writers = threadFiles.get() if writers == null then - writers = AnyRefMap.empty + writers = HashMap.empty threadFiles.set(writers) val writer = writers.getOrElseUpdate( dataDir, From 54312c68e6dbd7335648d06357104a780fb78bf8 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Wed, 18 Dec 2024 11:18:42 +0100 Subject: [PATCH 146/202] fix: better error messages when an enum derives from AnyVal --- .../src/dotty/tools/dotc/reporting/ErrorMessageID.scala | 1 + compiler/src/dotty/tools/dotc/reporting/messages.scala | 6 ++++++ compiler/src/dotty/tools/dotc/typer/Checking.scala | 7 ++++++- compiler/src/dotty/tools/dotc/typer/Namer.scala | 3 ++- tests/neg/i21944.check | 4 ++++ tests/neg/i21944.scala | 2 ++ 6 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 tests/neg/i21944.check create mode 100644 tests/neg/i21944.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index d3467fe70c52..cc78203e873f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -219,6 +219,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case DeprecatedAssignmentSyntaxID // errorNumber: 203 case DeprecatedInfixNamedArgumentSyntaxID // errorNumber: 204 case GivenSearchPriorityID // errorNumber: 205 + case EnumMayNotBeValueClassesID // errorNumber: 206 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 75aa553827f2..28a2b5757a93 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3399,3 +3399,9 @@ class GivenSearchPriorityWarning( |$migrationHints""" def explain(using Context) = "" + +final class EnumMayNotBeValueClasses(sym: Symbol)(using Context) extends SyntaxMsg(EnumMayNotBeValueClassesID): + def msg(using Context): String = i"$sym may not be a value class" + + def explain(using Context) = "" +end EnumMayNotBeValueClasses diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 1cd531046753..e870ffd0fc90 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -734,11 +734,16 @@ object Checking { case _ => report.error(ValueClassesMayNotContainInitalization(clazz), stat.srcPos) } - if (clazz.isDerivedValueClass) { + // We don't check synthesised enum anonymous classes that are generated from + // enum extending a value class type (AnyVal or an alias of it) + // The error message 'EnumMayNotBeValueClassesID' will take care of generating the error message (See #22236) + if (clazz.isDerivedValueClass && !clazz.isEnumAnonymClass) { if (clazz.is(Trait)) report.error(CannotExtendAnyVal(clazz), clazz.srcPos) if clazz.is(Module) then report.error(CannotExtendAnyVal(clazz), clazz.srcPos) + if (clazz.is(Enum)) + report.error(EnumMayNotBeValueClasses(clazz), clazz.srcPos) if (clazz.is(Abstract)) report.error(ValueClassesMayNotBeAbstract(clazz), clazz.srcPos) if (!clazz.isStatic) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 87dec93f8040..e0af9b2d892a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1626,7 +1626,8 @@ class Namer { typer: Typer => } else { val pclazz = pt.typeSymbol - if pclazz.is(Final) then + // The second condition avoids generating a useless message (See #22236 for more details) + if pclazz.is(Final) && !(pclazz.is(Enum) && pclazz.isDerivedValueClass) then report.error(ExtendFinalClass(cls, pclazz), cls.srcPos) else if pclazz.isEffectivelySealed && pclazz.associatedFile != cls.associatedFile then if pclazz.is(Sealed) && !pclazz.is(JavaDefined) then diff --git a/tests/neg/i21944.check b/tests/neg/i21944.check new file mode 100644 index 000000000000..591447c6a510 --- /dev/null +++ b/tests/neg/i21944.check @@ -0,0 +1,4 @@ +-- [E206] Syntax Error: tests/neg/i21944.scala:1:5 --------------------------------------------------------------------- +1 |enum Orientation extends AnyVal: // error + | ^ + | class Orientation may not be a value class diff --git a/tests/neg/i21944.scala b/tests/neg/i21944.scala new file mode 100644 index 000000000000..bf335e56c671 --- /dev/null +++ b/tests/neg/i21944.scala @@ -0,0 +1,2 @@ +enum Orientation extends AnyVal: // error + case North, South, East, West From 442e6a09eae30c22aad559ecf520713056e86cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 18 Dec 2024 13:52:59 +0100 Subject: [PATCH 147/202] Fix #22226: Use `classOf[BoxedUnit]` for Unit array in `ArrayConstructors`. The `ArrayConstructors` phase rewrites array constructors to calls to `scala.runtime.Arrays.newArray`. When it does that, it must pass the run-time `jl.Class` of the element type. Previously, it used `classOf[Unit]` when creating an `Array[Unit]` (or nested). That is not correct, as from the Java perspective, we need to create `Array[BoxedUnit]`. We now identify `elemType <: Unit` and replace it with `BoxedUnit`. --- This highlights a limitation of the Scala.js backend. We should rewrite calls to `newArray` in the backend to use direct array creation instead. --- .../tools/dotc/transform/ArrayConstructors.scala | 5 ++++- .../testsuite/compiler/RegressionTestScala3.scala | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ArrayConstructors.scala b/compiler/src/dotty/tools/dotc/transform/ArrayConstructors.scala index e94fa612e6cf..84cb64533532 100644 --- a/compiler/src/dotty/tools/dotc/transform/ArrayConstructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/ArrayConstructors.scala @@ -27,7 +27,10 @@ class ArrayConstructors extends MiniPhase { override def transformApply(tree: tpd.Apply)(using Context): tpd.Tree = { def expand(elemType: Type, dims: List[Tree]) = - tpd.newArray(elemType, tree.tpe, tree.span, JavaSeqLiteral(dims, TypeTree(defn.IntClass.typeRef))) + val elemTypeNonVoid = + if elemType.isValueSubType(defn.UnitType) then defn.BoxedUnitClass.typeRef + else elemType + tpd.newArray(elemTypeNonVoid, tree.tpe, tree.span, JavaSeqLiteral(dims, TypeTree(defn.IntClass.typeRef))) if (tree.fun.symbol eq defn.ArrayConstructor) { val TypeApply(tycon, targ :: Nil) = tree.fun: @unchecked diff --git a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala index de260f3481ed..02cb08789232 100644 --- a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala +++ b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala @@ -145,6 +145,17 @@ class RegressionTestScala3 { assertEquals(5, Issue14289.Container.b()) assertEquals(true, Issue14289.Container.c()) } + + @Test def createArrayOfUnitIssue22226(): Unit = { + val a = Array.ofDim[Unit](0) + assertSame(classOf[Array[Unit]], a.getClass()) + + val b = new Array[Unit](0) + assertSame(classOf[Array[Unit]], b.getClass()) + + val c = Array.ofDim[Unit](0, 0) + assertSame(classOf[Array[Array[Unit]]], c.getClass()) + } } object RegressionTestScala3 { From 3ac5f2def9222cdd25436f387ed6883d2d8687f4 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 12 Dec 2024 16:51:27 +0000 Subject: [PATCH 148/202] Teach AvoidMap to strip opaque alias refinements --- .../src/dotty/tools/dotc/core/TypeOps.scala | 21 +++++++++++++- tests/pos/i22068.less-min.scala | 21 ++++++++++++++ tests/pos/i22068.orig.scala | 29 +++++++++++++++++++ tests/pos/i22068.scala | 14 +++++++++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i22068.less-min.scala create mode 100644 tests/pos/i22068.orig.scala create mode 100644 tests/pos/i22068.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 7ae790c62a2c..e80cadcd3b8c 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -511,7 +511,26 @@ object TypeOps: tp else (if pre.isSingleton then NoType else tryWiden(tp, tp.prefix)).orElse { if (tp.isTerm && variance > 0 && !pre.isSingleton) - apply(tp.info.widenExpr) + tp.prefix match + case prefix: TermRef if prefix.name.is(InlineBinderName) && prefix.symbol.denot.is(Synthetic, butNot=InlineProxy) => + prefix.info.widenExpr.dealias match + case info: RefinedType => + // Strip refinements on an opaque alias proxy + // Using pos/i22068 as an example, + // Inliner#addOpaqueProxies add the following opaque alias proxy: + // val $proxy1: foos.type { type Foo[T] = String } = + // foos.$asInstanceOf[foos.type { type Foo[T] = String }] + // Then when InlineCall#expand creates a typed Inlined, + // we type avoid any local bindings, which includes that opaque alias proxy. + // To avoid that the replacement is a non-singleton RefinedType, + // we drop the refinements too and return foos.type. + // That way, when we inline `def m1` and we calculate the asSeenFrom + // of `b1.and(..)` b1 doesn't have an unstable prefix. + derivedSelect(tp, info.stripRefinement) + case _ => + apply(tp.info.widenExpr) + case _ => + apply(tp.info.widenExpr) else if (upper(pre).member(tp.name).exists) super.derivedSelect(tp, pre) else diff --git a/tests/pos/i22068.less-min.scala b/tests/pos/i22068.less-min.scala new file mode 100644 index 000000000000..e520d1d717c0 --- /dev/null +++ b/tests/pos/i22068.less-min.scala @@ -0,0 +1,21 @@ +class A +class B extends A +class C extends A + +object foos: + opaque type Tag[A] = String + object Tag: + inline given mkTag[A]: Tag[A] = ??? + type Full[A] = Tag[A] | Set[A] + sealed trait Set[A] extends Any + case class Union[A](tags: Seq[Tag[Any]]) extends AnyVal with Set[A]: + infix def and[B](t2: Full[B]): Unit = ??? + object Union: + inline given mkUnion[A]: Union[A] = ??? +import foos.Tag.* + +class Test: + inline def m1[K1, K2](using b1: Union[K1], b2: Union[K2]): Unit = + b1.and(b2) + + def t1(): Unit = m1[B | C, A] diff --git a/tests/pos/i22068.orig.scala b/tests/pos/i22068.orig.scala new file mode 100644 index 000000000000..9b2b54037a5a --- /dev/null +++ b/tests/pos/i22068.orig.scala @@ -0,0 +1,29 @@ +trait AnyFreeSpecLike: + inline implicit def convertToFreeSpecStringWrapper(s: String): FreeSpecStringWrapper = ??? + protected final class FreeSpecStringWrapper(string: String): + infix def in(testFun: => Any): Unit = ??? + + +import types.Tag.* +class TagTest extends AnyFreeSpecLike{ + inline def test[T1, T2](using k1: Union[T1], k2: Union[T2]): Unit = + "T1 <:< T2" in { + val kresult = k1 <:< k2 + ??? + } + class A + class B extends A + class C extends A + test[B | C, A] +} + +object types: + opaque type Tag[A] = String + object Tag: + inline given apply[A]: Tag[A] = ??? + type Full[A] = Tag[A] | Set[A] + sealed trait Set[A] extends Any + case class Union[A](tags: Seq[Tag[Any]]) extends AnyVal with Set[A]: + infix def <:<[B](t2: Full[B]): Boolean = ??? + object Union: + inline given apply[A]: Union[A] = ??? diff --git a/tests/pos/i22068.scala b/tests/pos/i22068.scala new file mode 100644 index 000000000000..1af942a2c46e --- /dev/null +++ b/tests/pos/i22068.scala @@ -0,0 +1,14 @@ +object foos: + opaque type Foo[T] = String + object bars: + class Bar1[A] { def and(b: Bar2): Unit = () } + class Bar2 + inline def mkBar1[A]: Bar1[A] = new Bar1[A] + def mkBar2 : Bar2 = new Bar2 +import foos.*, bars.* + +class Test: + inline def m1[X](b1: Bar1[X], b2: Bar2): Unit = + b1.and(b2) + + def t1(): Unit = m1(mkBar1[Int], mkBar2) From 56b60acbc975321a81e52abd5c88e1f785982f46 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 17 Dec 2024 14:11:52 +0000 Subject: [PATCH 149/202] Re-skolem type prefixes post-unpickling --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 3 ++- .../src/dotty/tools/dotc/inlines/Inliner.scala | 1 + tests/pos/i22070/macro.scala | 18 ++++++++++++++++++ tests/pos/i22070/usage.scala | 1 + 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i22070/macro.scala create mode 100644 tests/pos/i22070/usage.scala diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index de99ce0105ea..7e94bcfc6ae3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -395,9 +395,10 @@ class TreeUnpickler(reader: TastyReader, case TYPEREFin => val name = readName().toTypeName val prefix = readType() + def pre = if TypeOps.isLegalPrefix(prefix) then prefix else QualSkolemType(prefix) val space = readType() space.decl(name) match { - case symd: SymDenotation if prefix.isArgPrefixOf(symd.symbol) => TypeRef(prefix, symd.symbol) + case symd: SymDenotation if prefix.isArgPrefixOf(symd.symbol) => TypeRef(pre, symd.symbol) case _ => TypeRef(prefix, name, space.decl(name).asSeenFrom(prefix)) } case REFINEDtype => diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index e06e6b3e1615..1ea9b680e431 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -768,6 +768,7 @@ class Inliner(val call: tpd.Tree)(using Context): override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { val locked = ctx.typerState.ownedVars val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)) + selectionType(tree, qual1) // side-effect val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) val reducedProjection = reducer.reduceProjection(resNoReduce) if reducedProjection.isType then diff --git a/tests/pos/i22070/macro.scala b/tests/pos/i22070/macro.scala new file mode 100644 index 000000000000..bcf6ec6dd70f --- /dev/null +++ b/tests/pos/i22070/macro.scala @@ -0,0 +1,18 @@ +trait Featureful[T]: + def toFeatures(value: T): IArray[Float] + +object Featureful: + inline def derived[T](using scala.deriving.Mirror.Of[T]) = ${ derivedImpl[T] } + + import scala.quoted.* + private def derivedImpl[T: Type](using Quotes): Expr[Featureful[T]] = + import quotes.reflect.* + '{ + new Featureful[T]: + def toFeatures(value: T) = + val feats = IArray.empty[Featureful[?]] + val product = value.asInstanceOf[Product] + product.productIterator.zipWithIndex.foreach: (any, idx) => + feats(idx).toFeatures(any.asInstanceOf) + IArray.empty + } diff --git a/tests/pos/i22070/usage.scala b/tests/pos/i22070/usage.scala new file mode 100644 index 000000000000..6fc662dfcc13 --- /dev/null +++ b/tests/pos/i22070/usage.scala @@ -0,0 +1 @@ +case class Breaks(x: Boolean, y: Boolean) derives Featureful From 08fef874ca96dfcb3880f7dfc75fd6e89edcb6f6 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 18 Dec 2024 15:08:56 +0000 Subject: [PATCH 150/202] Refactor OpaqueProxy (de)construction --- .../src/dotty/tools/dotc/core/TypeOps.scala | 30 +++++------ .../dotty/tools/dotc/inlines/Inliner.scala | 52 +++++++++++++------ 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index e80cadcd3b8c..a7f41a71d7ce 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -512,23 +512,19 @@ object TypeOps: else (if pre.isSingleton then NoType else tryWiden(tp, tp.prefix)).orElse { if (tp.isTerm && variance > 0 && !pre.isSingleton) tp.prefix match - case prefix: TermRef if prefix.name.is(InlineBinderName) && prefix.symbol.denot.is(Synthetic, butNot=InlineProxy) => - prefix.info.widenExpr.dealias match - case info: RefinedType => - // Strip refinements on an opaque alias proxy - // Using pos/i22068 as an example, - // Inliner#addOpaqueProxies add the following opaque alias proxy: - // val $proxy1: foos.type { type Foo[T] = String } = - // foos.$asInstanceOf[foos.type { type Foo[T] = String }] - // Then when InlineCall#expand creates a typed Inlined, - // we type avoid any local bindings, which includes that opaque alias proxy. - // To avoid that the replacement is a non-singleton RefinedType, - // we drop the refinements too and return foos.type. - // That way, when we inline `def m1` and we calculate the asSeenFrom - // of `b1.and(..)` b1 doesn't have an unstable prefix. - derivedSelect(tp, info.stripRefinement) - case _ => - apply(tp.info.widenExpr) + case inlines.Inliner.OpaqueProxy(ref) => + // Strip refinements on an opaque alias proxy + // Using pos/i22068 as an example, + // Inliner#addOpaqueProxies add the following opaque alias proxy: + // val $proxy1: foos.type { type Foo[T] = String } = + // foos.$asInstanceOf[foos.type { type Foo[T] = String }] + // Then when InlineCall#expand creates a typed Inlined, + // we type avoid any local bindings, which includes that opaque alias proxy. + // To avoid that the replacement is a non-singleton RefinedType, + // we drop the refinements too and return foos.type. + // That way, when we inline `def m1` and we calculate the asSeenFrom + // of `b1.and(..)` b1 doesn't have an unstable prefix. + derivedSelect(tp, ref) case _ => apply(tp.info.widenExpr) else if (upper(pre).member(tp.name).exists) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 1ea9b680e431..a5beceba3e46 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -131,6 +131,39 @@ object Inliner: case _ => tree else super.transformInlined(tree) end InlinerMap + + object OpaqueProxy: + + def apply(ref: TermRef, cls: ClassSymbol, span: Span)(using Context): TermRef = + def openOpaqueAliases(selfType: Type): List[(Name, Type)] = selfType match + case RefinedType(parent, rname, TypeAlias(alias)) => + val opaq = cls.info.member(rname).symbol + if opaq.isOpaqueAlias then + (rname, alias.stripLazyRef.asSeenFrom(ref, cls)) + :: openOpaqueAliases(parent) + else Nil + case _ => Nil + val refinements = openOpaqueAliases(cls.givenSelfType) + val refinedType = refinements.foldLeft(ref: Type): (parent, refinement) => + RefinedType(parent, refinement._1, TypeAlias(refinement._2)) + val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType, span) + refiningSym.termRef + + def unapply(refiningRef: TermRef)(using Context): Option[TermRef] = + val refiningSym = refiningRef.symbol + if refiningSym.name.is(InlineBinderName) && refiningSym.is(Synthetic, butNot=InlineProxy) then + refiningRef.info match + case refinedType: RefinedType => refinedType.stripRefinement match + case ref: TermRef => Some(ref) + case _ => None + case _ => None + else + None + + end OpaqueProxy + + private[inlines] def newSym(name: Name, flags: FlagSet, info: Type, span: Span)(using Context): Symbol = + newSymbol(ctx.owner, name, flags, info, coord = span) end Inliner /** Produces an inlined version of `call` via its `inlined` method. @@ -189,7 +222,7 @@ class Inliner(val call: tpd.Tree)(using Context): private val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] private[inlines] def newSym(name: Name, flags: FlagSet, info: Type)(using Context): Symbol = - newSymbol(ctx.owner, name, flags, info, coord = call.span) + Inliner.newSym(name, flags, info, call.span) /** A binding for the parameter of an inline method. This is a `val` def for * by-value parameters and a `def` def for by-name parameters. `val` defs inherit @@ -351,20 +384,9 @@ class Inliner(val call: tpd.Tree)(using Context): && (forThisProxy || inlinedMethod.isContainedIn(cls)) && mapRef(ref).isEmpty then - def openOpaqueAliases(selfType: Type): List[(Name, Type)] = selfType match - case RefinedType(parent, rname, TypeAlias(alias)) => - val opaq = cls.info.member(rname).symbol - if opaq.isOpaqueAlias then - (rname, alias.stripLazyRef.asSeenFrom(ref, cls)) - :: openOpaqueAliases(parent) - else Nil - case _ => - Nil - val refinements = openOpaqueAliases(cls.givenSelfType) - val refinedType = refinements.foldLeft(ref: Type) ((parent, refinement) => - RefinedType(parent, refinement._1, TypeAlias(refinement._2)) - ) - val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType).asTerm + val refiningRef = OpaqueProxy(ref, cls, call.span) + val refiningSym = refiningRef.symbol.asTerm + val refinedType = refiningRef.info val refiningDef = ValDef(refiningSym, tpd.ref(ref).cast(refinedType), inferred = true).withSpan(span) inlining.println(i"add opaque alias proxy $refiningDef for $ref in $tp") bindingsBuf += refiningDef From 0041987fdc19922ef0fabfe51f6f5621a9da54cf Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Thu, 19 Dec 2024 16:21:35 +0100 Subject: [PATCH 151/202] Implement `tracked` members (#21761) closes #21754 Allow for the `tracked` modifier to be used for `val` members of classes and traits. `tracked` members and members inheriting from `tracked` force the type of the member (or it's overriding member) to be as exact as possible. More precisely, it will will assign the `tracked` member the infered type of the rhs. For instance, consider the following definition: ```scala 3 trait F: tracked val a: Int tracked val b: Int class N extends F: val a = 22 // a.type =:= 22 val b: Int = 22 // b.type =:= Int tracked val c = 22 // c.type =:= 22 ``` Here, the `tracked` modifier ensures that the type of `a` in `N` is `22` and not `Int`. But the type of `b` is `N` is `Int` since it's explicitly declared as `Int`. `tracked` members can also be immediately initialized, as in the case of `c`. --------- Co-authored-by: Matt Bovel Co-authored-by: odersky --- .../src/dotty/tools/dotc/ast/Desugar.scala | 12 +++- .../src/dotty/tools/dotc/core/Flags.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 4 +- .../dotty/tools/dotc/parsing/Scanners.scala | 3 +- .../src/dotty/tools/dotc/typer/Namer.scala | 16 ++++-- .../src/dotty/tools/dotc/typer/Typer.scala | 10 ++-- docs/_docs/internals/syntax.md | 5 +- .../reference/experimental/modularity.md | 40 +++++++++++--- tests/neg/abstract-tracked-1.scala | 12 ++++ tests/neg/abstract-tracked.check | 20 +++++++ tests/neg/abstract-tracked.scala | 14 +++++ tests/neg/tracked.check | 50 ++++++----------- tests/neg/tracked.scala | 6 +- tests/pos/abstract-tracked-2.scala | 11 ++++ tests/pos/abstract-tracked.scala | 55 +++++++++++++++++++ 15 files changed, 198 insertions(+), 62 deletions(-) create mode 100644 tests/neg/abstract-tracked-1.scala create mode 100644 tests/neg/abstract-tracked.check create mode 100644 tests/neg/abstract-tracked.scala create mode 100644 tests/pos/abstract-tracked-2.scala create mode 100644 tests/pos/abstract-tracked.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index a95e64e24b85..67e1885b511f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1086,12 +1086,13 @@ object desugar { if mods.isAllOf(Given | Inline | Transparent) then report.error("inline given instances cannot be trasparent", cdef) var classMods = if mods.is(Given) then mods &~ (Inline | Transparent) | Synthetic else mods - if vparamAccessors.exists(_.mods.is(Tracked)) then + val newBody = tparamAccessors ::: vparamAccessors ::: normalizedBody ::: caseClassMeths + if newBody.collect { case d: ValOrDefDef => d }.exists(_.mods.is(Tracked)) then classMods |= Dependent cpy.TypeDef(cdef: TypeDef)( name = className, rhs = cpy.Template(impl)(constr, parents1, clsDerived, self1, - tparamAccessors ::: vparamAccessors ::: normalizedBody ::: caseClassMeths) + newBody) ).withMods(classMods) } @@ -1561,6 +1562,12 @@ object desugar { rhsOK(rhs) } + val legalTracked: Context ?=> MemberDefTest = { + case valdef @ ValDef(_, _, _) => + val sym = valdef.symbol + !ctx.owner.exists || ctx.owner.isClass || ctx.owner.is(Case) || ctx.owner.isConstructor || valdef.mods.is(Param) || valdef.mods.is(ParamAccessor) + } + def checkOpaqueAlias(tree: MemberDef)(using Context): MemberDef = def check(rhs: Tree): MemberDef = rhs match case bounds: TypeBoundsTree if bounds.alias.isEmpty => @@ -1586,6 +1593,7 @@ object desugar { } else tested tested = checkOpaqueAlias(tested) tested = checkApplicable(Opaque, legalOpaque) + tested = checkApplicable(Tracked, legalTracked) tested case _ => tree diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index b915373da021..0775b3caaf0c 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -480,7 +480,7 @@ object Flags { */ val AfterLoadFlags: FlagSet = commonFlags( FromStartFlags, AccessFlags, Final, AccessorOrSealed, - Abstract, LazyOrTrait, SelfName, JavaDefined, JavaAnnotation, Transparent, Tracked) + Abstract, LazyOrTrait, SelfName, JavaDefined, JavaAnnotation, Transparent) /** A value that's unstable unless complemented with a Stable flag */ val UnstableValueFlags: FlagSet = Mutable | Method diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index e9f6b01a99c3..7933cbbea12f 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3519,7 +3519,7 @@ object Parsers { * UsingClsTermParamClause::= ‘(’ ‘using’ [‘erased’] (ClsParams | ContextTypes) ‘)’ * ClsParams ::= ClsParam {‘,’ ClsParam} * ClsParam ::= {Annotation} - * [{Modifier | ‘tracked’} (‘val’ | ‘var’)] Param + * [{Modifier} (‘val’ | ‘var’)] Param * TypelessClause ::= DefTermParamClause * | UsingParamClause * @@ -3557,8 +3557,6 @@ object Parsers { if isErasedKw then mods = addModifier(mods) if paramOwner.isClass then - if isIdent(nme.tracked) && in.featureEnabled(Feature.modularity) && !in.lookahead.isColon then - mods = addModifier(mods) mods = addFlag(modifiers(start = mods), ParamAccessor) mods = if in.token == VAL then diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 2dc0a1a8d805..2007b633a7c5 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -212,6 +212,7 @@ object Scanners { def featureEnabled(name: TermName) = Feature.enabled(name)(using languageImportContext) def erasedEnabled = featureEnabled(Feature.erasedDefinitions) + def trackedEnabled = featureEnabled(Feature.modularity) private var postfixOpsEnabledCache = false private var postfixOpsEnabledCtx: Context = NoContext @@ -1195,7 +1196,7 @@ object Scanners { def isSoftModifier: Boolean = token == IDENTIFIER - && (softModifierNames.contains(name) || name == nme.erased && erasedEnabled) + && (softModifierNames.contains(name) || name == nme.erased && erasedEnabled || name == nme.tracked && trackedEnabled) def isSoftModifierInModifierPosition: Boolean = isSoftModifier && inModifierPosition() diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 61c4f5b294cd..e8b22325d1e9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -878,7 +878,7 @@ class Namer { typer: Typer => case original: untpd.MemberDef => lazy val annotCtx = annotContext(original, sym) original.setMods: - original.mods.withAnnotations : + original.mods.withAnnotations: original.mods.annotations.mapConserve: annotTree => val cls = typedAheadAnnotationClass(annotTree)(using annotCtx) if (cls eq sym) @@ -2017,6 +2017,11 @@ class Namer { typer: Typer => paramFn: Type => Type, fallbackProto: Type )(using Context): Type = + /** Is this member tracked? This is true if it is marked as `tracked` or if + * it overrides a `tracked` member. To account for the later, `isTracked` + * is overriden to `true` as a side-effect of computing `inherited`. + */ + var isTracked: Boolean = sym.is(Tracked) /** A type for this definition that might be inherited from elsewhere: * If this is a setter parameter, the corresponding getter type. @@ -2052,8 +2057,10 @@ class Namer { typer: Typer => if paramss.isEmpty then info.widenExpr else NoType - val iRawInfo = - cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema, sym.targetName).info + val iDenot = cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema, sym.targetName) + val iSym = iDenot.symbol + if iSym.is(Tracked) then isTracked = true + val iRawInfo = iDenot.info val iResType = instantiatedResType(iRawInfo, paramss).asSeenFrom(site, cls) if (iResType.exists) typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inherited: $iResType") @@ -2147,6 +2154,7 @@ class Namer { typer: Typer => if defaultTp.exists then TypeOps.SimplifyKeepUnchecked() else null) match case ctp: ConstantType if sym.isInlineVal => ctp + case tp if isTracked => tp case tp => TypeComparer.widenInferred(tp, pt, Widen.Unions) // Replace aliases to Unit by Unit itself. If we leave the alias in @@ -2157,7 +2165,7 @@ class Namer { typer: Typer => def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.srcPos) //if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType") if (inherited.exists) - if sym.isInlineVal then lhsType else inherited + if sym.isInlineVal || isTracked then lhsType else inherited else { if (sym.is(Implicit)) mdef match { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d15e263ce777..c941ffe74e18 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2433,7 +2433,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else if ctx.reporter.errorsReported then UnspecifiedErrorType else errorType(em"cannot infer type; expected type $pt is not fully defined", tree.srcPos)) - def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): Tree = + def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): Tree = { tree match case tree: untpd.DerivedTypeTree => tree.ensureCompletions @@ -2449,6 +2449,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } case _ => completeTypeTree(InferredTypeTree(), pt, tree) + } def typedInLambdaTypeTree(tree: untpd.InLambdaTypeTree, pt: Type)(using Context): Tree = val tp = @@ -2860,7 +2861,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val nnInfo = rhs1.notNullInfo vdef1.withNotNullInfo(if sym.is(Lazy) then nnInfo.retractedInfo else nnInfo) } - private def retractDefDef(sym: Symbol)(using Context): Tree = // it's a discarded method (synthetic case class method or synthetic java record constructor or overridden member), drop it val canBeInvalidated: Boolean = @@ -3672,7 +3672,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } /** Typecheck and adapt tree, returning a typed tree. Parameters as for `typedUnadapted` */ - def typed(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = + def typed(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = { trace(i"typing $tree, pt = $pt", typr, show = true) { record(s"typed $getClass") record("typed total") @@ -3684,6 +3684,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tree.withType(WildcardType) else adapt(typedUnadapted(tree, pt, locked), pt, locked) } + } def typed(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = typed(tree, pt, ctx.typerState.ownedVars) @@ -3799,7 +3800,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedExpr(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = withoutMode(Mode.PatternOrTypeBits)(typed(tree, pt)) - def typedType(tree: untpd.Tree, pt: Type = WildcardType, mapPatternBounds: Boolean = false)(using Context): Tree = + def typedType(tree: untpd.Tree, pt: Type = WildcardType, mapPatternBounds: Boolean = false)(using Context): Tree = { val tree1 = withMode(Mode.Type) { typed(tree, pt) } if mapPatternBounds && ctx.mode.is(Mode.Pattern) && !ctx.isAfterTyper then tree1 match @@ -3815,6 +3816,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree1 => tree1 else tree1 + } def typedPattern(tree: untpd.Tree, selType: Type = WildcardType)(using Context): Tree = withMode(Mode.Pattern)(typed(tree, selType)) diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index d0074bb503c2..665b4f5144ba 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -141,7 +141,7 @@ type val var while with yield ### Soft keywords ``` -as derives end erased extension infix inline opaque open throws transparent using | * + - +as derives end erased extension infix inline opaque open throws tracked transparent using | * + - ``` See the [separate section on soft keywords](../reference/soft-modifier.md) for additional @@ -381,7 +381,7 @@ ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’ | [nl] ‘(’ ‘using’ (ClsParams | FunArgTypes) ‘)’ ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var - [{Modifier | ‘tracked’} (‘val’ | ‘var’)] Param + [{Modifier} (‘val’ | ‘var’)] Param DefParamClauses ::= DefParamClause { DefParamClause } -- and two DefTypeParamClause cannot be adjacent DefParamClause ::= DefTypeParamClause @@ -418,6 +418,7 @@ LocalModifier ::= ‘abstract’ | ‘transparent’ | ‘infix’ | ‘erased’ + | ‘tracked’ AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ id ‘]’ diff --git a/docs/_docs/reference/experimental/modularity.md b/docs/_docs/reference/experimental/modularity.md index a989b71770af..66d4c0c23ede 100644 --- a/docs/_docs/reference/experimental/modularity.md +++ b/docs/_docs/reference/experimental/modularity.md @@ -108,14 +108,6 @@ This works as it should now. Without the addition of `tracked` to the parameter of `SetFunctor` typechecking would immediately lose track of the element type `T` after an `add`, and would therefore fail. -**Syntax Change** - -``` -ClsParam ::= {Annotation} [{Modifier | ‘tracked’} (‘val’ | ‘var’)] Param -``` - -The (soft) `tracked` modifier is only allowed for `val` parameters of classes. - **Discussion** Since `tracked` is so useful, why not assume it by default? First, `tracked` makes sense only for `val` parameters. If a class parameter is not also a field declared using `val` then there's nothing to refine in the constructor result type. One could think of at least making all `val` parameters tracked by default, but that would be a backwards incompatible change. For instance, the following code would break: @@ -134,6 +126,38 @@ only if the class refers to a type member of `x`. But it turns out that this scheme is unimplementable since it would quickly lead to cyclic references when typechecking recursive class graphs. So an explicit `tracked` looks like the best available option. +## Tracked members + +The `tracked` modifier can also be used for `val` members of classes and traits +to force the type of the member (or it's overriding member) to be as exact as +possible. More precisely, it will will assign the `tracked` member the infered +type of the rhs. For instance, consider the following definition: + +```scala +trait F: + tracked val a: Int + tracked val b: Int + +class N extends F: + val a = 22 // a.type =:= 22 + val b: Int = 22 // b.type =:= Int + tracked val c = 22 // c.type =:= 22 +``` + +Here, the `tracked` modifier ensures that the type of `a` in `N` is `22` and not +`Int`. But the type of `b` is `N` is `Int` since it's explicitly declared as +`Int`. `tracked` members can also be immediately initialized, as in the case of +`c`. + +## Tracked syntax change + +``` +LocalModifier ::= ‘tracked’ +``` + +The (soft) `tracked` modifier is allowed as a local modifier. + + ## Allow Class Parents to be Refined Types Since `tracked` parameters create refinements in constructor types, diff --git a/tests/neg/abstract-tracked-1.scala b/tests/neg/abstract-tracked-1.scala new file mode 100644 index 000000000000..0aef9f938816 --- /dev/null +++ b/tests/neg/abstract-tracked-1.scala @@ -0,0 +1,12 @@ +import scala.language.experimental.modularity +import scala.language.future + +trait F: + tracked val a: Int + +class G: + val a: Int = 1 + +def Test = + val g = new G + summon[g.a.type <:< 1] // error diff --git a/tests/neg/abstract-tracked.check b/tests/neg/abstract-tracked.check new file mode 100644 index 000000000000..70a85e81df85 --- /dev/null +++ b/tests/neg/abstract-tracked.check @@ -0,0 +1,20 @@ +-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:4:14 ---------------------------------------------------------- +4 |tracked trait F // error + |^^^^^^^^^^^^^^^ + |Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:9:15 ---------------------------------------------------------- +9 |tracked object O // error + |^^^^^^^^^^^^^^^^ + |Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:11:14 --------------------------------------------------------- +11 |tracked class C // error + |^^^^^^^^^^^^^^^ + |Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:7:14 ---------------------------------------------------------- +7 | tracked def f: F // error + | ^^^^^^^^^^^^^^^^ + | Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:14:14 --------------------------------------------------------- +14 | tracked val x = 1 // error + | ^^^^^^^^^^^^^^^^^ + | Modifier tracked is not allowed for this definition diff --git a/tests/neg/abstract-tracked.scala b/tests/neg/abstract-tracked.scala new file mode 100644 index 000000000000..ff4a7ea8174f --- /dev/null +++ b/tests/neg/abstract-tracked.scala @@ -0,0 +1,14 @@ +import scala.language.experimental.modularity +import scala.language.future + +tracked trait F // error + +trait G: + tracked def f: F // error + +tracked object O // error + +tracked class C // error + +def f = + tracked val x = 1 // error diff --git a/tests/neg/tracked.check b/tests/neg/tracked.check index 14a4d2a08300..3494c401a007 100644 --- a/tests/neg/tracked.check +++ b/tests/neg/tracked.check @@ -6,22 +6,6 @@ 7 | def foo(tracked a: Int) = // error | ^ | ':' expected, but identifier found --- Error: tests/neg/tracked.scala:8:12 --------------------------------------------------------------------------------- -8 | tracked val b: Int = 2 // error - | ^^^ - | end of statement expected but 'val' found --- Error: tests/neg/tracked.scala:11:10 -------------------------------------------------------------------------------- -11 | tracked object Foo // error // error - | ^^^^^^ - | end of statement expected but 'object' found --- Error: tests/neg/tracked.scala:14:10 -------------------------------------------------------------------------------- -14 | tracked class D // error // error - | ^^^^^ - | end of statement expected but 'class' found --- Error: tests/neg/tracked.scala:17:10 -------------------------------------------------------------------------------- -17 | tracked type T = Int // error // error - | ^^^^ - | end of statement expected but 'type' found -- Error: tests/neg/tracked.scala:20:25 -------------------------------------------------------------------------------- 20 | given g2: (tracked val x: Int) => C = C(x) // error | ^^^^^^^^^^^^^^^^^^ @@ -30,21 +14,19 @@ 4 |class C2(tracked var x: Int) // error | ^ | mutable variables may not be `tracked` --- [E006] Not Found Error: tests/neg/tracked.scala:11:2 ---------------------------------------------------------------- -11 | tracked object Foo // error // error - | ^^^^^^^ - | Not found: tracked - | - | longer explanation available when compiling with `-explain` --- [E006] Not Found Error: tests/neg/tracked.scala:14:2 ---------------------------------------------------------------- -14 | tracked class D // error // error - | ^^^^^^^ - | Not found: tracked - | - | longer explanation available when compiling with `-explain` --- [E006] Not Found Error: tests/neg/tracked.scala:17:2 ---------------------------------------------------------------- -17 | tracked type T = Int // error // error - | ^^^^^^^ - | Not found: tracked - | - | longer explanation available when compiling with `-explain` +-- [E156] Syntax Error: tests/neg/tracked.scala:8:16 ------------------------------------------------------------------- +8 | tracked val b: Int = 2 // error + | ^^^^^^^^^^^^^^^^^^^^^^ + | Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/tracked.scala:11:17 ------------------------------------------------------------------ +11 | tracked object Foo // error + | ^^^^^^^^^^^^^^^^^^ + | Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/tracked.scala:14:16 ------------------------------------------------------------------ +14 | tracked class D // error + | ^^^^^^^^^^^^^^^ + | Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/tracked.scala:17:15 ------------------------------------------------------------------ +17 | tracked type T = Int // error + | ^^^^^^^^^^^^^^^^^^^^ + | Modifier tracked is not allowed for this definition diff --git a/tests/neg/tracked.scala b/tests/neg/tracked.scala index 9f874ca3c0da..3d6c1a14fc55 100644 --- a/tests/neg/tracked.scala +++ b/tests/neg/tracked.scala @@ -8,13 +8,13 @@ object A: tracked val b: Int = 2 // error object B: - tracked object Foo // error // error + tracked object Foo // error object C: - tracked class D // error // error + tracked class D // error object D: - tracked type T = Int // error // error + tracked type T = Int // error object E: given g2: (tracked val x: Int) => C = C(x) // error diff --git a/tests/pos/abstract-tracked-2.scala b/tests/pos/abstract-tracked-2.scala new file mode 100644 index 000000000000..01e4ee84c548 --- /dev/null +++ b/tests/pos/abstract-tracked-2.scala @@ -0,0 +1,11 @@ +import scala.language.experimental.modularity +import scala.language.future + +abstract class Vec: + tracked val size: Int + +@main def main = + val v = new Vec: + val size0: size.type = 10 + val size = 10 + val size1: size.type = 10 diff --git a/tests/pos/abstract-tracked.scala b/tests/pos/abstract-tracked.scala new file mode 100644 index 000000000000..21812db9c04d --- /dev/null +++ b/tests/pos/abstract-tracked.scala @@ -0,0 +1,55 @@ +import scala.language.experimental.modularity +import scala.language.future + +trait F: + tracked val a: Int + +trait G: + tracked val b: Int + +trait H: + tracked val c: Int = 3 + +trait I extends F + +trait J extends F: + val a: Int = 1 + +class K(tracked val d: Int) + +class L + +trait M: + val f: Int + +class N extends F: + val a = 10 + +object Test: + val f = new F: + val a = 1 + val g = new G: + val b: 2 = 2 + val h = new H: + override val c = 4 + val i = new I: + val a = 5 + val j = new J: + override val a = 6 + val k = new K(7) + val l = new L { + tracked val e = 8 + } + val m = new M: + tracked val f = 9 + val n = new N + + summon[f.a.type <:< 1] + summon[g.b.type <:< 2] + summon[h.c.type <:< 4] + summon[i.a.type <:< 5] + summon[j.a.type <:< 6] + summon[k.d.type <:< 7] + // summon[l.e.type <:< 8] // unrelated issue -- error: e is not a member of L + summon[m.f.type <:< 9] + summon[n.a.type <:< 10] From 479aff551774976037d6d5ad5ff9a9c8b1494f7c Mon Sep 17 00:00:00 2001 From: philippus Date: Thu, 19 Dec 2024 22:31:55 +0100 Subject: [PATCH 152/202] Update asm to patched 9.7.1 --- compiler/src/dotty/tools/backend/jvm/BackendUtils.scala | 3 ++- project/Build.scala | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala index cb7ed3d54788..ae423b6b80dd 100644 --- a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala +++ b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala @@ -185,6 +185,7 @@ object BackendUtils { 20 -> asm.Opcodes.V20, 21 -> asm.Opcodes.V21, 22 -> asm.Opcodes.V22, - 23 -> asm.Opcodes.V23 + 23 -> asm.Opcodes.V23, + 24 -> asm.Opcodes.V24 ) } diff --git a/project/Build.scala b/project/Build.scala index 98e26858bf79..bf7f1aa47a64 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -761,7 +761,7 @@ object Build { // get libraries onboard libraryDependencies ++= Seq( - "org.scala-lang.modules" % "scala-asm" % "9.7.0-scala-2", // used by the backend + "org.scala-lang.modules" % "scala-asm" % "9.7.1-scala-1", // used by the backend Dependencies.compilerInterface, "org.jline" % "jline-reader" % "3.27.1", // used by the REPL "org.jline" % "jline-terminal" % "3.27.1", From ad6e19410f3e13fa3d4ec83b58e71fd4feec744e Mon Sep 17 00:00:00 2001 From: rochala Date: Fri, 20 Dec 2024 12:56:37 +0100 Subject: [PATCH 153/202] Fix presentation compiler testcases --- project/Build.scala | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 98e26858bf79..e21bf70f1d69 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -95,8 +95,8 @@ object Build { * Reference version should track the latest version pushed to Maven: * - In main branch it should be the last RC version * - In release branch it should be the last stable release - * - * Warning: Change of this variable needs to be consulted with `expectedTastyVersion` + * + * Warning: Change of this variable needs to be consulted with `expectedTastyVersion` */ val referenceVersion = "3.6.3-RC1" @@ -106,8 +106,8 @@ object Build { * * Should only be referred from `dottyVersion` or settings/tasks requiring simplified version string, * eg. `compatMode` or Windows native distribution version. - * - * Warning: Change of this variable might require updating `expectedTastyVersion` + * + * Warning: Change of this variable might require updating `expectedTastyVersion` */ val developedVersion = "3.6.4" @@ -119,13 +119,13 @@ object Build { * During final, stable release is set exactly to `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: + + /** The version of TASTY that should be emitted, checked in runtime test + * For defails on how TASTY version should be set see related discussions: * - https://github.com/scala/scala3/issues/13447#issuecomment-912447107 * - https://github.com/scala/scala3/issues/14306#issuecomment-1069333516 * - https://github.com/scala/scala3/pull/19321 - * + * * Simplified rules, given 3.$minor.$patch = $developedVersion * - Major version is always 28 * - TASTY minor version: @@ -1447,7 +1447,7 @@ object Build { lazy val `scala3-presentation-compiler` = project.in(file("presentation-compiler")) .withCommonSettings(Bootstrapped) - .dependsOn(`scala3-compiler-bootstrapped`, `scala3-library-bootstrapped`, `scala3-presentation-compiler-testcases`) + .dependsOn(`scala3-compiler-bootstrapped`, `scala3-library-bootstrapped`, `scala3-presentation-compiler-testcases` % "test->test") .settings(presentationCompilerSettings) .settings(scala3PresentationCompilerBuildInfo) .settings( @@ -2505,7 +2505,7 @@ object Build { case Bootstrapped => commonBootstrappedSettings }) } - + /* Tests TASTy version invariants during NIGHLY, RC or Stable releases */ def checkReleasedTastyVersion(): Unit = { lazy val (scalaMinor, scalaPatch, scalaIsRC) = baseVersion.split("\\.|-").take(4) match { @@ -2518,19 +2518,19 @@ object Build { case Array("28", minor, "experimental", _) => (minor.toInt, true) case other => sys.error(s"Invalid TASTy version string: $expectedTastyVersion") } - + if(isNightly) { assert(tastyIsExperimental, "TASTY needs to be experimental in nightly builds") val expectedTastyMinor = if(scalaPatch == 0) scalaMinor else scalaMinor + 1 assert(tastyMinor == expectedTastyMinor, "Invalid TASTy minor version") } - + if(isRelease) { assert(scalaMinor == tastyMinor, "Minor versions of TASTY vesion and Scala version should match in release builds") if (scalaIsRC && scalaPatch == 0) assert(tastyIsExperimental, "TASTy should be experimental when releasing a new minor version RC") else - assert(!tastyIsExperimental, "Stable version cannot use experimental TASTY") + assert(!tastyIsExperimental, "Stable version cannot use experimental TASTY") } } } From 3e627316cc62767511c368b062e1767d797d532e Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Fri, 20 Dec 2024 14:50:08 +0100 Subject: [PATCH 154/202] REPL: Add back `:silent` command (#22248) --- .../src/dotty/tools/repl/ParseResult.scala | 6 +++++ .../src/dotty/tools/repl/ReplDriver.scala | 25 ++++++++----------- compiler/test-resources/repl/silent | 25 +++++++++++++++++++ .../dotty/tools/repl/TabcompleteTests.scala | 1 + 4 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 compiler/test-resources/repl/silent diff --git a/compiler/src/dotty/tools/repl/ParseResult.scala b/compiler/src/dotty/tools/repl/ParseResult.scala index 24a624173050..6c9f95d4dca2 100644 --- a/compiler/src/dotty/tools/repl/ParseResult.scala +++ b/compiler/src/dotty/tools/repl/ParseResult.scala @@ -93,6 +93,10 @@ object Reset { val command: String = ":reset" } +/** Toggle automatic printing of results */ +case object Silent extends Command: + val command: String = ":silent" + /** `:quit` exits the repl */ case object Quit extends Command { val command: String = ":quit" @@ -113,6 +117,7 @@ case object Help extends Command { |:imports show import history |:reset [options] reset the repl to its initial state, forgetting all session entries |:settings update compiler options, if possible + |:silent disable/enable automatic printing of results """.stripMargin } @@ -137,6 +142,7 @@ object ParseResult { TypeOf.command -> (arg => TypeOf(arg)), DocOf.command -> (arg => DocOf(arg)), Settings.command -> (arg => Settings(arg)), + Silent.command -> (_ => Silent), ) def apply(source: SourceFile)(using state: State): ParseResult = { diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 486005658d79..3a2b8ce00bbe 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -60,12 +60,14 @@ import scala.util.Using * @param valIndex the index of next value binding for free expressions * @param imports a map from object index to the list of user defined imports * @param invalidObjectIndexes the set of object indexes that failed to initialize + * @param quiet whether we print evaluation results * @param context the latest compiler context */ case class State(objectIndex: Int, valIndex: Int, imports: Map[Int, List[tpd.Import]], invalidObjectIndexes: Set[Int], + quiet: Boolean, context: Context): def validObjectIndexes = (1 to objectIndex).filterNot(invalidObjectIndexes.contains(_)) @@ -114,7 +116,7 @@ class ReplDriver(settings: Array[String], } /** the initial, empty state of the REPL session */ - final def initialState: State = State(0, 0, Map.empty, Set.empty, rootCtx) + final def initialState: State = State(0, 0, Map.empty, Set.empty, false, rootCtx) /** Reset state of repl to the initial state * @@ -217,11 +219,6 @@ class ReplDriver(settings: Array[String], interpret(ParseResult.complete(input)) } - final def runQuietly(input: String)(using State): State = runBody { - val parsed = ParseResult(input) - interpret(parsed, quiet = true) - } - protected def runBody(body: => State): State = rendering.classLoader()(using rootCtx).asContext(withRedirectedOutput(body)) // TODO: i5069 @@ -290,10 +287,10 @@ class ReplDriver(settings: Array[String], .getOrElse(Nil) end completions - protected def interpret(res: ParseResult, quiet: Boolean = false)(using state: State): State = { + protected def interpret(res: ParseResult)(using state: State): State = { res match { case parsed: Parsed if parsed.trees.nonEmpty => - compile(parsed, state, quiet) + compile(parsed, state) case SyntaxErrors(_, errs, _) => displayErrors(errs) @@ -311,7 +308,7 @@ class ReplDriver(settings: Array[String], } /** Compile `parsed` trees and evolve `state` in accordance */ - private def compile(parsed: Parsed, istate: State, quiet: Boolean = false): State = { + private def compile(parsed: Parsed, istate: State): State = { def extractNewestWrapper(tree: untpd.Tree): Name = tree match { case PackageDef(_, (obj: untpd.ModuleDef) :: Nil) => obj.name.moduleClassName case _ => nme.NO_NAME @@ -362,11 +359,9 @@ class ReplDriver(settings: Array[String], given Ordering[Diagnostic] = Ordering[(Int, Int, Int)].on(d => (d.pos.line, -d.level, d.pos.column)) - if (!quiet) { - (definitions ++ warnings) - .sorted - .foreach(printDiagnostic) - } + (if istate.quiet then warnings else definitions ++ warnings) + .sorted + .foreach(printDiagnostic) updatedState } @@ -542,6 +537,8 @@ class ReplDriver(settings: Array[String], rootCtx = setupRootCtx(tokenize(arg).toArray, rootCtx) state.copy(context = rootCtx) + case Silent => state.copy(quiet = !state.quiet) + case Quit => // end of the world! state diff --git a/compiler/test-resources/repl/silent b/compiler/test-resources/repl/silent new file mode 100644 index 000000000000..9e851e8adb01 --- /dev/null +++ b/compiler/test-resources/repl/silent @@ -0,0 +1,25 @@ +scala>:silent +scala> 1+1 +scala> case class A(x: Int) +scala> A("string") +-- [E007] Type Mismatch Error: ------------------------------------------------- +1 | A("string") + | ^^^^^^^^ + | Found: ("string" : String) + | Required: Int + | + | longer explanation available when compiling with `-explain` +1 error found +scala> Option[Int](2) match { case Some(x) => x } +1 warning found +-- [E029] Pattern Match Exhaustivity Warning: ---------------------------------- +1 | Option[Int](2) match { case Some(x) => x } + | ^^^^^^^^^^^^^^ + | match may not be exhaustive. + | + | It would fail on pattern case: None + | + | longer explanation available when compiling with `-explain` +scala>:silent +scala> 1 + 2 +val res2: Int = 3 diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index 95419824d9d1..01b9d1109994 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -217,6 +217,7 @@ class TabcompleteTests extends ReplTest { ":quit", ":reset", ":settings", + ":silent", ":type" ), tabComplete(":") From 669972b5180471291fbb54624185915e3b71c2f9 Mon Sep 17 00:00:00 2001 From: Oleg Zenzin Date: Fri, 20 Dec 2024 13:10:48 -0800 Subject: [PATCH 155/202] Update Example referring to updated macros-spec.md An example code in macros-spec.md was updated in commit d8c9714, but thisstaging.md is still referring the old code. This small change removes reference to old code. --- docs/_docs/reference/metaprogramming/staging.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/_docs/reference/metaprogramming/staging.md b/docs/_docs/reference/metaprogramming/staging.md index 1c154e09f50e..001ae622eabc 100644 --- a/docs/_docs/reference/metaprogramming/staging.md +++ b/docs/_docs/reference/metaprogramming/staging.md @@ -98,9 +98,9 @@ scala -with-compiler -classpath out Test ## Example Now take exactly the same example as in [Macros](./macros.md). Assume that we -do not want to pass an array statically but generate code at run-time and pass +do not want to pass a base double value statically but generate code at run-time and pass the value, also at run-time. Note, how we make a future-stage function of type -`Expr[Array[Int] => Int]` in line 6 below. Using `staging.run { ... }` we can evaluate an +`Expr[Double => Double]` in line 6 below. Using `staging.run { ... }` we can evaluate an expression at runtime. Within the scope of `staging.run` we can also invoke `show` on an expression to get a source-like representation of the expression. @@ -110,12 +110,12 @@ import scala.quoted.* // make available the necessary compiler for runtime code generation given staging.Compiler = staging.Compiler.make(getClass.getClassLoader) -val f: Array[Int] => Int = staging.run { - val stagedSum: Expr[Array[Int] => Int] = - '{ (arr: Array[Int]) => ${sum('arr)}} - println(stagedSum.show) // Prints "(arr: Array[Int]) => { var sum = 0; ... }" - stagedSum +val power3: Double => Double = staging.run { + val stagedPower3: Expr[Double => Double] = + '{ (x: Double) => ${ unrolledPowerCode('x, 3) } } + println(stagedPower3.show) // Prints "((x: scala.Double) => x.*(x.*(x)))" + stagedPower3 } -f.apply(Array(1, 2, 3)) // Returns 6 +power3.apply(2.0) // Returns 8.0 ``` From 5ac4d735b75a3b3efe42542ea63cdcb396a2f5ab Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 23 Dec 2024 18:31:04 +0100 Subject: [PATCH 156/202] Drop unused type alias --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 3ce68792088a..ebe128d7776c 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -24,9 +24,6 @@ import CheckCaptures.CheckerAPI /** Operations accessed from CheckCaptures */ trait SetupAPI: - /** The operation to recheck a ValDef or DefDef */ - type DefRecheck = (tpd.ValOrDefDef, Symbol) => Context ?=> Type - /** Setup procedure to run for each compilation unit * @param tree the typed tree of the unit to check * @param checker the capture checker which will run subsequently. From e464c18bfe5d0abe9a2c99806796a23d5e34adb4 Mon Sep 17 00:00:00 2001 From: Florian3k Date: Mon, 23 Dec 2024 22:29:38 +0100 Subject: [PATCH 157/202] Scaladoc: add support for named tuples --- .../src/tests/namedTuples.scala | 23 +++++++ .../tools/scaladoc/tasty/TypesSupport.scala | 65 ++++++++++--------- .../TranslatableSignaturesTestCases.scala | 2 + 3 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 scaladoc-testcases/src/tests/namedTuples.scala diff --git a/scaladoc-testcases/src/tests/namedTuples.scala b/scaladoc-testcases/src/tests/namedTuples.scala new file mode 100644 index 000000000000..30a83e7e01b0 --- /dev/null +++ b/scaladoc-testcases/src/tests/namedTuples.scala @@ -0,0 +1,23 @@ +package tests.namedTuples + +import language.experimental.namedTuples +import NamedTuple.* + +type Person = (name: String, age: Int) + +type Person2 = NamedTuple[("age2", "name2"), (Int, String)] //expected: type Person2 = (age2: Int, name2: String) + +val x = (name = "Bob", age = 25) //expected: val x: (name: String, age: Int) + +def foo(p1: (age: Int, name: String), p2: (name: String, age: Int)): Nothing + = ??? + +def invalid1: NamedTuple[("age", String), (Int, Int)] + = ??? + +def invalid2: NamedTuple[("age", "name"), (Int, Int, Int)] + = ??? + +def invalid3: NamedTuple[("age", "name", "something"), (Int, Int)] + = ??? + diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 855678a091d2..15c3071c38c9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -204,36 +204,43 @@ trait TypesSupport: prefix ++ plain("{ ").l ++ refinedElems.flatMap(e => parseRefinedElem(e.name, e.info)) ++ plain(" }").l } } + + case AppliedType(tpe, args) if defn.isTupleClass(tpe.typeSymbol) && args.length > 1 => + inParens(commas(args.map(inner(_)))) + + case AppliedType(namedTuple, List(AppliedType(tuple1, names), AppliedType(tuple2, types))) + if namedTuple.typeSymbol == Symbol.requiredModule("scala.NamedTuple").typeMember("NamedTuple") + && defn.isTupleClass(tuple1.typeSymbol) && defn.isTupleClass(tuple2.typeSymbol) && names.length == types.length + && names.forall { case ConstantType(StringConstant(_)) => true case _ => false } => + val elems = names + .collect { case ConstantType(StringConstant(s)) => s } + .zip(types) + .map((name, tpe) => plain(name) +: plain(": ") +: inner(tpe)) + inParens(commas(elems)) + + case t @ AppliedType(tpe, List(lhs, rhs)) if isInfix(t) => + inParens(inner(lhs), shouldWrapInParens(lhs, t, true)) + ++ plain(" ").l + ++ inner(tpe) + ++ plain(" ").l + ++ inParens(inner(rhs), 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) + 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), wrapInParens) ++ keyword(arrow).l ++ inner(rtpe) + case _ => + plain("(").l ++ commas(args.init.map(inner(_))) ++ plain(")").l ++ keyword(arrow).l ++ inner(args.last) + case t @ AppliedType(tpe, typeList) => - import dotty.tools.dotc.util.Chars._ - if defn.isTupleClass(tpe.typeSymbol) && typeList.length != 1 then - typeList match - case Nil => Nil - case args => inParens(commas(args.map(inner(_)))) - else if isInfix(t) then - val lhs = typeList.head - val rhs = typeList.last - inParens(inner(lhs), shouldWrapInParens(lhs, t, true)) - ++ plain(" ").l - ++ inner(tpe) - ++ plain(" ").l - ++ inParens(inner(rhs), shouldWrapInParens(rhs, t, false)) - else if t.isFunctionType then - val arrow = if t.isContextFunctionType then " ?=> " else " => " - typeList match - case Nil => - Nil - case Seq(rtpe) => - plain("()").l ++ keyword(arrow).l ++ inner(rtpe) - case Seq(arg, rtpe) => - val partOfSignature = stripAnnotated(arg) match - case _: TermRef | _: TypeRef | _: ConstantType | _: ParamRef => inner(arg) - case at: AppliedType if !isInfix(at) && !at.isFunctionType && !at.isTupleN => inner(arg) - case _ => inParens(inner(arg)) - partOfSignature ++ keyword(arrow).l ++ inner(rtpe) - case args => - plain("(").l ++ commas(args.init.map(inner(_))) ++ plain(")").l ++ keyword(arrow).l ++ inner(args.last) - else inner(tpe) ++ plain("[").l ++ commas(typeList.map { t => t match + inner(tpe) ++ plain("[").l ++ commas(typeList.map { t => t match case _: TypeBounds => keyword("_").l ++ inner(t) case _ => topLevelProcess(t) }) ++ plain("]").l diff --git a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala index bfa2a372827a..4ed17662fff8 100644 --- a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala @@ -124,3 +124,5 @@ class ExtendsCall extends SignatureTest("extendsCall", SignatureTest.all) class RefinedFunctionTypes extends SignatureTest("refinedFunctionTypes", SignatureTest.all) class RightAssocExtension extends SignatureTest("rightAssocExtension", SignatureTest.all) + +class NamedTuples extends SignatureTest("namedTuples", SignatureTest.all) From 0ae68cff08784d07eefa61a2fa2d1b4c0b26b62c Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sun, 29 Dec 2024 11:36:46 -0800 Subject: [PATCH 158/202] Tweak ExtensionNullifiedByMember --- .../dotty/tools/dotc/reporting/messages.scala | 11 +++++--- tests/warn/i16743.check | 24 ++++++++-------- tests/warn/i22267.check | 14 ++++++++++ tests/warn/i22267.scala | 28 +++++++++++++++++++ 4 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 tests/warn/i22267.check create mode 100644 tests/warn/i22267.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 28a2b5757a93..692b87f68821 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2504,12 +2504,15 @@ class ExtensionNullifiedByMember(method: Symbol, target: Symbol)(using Context) extends Message(ExtensionNullifiedByMemberID): def kind = MessageKind.PotentialIssue def msg(using Context) = - i"""Extension method ${hl(method.name.toString)} will never be selected - |because ${hl(target.name.toString)} already has a member with the same name and compatible parameter types.""" + val targetName = hl(target.name.toString) + i"""Extension method ${hl(method.name.toString)} will never be selected from type $targetName + |because $targetName already has a member with the same name and compatible parameter types.""" def explain(using Context) = - i"""An extension method can be invoked as a regular method, but if that is intended, + i"""Although extensions can be overloaded, they do not overload existing member methods. + |An extension method can be invoked as a regular method, but if that is the intended usage, |it should not be defined as an extension. - |Although extensions can be overloaded, they do not overload existing member methods.""" + | + |The extension may be invoked as though selected from an arbitrary type if conversions are in play.""" class TraitCompanionWithMutableStatic()(using Context) extends SyntaxMsg(TraitCompanionWithMutableStaticID) { diff --git a/tests/warn/i16743.check b/tests/warn/i16743.check index 3010338cfb45..6fa1f2c83357 100644 --- a/tests/warn/i16743.check +++ b/tests/warn/i16743.check @@ -1,84 +1,84 @@ -- [E194] Potential Issue Warning: tests/warn/i16743.scala:30:6 -------------------------------------------------------- 30 | def t = 27 // warn | ^ - | Extension method t will never be selected + | Extension method t will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:32:6 -------------------------------------------------------- 32 | def g(x: String)(i: Int): String = x*i // warn | ^ - | Extension method g will never be selected + | Extension method g will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:33:6 -------------------------------------------------------- 33 | def h(x: String): String = x // warn | ^ - | Extension method h will never be selected + | Extension method h will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:35:6 -------------------------------------------------------- 35 | def j(x: Any, y: Int): String = (x.toString)*y // warn | ^ - | Extension method j will never be selected + | Extension method j will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:36:6 -------------------------------------------------------- 36 | def k(x: String): String = x // warn | ^ - | Extension method k will never be selected + | Extension method k will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:38:6 -------------------------------------------------------- 38 | def m(using String): String = "m" + summon[String] // warn | ^ - | Extension method m will never be selected + | Extension method m will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:39:6 -------------------------------------------------------- 39 | def n(using String): String = "n" + summon[String] // warn | ^ - | Extension method n will never be selected + | Extension method n will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:40:6 -------------------------------------------------------- 40 | def o: String = "42" // warn | ^ - | Extension method o will never be selected + | Extension method o will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:41:6 -------------------------------------------------------- 41 | def u: Int = 27 // warn | ^ - | Extension method u will never be selected + | Extension method u will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:44:6 -------------------------------------------------------- 44 | def at: Int = 42 // warn | ^ - | Extension method at will never be selected + | Extension method at will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:46:6 -------------------------------------------------------- 46 | def x(using String)(n: Int): Int = summon[String].toInt + n // warn | ^ - | Extension method x will never be selected + | Extension method x will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:47:6 -------------------------------------------------------- 47 | def y(using String)(s: String): String = s + summon[String] // warn | ^ - | Extension method y will never be selected + | Extension method y will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` diff --git a/tests/warn/i22267.check b/tests/warn/i22267.check new file mode 100644 index 000000000000..a6b1ccbdef52 --- /dev/null +++ b/tests/warn/i22267.check @@ -0,0 +1,14 @@ +-- [E194] Potential Issue Warning: tests/warn/i22267.scala:13:26 ------------------------------------------------------- +13 | extension (self: C) def m(n: Double): Unit = println(2->n) // warn + | ^ + | Extension method m will never be selected from type C + | because C already has a member with the same name and compatible parameter types. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Although extensions can be overloaded, they do not overload existing member methods. + | An extension method can be invoked as a regular method, but if that is the intended usage, + | it should not be defined as an extension. + | + | The extension may be invoked as though selected from an arbitrary type if conversions are in play. + -------------------------------------------------------------------------------------------------------------------- diff --git a/tests/warn/i22267.scala b/tests/warn/i22267.scala new file mode 100644 index 000000000000..d1f6699342e9 --- /dev/null +++ b/tests/warn/i22267.scala @@ -0,0 +1,28 @@ +//> using options -explain + +import language.implicitConversions + +case class M[T](value: T) +given [T]: Conversion[M[T], T] = _.value +class C: + def m(n: Double): Unit = println(0->n) +object C: + given Ops = Ops() +class Ops: + extension (self: C) def m(n: Int): Unit = println(1->n) + extension (self: C) def m(n: Double): Unit = println(2->n) // warn + extension (self: C) def m(s: String): Unit = println(3->s) + +@main def test() = + val c = M(C()) + def i = 42 + def pi = 3.14 + c.value.m(i) + c.value.m(pi) + c.m(i) // conversion + c.m(pi) // conversion + c.m("hello, world") // extension + //m(c)(pi) + val c0 = C() + c0.m(pi) + c0.m("hello, world") From aa44a3c564f345e465af67cd8e8ef4c62d598d85 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 30 Dec 2024 09:38:05 -0800 Subject: [PATCH 159/202] ExtensionNullifiedByMember requires both opaque params --- .../dotty/tools/dotc/typer/RefChecks.scala | 9 ++++++- tests/warn/i22279.scala | 24 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/warn/i22279.scala diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 7e53b38b5f98..dcdabaf3a72d 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -1156,6 +1156,11 @@ object RefChecks { * * If the extension method is nullary, it is always hidden by a member of the same name. * (Either the member is nullary, or the reference is taken as the eta-expansion of the member.) + * + * This check is in lieu of a more expensive use-site check that an application failed to use an extension. + * That check would account for accessibility and opacity. As a limitation, this check considers + * only public members, a target receiver that is not an alias, and corresponding method parameters + * that are either both opaque types or both not. */ def checkExtensionMethods(sym: Symbol)(using Context): Unit = if sym.is(Extension) && !sym.nextOverriddenSymbol.exists then @@ -1179,7 +1184,9 @@ object RefChecks { val memberParamTps = member.info.stripPoly.firstParamTypes !memberParamTps.isEmpty && memberParamTps.lengthCompare(paramTps) == 0 - && memberParamTps.lazyZip(paramTps).forall((m, x) => x frozen_<:< m) + && memberParamTps.lazyZip(paramTps).forall: (m, x) => + m.typeSymbol.denot.isOpaqueAlias == x.typeSymbol.denot.isOpaqueAlias + && (x frozen_<:< m) } } .exists diff --git a/tests/warn/i22279.scala b/tests/warn/i22279.scala new file mode 100644 index 000000000000..af69ce8b61e9 --- /dev/null +++ b/tests/warn/i22279.scala @@ -0,0 +1,24 @@ + +import scala.io.Source + +object Lengths: + opaque type Length = Int + object Length: + def apply(i: Int): Length = i + extension (source: Source) + def take(length: Length): IndexedSeq[Char] = // no warn + source.take(length).to(IndexedSeq) +end Lengths + +trait Taken: + def take(n: Lengths.Length): Taken = ??? + +object Lengthy: + import Lengths.* + extension (taken: Taken) def take(n: Length): Taken = ??? // warn + +@main def test() = println: + import Lengths.* + val src = Source.fromString("hello, world") + val len = Length("hello".length) + src.take(len) From 9ba16020ba1c831a94e370b8703d87ffda38302f Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 30 Dec 2024 23:45:22 +0100 Subject: [PATCH 160/202] Set referenceVersion to 3.6.3-RC2 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 806412931696..a7be508ce11a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -98,7 +98,7 @@ object Build { * * Warning: Change of this variable needs to be consulted with `expectedTastyVersion` */ - val referenceVersion = "3.6.3-RC1" + val referenceVersion = "3.6.3-RC2" /** Version of the Scala compiler targeted in the current release cycle * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes From 1448123ce79ac96cc1de25ee84fd9a1fb063f3ba Mon Sep 17 00:00:00 2001 From: Jan Chyb <48855024+jchyb@users.noreply.github.com> Date: Fri, 3 Jan 2025 19:59:53 +0100 Subject: [PATCH 161/202] Fix inline reduction for CaseDef guards with asInstanceOf (#22305) In Inliner.scala we add asInstanceOf to references to private inline methods to make sure we later are able to know which method is referenced (if e.g. we inline out of the scope where that method would be visible). This added asInstanceOf caused issue when inlining CaseDef guards, as instead of a simple constant literal we get an Inlined node with an added binding, like this: ```scala { val A_this: A = A_this.asInstanceOf[A] true:Boolean } ``` We fix that by just unpacking that Inlined node (and we do not need that binding for constant literals, so we can just ignore it). --- .../dotty/tools/dotc/inlines/InlineReducer.scala | 2 +- tests/pos/i22300.scala | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i22300.scala diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala index 0c321570bad5..3edb323e6b3b 100644 --- a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -362,7 +362,7 @@ class InlineReducer(inliner: Inliner)(using Context): val (caseBindings, from, to) = substBindings(caseBindingMap.toList, mutable.ListBuffer(), Nil, Nil) val (guardOK, canReduceGuard) = if cdef.guard.isEmpty then (true, true) - else typer.typed(cdef.guard.subst(from, to), defn.BooleanType) match { + else stripInlined(typer.typed(cdef.guard.subst(from, to), defn.BooleanType)) match { case ConstantValue(v: Boolean) => (v, true) case _ => (false, false) } diff --git a/tests/pos/i22300.scala b/tests/pos/i22300.scala new file mode 100644 index 000000000000..156d913951b6 --- /dev/null +++ b/tests/pos/i22300.scala @@ -0,0 +1,16 @@ +class Foo: + + inline def inlineMatch[T]: String = + inline compiletime.erasedValue[T] match + case _: EmptyTuple => "" + case _: (h *: _) if checkType[h](0) => "" + + private inline def checkType[T](i: Int): Boolean = + inline compiletime.erasedValue[T] match + case _: Int => true + case _ => false + +val foo = Foo() + +@main def main () = + foo.inlineMatch[(Int, Boolean)] From 82272587c45afb3dd28dc75b77b875adb54403ce Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 6 Jan 2025 08:47:42 +0000 Subject: [PATCH 162/202] fix: typo in documentation for Tuple.zip --- library/src/scala/Tuple.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index d07f2c89e004..57d1572772e2 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -65,7 +65,7 @@ sealed trait Tuple extends Product { inline def size[This >: this.type <: Tuple]: Size[This] = runtime.Tuples.size(this).asInstanceOf[Size[This]] - /** Given two tuples, `(a1, ..., an)` and `(a1, ..., an)`, returns a tuple + /** Given two tuples, `(a1, ..., an)` and `(b1, ..., bn)`, returns a tuple * `((a1, b1), ..., (an, bn))`. If the two tuples have different sizes, * the extra elements of the larger tuple will be disregarded. * The result is typed as `((A1, B1), ..., (An, Bn))` if at least one of the From 1fbaafcc12b1bc241c706fcb53f68d6b345916db Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 6 Jan 2025 16:56:44 +0100 Subject: [PATCH 163/202] Fail more eagerly when trying to adapt named unapply patterns --- .../dotty/tools/dotc/typer/Applications.scala | 17 ++++++++--- tests/neg/i22192.check | 30 +++++++++++++++++++ tests/neg/i22192.scala | 11 +++++++ tests/neg/i22192a.check | 20 +++++++++++++ tests/neg/i22192a.scala | 13 ++++++++ tests/pos/i22192.scala | 15 ++++++++++ 6 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 tests/neg/i22192.check create mode 100644 tests/neg/i22192.scala create mode 100644 tests/neg/i22192a.check create mode 100644 tests/neg/i22192a.scala create mode 100644 tests/pos/i22192.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 73719cc287a4..c2864093ff70 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -109,6 +109,10 @@ object Applications { if (isValid) elemTp else NoType } + def namedTupleOrProductTypes(tp: Type)(using Context): List[Type] = + if tp.isNamedTupleType then tp.namedTupleElementTypes.map(_(1)) + else productSelectorTypes(tp, NoSourcePosition) + def productSelectorTypes(tp: Type, errorPos: SrcPos)(using Context): List[Type] = { val sels = for (n <- Iterator.from(0)) yield extractorMemberType(tp, nme.selectorName(n), errorPos) sels.takeWhile(_.exists).toList @@ -177,9 +181,14 @@ object Applications { else fallback private def tryAdaptPatternArgs(elems: List[untpd.Tree], pt: Type)(using Context): Option[List[untpd.Tree]] = - tryEither[Option[List[untpd.Tree]]] - (Some(desugar.adaptPatternArgs(elems, pt))) - ((_, _) => None) + namedTupleOrProductTypes(pt) match + case List(defn.NamedTuple(_, _))=> + // if the product types list is a singleton named tuple, autotupling might be applied, so don't fail eagerly + tryEither[Option[List[untpd.Tree]]] + (Some(desugar.adaptPatternArgs(elems, pt))) + ((_, _) => None) + case pts => + Some(desugar.adaptPatternArgs(elems, pt)) private def getUnapplySelectors(tp: Type)(using Context): List[Type] = // We treat patterns as product elements if @@ -199,7 +208,7 @@ object Applications { else tp :: Nil private def productUnapplySelectors(tp: Type)(using Context): Option[List[Type]] = - if defn.isProductSubType(tp) then + if defn.isProductSubType(tp) && args.lengthCompare(productArity(tp)) <= 0 then tryAdaptPatternArgs(args, tp) match case Some(args1) if isProductMatch(tp, args1.length, pos) => args = args1 diff --git a/tests/neg/i22192.check b/tests/neg/i22192.check new file mode 100644 index 000000000000..35ed426ccb11 --- /dev/null +++ b/tests/neg/i22192.check @@ -0,0 +1,30 @@ +-- Error: tests/neg/i22192.scala:6:12 ---------------------------------------------------------------------------------- +6 | case City(iam = n, confused = p) => // error // error + | ^^^^^^^ + | No element named `iam` is defined in selector type City +-- Error: tests/neg/i22192.scala:6:21 ---------------------------------------------------------------------------------- +6 | case City(iam = n, confused = p) => // error // error + | ^^^^^^^^^^^^ + | No element named `confused` is defined in selector type City +-- [E006] Not Found Error: tests/neg/i22192.scala:7:7 ------------------------------------------------------------------ +7 | s"$n has a population of $p" // error // error + | ^ + | Not found: n + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/i22192.scala:7:30 ----------------------------------------------------------------- +7 | s"$n has a population of $p" // error // error + | ^ + | Not found: p + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg/i22192.scala:10:12 --------------------------------------------------------------------------------- +10 | case Some(iam = n) => // error + | ^^^^^^^ + | No element named `iam` is defined in selector type City +-- [E006] Not Found Error: tests/neg/i22192.scala:11:4 ----------------------------------------------------------------- +11 | n // error + | ^ + | Not found: n + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i22192.scala b/tests/neg/i22192.scala new file mode 100644 index 000000000000..16eeb643dee0 --- /dev/null +++ b/tests/neg/i22192.scala @@ -0,0 +1,11 @@ +import scala.language.experimental.namedTuples + +case class City(name: String, population: Int) + +def getCityInfo(city: City) = city match + case City(iam = n, confused = p) => // error // error + s"$n has a population of $p" // error // error + +def getCityInfo1(city: Option[City]) = city match + case Some(iam = n) => // error + n // error \ No newline at end of file diff --git a/tests/neg/i22192a.check b/tests/neg/i22192a.check new file mode 100644 index 000000000000..8bfbcb127c54 --- /dev/null +++ b/tests/neg/i22192a.check @@ -0,0 +1,20 @@ +-- Error: tests/neg/i22192a.scala:6:12 --------------------------------------------------------------------------------- +6 | case Some(iam = n) => // error + | ^^^^^^^ + | No element named `iam` is defined in selector type (name : String) +-- [E006] Not Found Error: tests/neg/i22192a.scala:7:4 ----------------------------------------------------------------- +7 | n // error + | ^ + | Not found: n + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg/i22192a.scala:11:12 -------------------------------------------------------------------------------- +11 | case Some(iam = n) => // error + | ^^^^^^^ + | No element named `iam` is defined in selector type (name : String, population : Int) +-- [E006] Not Found Error: tests/neg/i22192a.scala:12:4 ---------------------------------------------------------------- +12 | n // error + | ^ + | Not found: n + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i22192a.scala b/tests/neg/i22192a.scala new file mode 100644 index 000000000000..6460e613d517 --- /dev/null +++ b/tests/neg/i22192a.scala @@ -0,0 +1,13 @@ +import scala.language.experimental.namedTuples + +type City = (name: String) + +def getCityInfo(city: Option[City]) = city match + case Some(iam = n) => // error + n // error + case _ => + +def getCiryInfo1(city: Option[(name: String, population: Int)]) = city match + case Some(iam = n) => // error + n // error + case _ => \ No newline at end of file diff --git a/tests/pos/i22192.scala b/tests/pos/i22192.scala new file mode 100644 index 000000000000..4214a56f4b38 --- /dev/null +++ b/tests/pos/i22192.scala @@ -0,0 +1,15 @@ +import scala.language.experimental.namedTuples + +case class City(name: String, population: Int) + +def getCityInfo(city: City) = city match + case City(population = p, name = n) => + s"$n has a population of $p" + +def getCityInfo1(city: Option[(name: String)]) = city match + case Some(name = n) => n + case _ => + +def getCityInfo2(city: Option[(name: String, population: Int)]) = city match + case Some(name = n) => n + case _ => From 1f28390a5b2727658889d396e0fbbadc628d176f Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 7 Jan 2025 15:09:32 +0100 Subject: [PATCH 164/202] Try implict searching after finding dynamic select --- .../src/dotty/tools/dotc/typer/Typer.scala | 12 ++++----- tests/pos/i20512.scala | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 tests/pos/i20512.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c941ffe74e18..76b853c4aabd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -852,11 +852,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else EmptyTree def dynamicSelect(pt: Type) = - val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName) - if pt.isInstanceOf[FunOrPolyProto] || pt == LhsProto then - assignType(tree2, TryDynamicCallType) - else - typedDynamicSelect(tree2, Nil, pt) + val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName) + if pt.isInstanceOf[FunOrPolyProto] || pt == LhsProto then + assignType(tree2, TryDynamicCallType) + else + typedDynamicSelect(tree2, Nil, pt) // Otherwise, if the qualifier derives from class Dynamic, expand to a // dynamic dispatch using selectDynamic or applyDynamic @@ -885,7 +885,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Reject corner case where selectDynamic needs annother selectDynamic to be called. E.g. as in neg/unselectable-fields.scala. report.error(i"Cannot use selectDynamic here since it needs another selectDynamic to be invoked", tree.srcPos) case _ => - dynSelected.ensureConforms(fieldType) + adapt(dynSelected, defn.AnyType).ensureConforms(fieldType) case _ => EmptyTree else EmptyTree diff --git a/tests/pos/i20512.scala b/tests/pos/i20512.scala new file mode 100644 index 000000000000..d30a3df9fc7c --- /dev/null +++ b/tests/pos/i20512.scala @@ -0,0 +1,26 @@ +import language.experimental.namedTuples + +import NamedTuple.* + +trait Selector1 extends Selectable { + type Fields = (int: Int, str: String) + + def selectDynamic(name: String)(using name.type <:< Tuple.Union[NamedTuple.Names[Fields]]) = ??? +} + +def test20512 = { + val s: Selector1 = new Selector1 {} + val int = s.int + val str = s.str +} + +trait Ctx + +class Selector2 extends Selectable: + type Fields = (bar: Int, baz: Int) + def selectDynamic(fieldName: String)(using Ctx): Any = ??? + +def test22023(using Ctx) = + val f = Selector2() + val bar = f.bar + val baz = f.baz \ No newline at end of file From 200548e1c3e3b861090d2502a68c7bcacc057131 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 7 Jan 2025 16:13:49 +0100 Subject: [PATCH 165/202] Add test to pickling list --- compiler/test/dotc/pos-test-pickling.blacklist | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index f35d527edf62..23c79affada0 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -70,6 +70,7 @@ i18211.scala 10867.scala named-tuples1.scala i20897.scala +i20512.scala # Opaque type i5720.scala From 2ae14c270e7f8ec1d887fc851c79a236d1fd5661 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Wed, 8 Jan 2025 11:29:25 +0100 Subject: [PATCH 166/202] chore: add regression test for #22320 --- tests/neg/i22320.check | 12 ++++++++++++ tests/neg/i22320.scala | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tests/neg/i22320.check create mode 100644 tests/neg/i22320.scala diff --git a/tests/neg/i22320.check b/tests/neg/i22320.check new file mode 100644 index 000000000000..3bada9a0b73c --- /dev/null +++ b/tests/neg/i22320.check @@ -0,0 +1,12 @@ +-- [E008] Not Found Error: tests/neg/i22320.scala:19:19 ---------------------------------------------------------------- +19 | val z = system.z // error + | ^^^^^^^^ + | value z is not a member of a.System. + | An extension method was tried, but could not be fully constructed: + | + | a.z(system) + | + | failed with: + | + | Found: (system : a.System) + | Required: a.SimulatedSystem diff --git a/tests/neg/i22320.scala b/tests/neg/i22320.scala new file mode 100644 index 000000000000..4a9eccf08474 --- /dev/null +++ b/tests/neg/i22320.scala @@ -0,0 +1,19 @@ +package a: + opaque type System = Any + opaque type SimulatedSystem <: System = System + + extension (system: System) + def x: BigInt = ??? + def y: BigInt = ??? + end extension + + extension (system: SimulatedSystem) + def z: BigInt = ??? + end extension + +package b: + import a.* + def issue(system: System) = + val x = system.x + val y = system.y + val z = system.z // error \ No newline at end of file From 1f2a8354cf26a964797af2a5bd684485f68e78e2 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Wed, 8 Jan 2025 11:49:48 +0100 Subject: [PATCH 167/202] fix: align the spec to allow the marker --- docs/_spec/01-lexical-syntax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_spec/01-lexical-syntax.md b/docs/_spec/01-lexical-syntax.md index e1686204116e..b7fb455fc2fd 100644 --- a/docs/_spec/01-lexical-syntax.md +++ b/docs/_spec/01-lexical-syntax.md @@ -137,7 +137,7 @@ Otherwise, soft keywords are treated as actual keywords in the following situati - `as`, if it appears in a renaming import clause. - `derives`, if it appears after an extension clause or after the name and possibly parameters of a class, trait, object, or enum definition. - `end`, if it appears at the start of a line following a statement (i.e. definition or toplevel expression) and is followed on the same line by a single non-comment token that is: - - one of the keywords `for`, `given`, `if`, `match`, `new`, `this`, `throw`, `try`, `val`, `while`, or + - one of the keywords `for`, `given`, `if`, `match`, `new`, `this`, `throw`, `try`, `val`, `while`, `extension` or - an identifier. - `extension`, if it appears at the start of a statement and is followed by `(` or `[`. - `inline`, if it is followed by any token that can start an expression. From 5ea7c13070145c62111f9cb230ec2c4c2190b43f Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 13 Dec 2024 12:53:50 +0100 Subject: [PATCH 168/202] Add REPL init script setting --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 1 + compiler/src/dotty/tools/repl/ReplDriver.scala | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 6a8a88a429e5..143bdd80de33 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -126,6 +126,7 @@ trait CommonScalaSettings: val encoding: Setting[String] = StringSetting(RootSetting, "encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding, aliases = List("--encoding")) val usejavacp: Setting[Boolean] = BooleanSetting(RootSetting, "usejavacp", "Utilize the java.class.path in classpath resolution.", aliases = List("--use-java-class-path")) val scalajs: Setting[Boolean] = BooleanSetting(RootSetting, "scalajs", "Compile in Scala.js mode (requires scalajs-library.jar on the classpath).", aliases = List("--scalajs")) + val replInitScript: Setting[String] = StringSetting(RootSetting, "repl-init-script", "code", "The code will be run on REPL startup.", "") end CommonScalaSettings /** -P "plugin" settings. Various tools might support plugins. */ diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 3a2b8ce00bbe..96aa31b8753b 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -116,7 +116,12 @@ class ReplDriver(settings: Array[String], } /** the initial, empty state of the REPL session */ - final def initialState: State = State(0, 0, Map.empty, Set.empty, false, rootCtx) + final def initialState: State = + val emptyState = State(0, 0, Map.empty, Set.empty, false, rootCtx) + val initScript = rootCtx.settings.replInitScript.value(using rootCtx) + initScript.trim() match + case "" => emptyState + case script => run(script)(using emptyState) /** Reset state of repl to the initial state * From 00e07c46ff3c29ae053508bedac21d29ccdd930c Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 8 Jan 2025 15:44:13 +0100 Subject: [PATCH 169/202] Update default value --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 143bdd80de33..599812a9a390 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -126,7 +126,7 @@ trait CommonScalaSettings: val encoding: Setting[String] = StringSetting(RootSetting, "encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding, aliases = List("--encoding")) val usejavacp: Setting[Boolean] = BooleanSetting(RootSetting, "usejavacp", "Utilize the java.class.path in classpath resolution.", aliases = List("--use-java-class-path")) val scalajs: Setting[Boolean] = BooleanSetting(RootSetting, "scalajs", "Compile in Scala.js mode (requires scalajs-library.jar on the classpath).", aliases = List("--scalajs")) - val replInitScript: Setting[String] = StringSetting(RootSetting, "repl-init-script", "code", "The code will be run on REPL startup.", "") + val replInitScript: Setting[String] = StringSetting(RootSetting, "repl-init-script", "code", "The code will be run on REPL startup.", "", aliases = List("--repl-init-script")) end CommonScalaSettings /** -P "plugin" settings. Various tools might support plugins. */ From 87e084e4dab871ea3787addea3b233dd152fe07a Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 8 Jan 2025 15:52:51 +0100 Subject: [PATCH 170/202] Add a script test --- compiler/test-resources/repl/init-script-flag | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 compiler/test-resources/repl/init-script-flag diff --git a/compiler/test-resources/repl/init-script-flag b/compiler/test-resources/repl/init-script-flag new file mode 100644 index 000000000000..373f21e15e93 --- /dev/null +++ b/compiler/test-resources/repl/init-script-flag @@ -0,0 +1,5 @@ +scala>:reset --repl-init-script:'println("Hello from init script!")' +Resetting REPL state with the following settings: + --repl-init-script:println("Hello from init script!") + +Hello from init script! From 34e5d5675d14199f1b288430c47c9972b2ffcf24 Mon Sep 17 00:00:00 2001 From: Seth Tisue Date: Wed, 8 Jan 2025 15:25:02 -0800 Subject: [PATCH 171/202] copyright 2025 note that I added "dba Akka" to NOTICE.md but I don't believe it's necessary to pollute the version history adding that to the top of every source file, too. in legal contexts, "Lightbend, Inc." is still the company's legal name --- NOTICE.md | 19 ++++++++++--------- pkgs/chocolatey/scala.nuspec | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index fd931397a500..b3f97913df2f 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1,6 +1,6 @@ -Dotty (https://dotty.epfl.ch) -Copyright 2012-2024 EPFL -Copyright 2012-2024 Lightbend, Inc. +Scala 3 (https://www.scala-lang.org) +Copyright 2012-2025 EPFL +Copyright 2012-2025 Lightbend, Inc. dba Akka Licensed under the Apache License, Version 2.0 (the "License"): http://www.apache.org/licenses/LICENSE-2.0 @@ -11,12 +11,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -The dotty compiler frontend has been developed since November 2012 by Martin -Odersky. It is expected and hoped for that the list of contributors to the -codebase will grow quickly. Dotty draws inspiration and code from the original -Scala compiler "nsc", which is developed at scala/scala [1]. +The Scala 3 compiler is also known as Dotty. The Dotty compiler +frontend has been developed since November 2012 by Martin Odersky. It +is expected and hoped for that the list of contributors to the +codebase will grow quickly. Dotty draws inspiration and code from the +original Scala 2 compiler "nsc", which is still developed at scala/scala [1]. -The majority of the dotty codebase is new code, with the exception of the +The majority of the Dotty codebase is new code, with the exception of the components mentioned below. We have for each component tried to come up with a list of the original authors in the scala/scala [1] codebase. Apologies if some major authors were omitted by oversight. @@ -28,7 +29,7 @@ major authors were omitted by oversight. * dotty.tools.dotc.classpath: The classpath handling is taken mostly as is from scala/scala [1]. The original authors were Grzegorz Kossakowski, - Michał Pociecha, Lukas Rytz, Jason Zaugg and others. + Michał Pociecha, Lukas Rytz, Jason Zaugg and others. * dotty.tools.dotc.config: The configuration components were adapted and extended from scala/scala [1]. The original sources were authored by Paul diff --git a/pkgs/chocolatey/scala.nuspec b/pkgs/chocolatey/scala.nuspec index bb2e0e07ce70..83033fe4b349 100644 --- a/pkgs/chocolatey/scala.nuspec +++ b/pkgs/chocolatey/scala.nuspec @@ -13,7 +13,7 @@ https://github.com/scala/scala3 https://scala-lang.org/ https://github.com/scala/scala3/issues - © 2002-2024, LAMP/EPFL + © 2002-2025, LAMP/EPFL https://cdn.jsdelivr.net/gh/scala/scala3@a046b0014ffd9536144d67a48f8759901b96d12f/pkgs/chocolatey/icon.svg https://github.com/scala/scala3/blob/main/LICENSE true From bbdf3cb046639b4ca10d347a8b91efd8be77d864 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 9 Jan 2025 17:21:09 +0100 Subject: [PATCH 172/202] Move cc tests to captures subdirectories --- .../captures}/capt-wf.scala | 0 .../captures}/cc-ex-conformance.scala | 0 .../captures}/cc-poly-1.check | 4 +-- .../captures}/cc-poly-1.scala | 0 .../captures}/cc-poly-2.check | 4 +-- .../captures}/cc-poly-2.scala | 0 .../captures}/existential-mapping.check | 26 +++++++++---------- .../captures}/existential-mapping.scala | 0 .../captures}/i19470.check | 2 +- .../captures}/i19470.scala | 0 .../captures}/i20503.scala | 0 .../captures}/language-imports.scala | 0 .../captures}/leak-problem-unboxed.scala | 0 .../captures}/leak-problem.scala | 0 .../captures}/annot-i20272a.scala | 0 .../captures}/byname-purefuns-adapt/A_1.scala | 0 .../captures}/byname-purefuns-adapt/B_2.scala | 0 .../captures}/cc-experimental.scala | 0 .../captures}/cc-poly-1.scala | 0 .../captures}/cc-poly-source-capability.scala | 0 .../captures}/cc-poly-source.scala | 0 .../captures}/dotty-experimental.scala | 0 .../captures}/i18699.scala | 0 .../captures}/i18909.scala | 0 .../captures}/i20135.scala | 0 .../captures}/i21390.TrieMap.scala | 0 .../captures}/infer-exists.scala | 0 .../captures}/invariant-cc.scala | 0 .../captures}/polycap.scala | 0 .../captures}/reach-capability.scala | 0 .../captures}/reach-problem.scala | 0 tests/pos/Buffer.scala | 22 ---------------- 32 files changed, 18 insertions(+), 40 deletions(-) rename tests/{neg => neg-custom-args/captures}/capt-wf.scala (100%) rename tests/{neg => neg-custom-args/captures}/cc-ex-conformance.scala (100%) rename tests/{neg => neg-custom-args/captures}/cc-poly-1.check (60%) rename tests/{neg => neg-custom-args/captures}/cc-poly-1.scala (100%) rename tests/{neg => neg-custom-args/captures}/cc-poly-2.check (65%) rename tests/{neg => neg-custom-args/captures}/cc-poly-2.scala (100%) rename tests/{neg => neg-custom-args/captures}/existential-mapping.check (70%) rename tests/{neg => neg-custom-args/captures}/existential-mapping.scala (100%) rename tests/{neg => neg-custom-args/captures}/i19470.check (62%) rename tests/{neg => neg-custom-args/captures}/i19470.scala (100%) rename tests/{neg => neg-custom-args/captures}/i20503.scala (100%) rename tests/{neg => neg-custom-args/captures}/language-imports.scala (100%) rename tests/{neg => neg-custom-args/captures}/leak-problem-unboxed.scala (100%) rename tests/{neg => neg-custom-args/captures}/leak-problem.scala (100%) rename tests/{pos => pos-custom-args/captures}/annot-i20272a.scala (100%) rename tests/{pos => pos-custom-args/captures}/byname-purefuns-adapt/A_1.scala (100%) rename tests/{pos => pos-custom-args/captures}/byname-purefuns-adapt/B_2.scala (100%) rename tests/{pos => pos-custom-args/captures}/cc-experimental.scala (100%) rename tests/{pos => pos-custom-args/captures}/cc-poly-1.scala (100%) rename tests/{pos => pos-custom-args/captures}/cc-poly-source-capability.scala (100%) rename tests/{pos => pos-custom-args/captures}/cc-poly-source.scala (100%) rename tests/{pos => pos-custom-args/captures}/dotty-experimental.scala (100%) rename tests/{pos => pos-custom-args/captures}/i18699.scala (100%) rename tests/{pos => pos-custom-args/captures}/i18909.scala (100%) rename tests/{pos => pos-custom-args/captures}/i20135.scala (100%) rename tests/{pos => pos-custom-args/captures}/i21390.TrieMap.scala (100%) rename tests/{pos => pos-custom-args/captures}/infer-exists.scala (100%) rename tests/{pos => pos-custom-args/captures}/invariant-cc.scala (100%) rename tests/{pos => pos-custom-args/captures}/polycap.scala (100%) rename tests/{pos => pos-custom-args/captures}/reach-capability.scala (100%) rename tests/{pos => pos-custom-args/captures}/reach-problem.scala (100%) delete mode 100644 tests/pos/Buffer.scala diff --git a/tests/neg/capt-wf.scala b/tests/neg-custom-args/captures/capt-wf.scala similarity index 100% rename from tests/neg/capt-wf.scala rename to tests/neg-custom-args/captures/capt-wf.scala diff --git a/tests/neg/cc-ex-conformance.scala b/tests/neg-custom-args/captures/cc-ex-conformance.scala similarity index 100% rename from tests/neg/cc-ex-conformance.scala rename to tests/neg-custom-args/captures/cc-ex-conformance.scala diff --git a/tests/neg/cc-poly-1.check b/tests/neg-custom-args/captures/cc-poly-1.check similarity index 60% rename from tests/neg/cc-poly-1.check rename to tests/neg-custom-args/captures/cc-poly-1.check index abb507078bf4..ea486f55a61f 100644 --- a/tests/neg/cc-poly-1.check +++ b/tests/neg-custom-args/captures/cc-poly-1.check @@ -1,10 +1,10 @@ --- [E057] Type Mismatch Error: tests/neg/cc-poly-1.scala:12:6 ---------------------------------------------------------- +-- [E057] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-1.scala:12:6 ------------------------------------- 12 | f[Any](D()) // error | ^ | Type argument Any does not conform to upper bound caps.CapSet^ | | longer explanation available when compiling with `-explain` --- [E057] Type Mismatch Error: tests/neg/cc-poly-1.scala:13:6 ---------------------------------------------------------- +-- [E057] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-1.scala:13:6 ------------------------------------- 13 | f[String](D()) // error | ^ | Type argument String does not conform to upper bound caps.CapSet^ diff --git a/tests/neg/cc-poly-1.scala b/tests/neg-custom-args/captures/cc-poly-1.scala similarity index 100% rename from tests/neg/cc-poly-1.scala rename to tests/neg-custom-args/captures/cc-poly-1.scala diff --git a/tests/neg/cc-poly-2.check b/tests/neg-custom-args/captures/cc-poly-2.check similarity index 65% rename from tests/neg/cc-poly-2.check rename to tests/neg-custom-args/captures/cc-poly-2.check index 7a2882775a75..90568de385b3 100644 --- a/tests/neg/cc-poly-2.check +++ b/tests/neg-custom-args/captures/cc-poly-2.check @@ -1,11 +1,11 @@ --- [E007] Type Mismatch Error: tests/neg/cc-poly-2.scala:14:19 --------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-2.scala:14:19 ------------------------------------ 14 | f[CapSet^{c1}](d) // error | ^ | Found: (d : Test.D^) | Required: Test.D^{c1} | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/cc-poly-2.scala:16:20 --------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-2.scala:16:20 ------------------------------------ 16 | val _: D^{c1} = x // error | ^ | Found: (x : Test.D^{d}) diff --git a/tests/neg/cc-poly-2.scala b/tests/neg-custom-args/captures/cc-poly-2.scala similarity index 100% rename from tests/neg/cc-poly-2.scala rename to tests/neg-custom-args/captures/cc-poly-2.scala diff --git a/tests/neg/existential-mapping.check b/tests/neg-custom-args/captures/existential-mapping.check similarity index 70% rename from tests/neg/existential-mapping.check rename to tests/neg-custom-args/captures/existential-mapping.check index edfce67f6eef..cd71337868e1 100644 --- a/tests/neg/existential-mapping.check +++ b/tests/neg-custom-args/captures/existential-mapping.check @@ -1,85 +1,85 @@ --- Error: tests/neg/existential-mapping.scala:44:13 -------------------------------------------------------------------- +-- Error: tests/neg-custom-args/captures/existential-mapping.scala:44:13 ----------------------------------------------- 44 | val z1: A^ => Array[C^] = ??? // error | ^^^^^^^^^^^^^^^ | Array[box C^] captures the root capability `cap` in invariant position --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:9:25 ------------------------------------------------ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:9:25 --------------------------- 9 | val _: (x: C^) -> C = x1 // error | ^^ | Found: (x1 : (x: C^) -> (ex$3: caps.Exists) -> C^{ex$3}) | Required: (x: C^) -> C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:12:20 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:12:20 -------------------------- 12 | val _: C^ -> C = x2 // error | ^^ | Found: (x2 : C^ -> (ex$7: caps.Exists) -> C^{ex$7}) | Required: C^ -> C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:15:30 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:15:30 -------------------------- 15 | val _: A^ -> (x: C^) -> C = x3 // error | ^^ | Found: (x3 : A^ -> (x: C^) -> (ex$11: caps.Exists) -> C^{ex$11}) | Required: A^ -> (x: C^) -> C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:18:25 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:18:25 -------------------------- 18 | val _: A^ -> C^ -> C = x4 // error | ^^ | Found: (x4 : A^ -> C^ -> (ex$19: caps.Exists) -> C^{ex$19}) | Required: A^ -> C^ -> C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:21:30 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:21:30 -------------------------- 21 | val _: A^ -> (x: C^) -> C = x5 // error | ^^ | Found: (x5 : A^ -> (ex$27: caps.Exists) -> Fun[C^{ex$27}]) | Required: A^ -> (x: C^) -> C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:24:30 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:24:30 -------------------------- 24 | val _: A^ -> (x: C^) => C = x6 // error | ^^ | Found: (x6 : A^ -> (ex$33: caps.Exists) -> IFun[C^{ex$33}]) | Required: A^ -> (ex$36: caps.Exists) -> (x: C^) ->{ex$36} C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:27:25 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:27:25 -------------------------- 27 | val _: (x: C^) => C = y1 // error | ^^ | Found: (y1 : (x: C^) => (ex$38: caps.Exists) -> C^{ex$38}) | Required: (x: C^) => C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:30:20 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:30:20 -------------------------- 30 | val _: C^ => C = y2 // error | ^^ | Found: (y2 : C^ => (ex$42: caps.Exists) -> C^{ex$42}) | Required: C^ => C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:33:30 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:33:30 -------------------------- 33 | val _: A^ => (x: C^) => C = y3 // error | ^^ | Found: (y3 : A^ => (ex$47: caps.Exists) -> (x: C^) ->{ex$47} (ex$46: caps.Exists) -> C^{ex$46}) | Required: A^ => (ex$50: caps.Exists) -> (x: C^) ->{ex$50} C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:36:25 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:36:25 -------------------------- 36 | val _: A^ => C^ => C = y4 // error | ^^ | Found: (y4 : A^ => (ex$53: caps.Exists) -> C^ ->{ex$53} (ex$52: caps.Exists) -> C^{ex$52}) | Required: A^ => (ex$56: caps.Exists) -> C^ ->{ex$56} C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:39:30 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:39:30 -------------------------- 39 | val _: A^ => (x: C^) -> C = y5 // error | ^^ | Found: (y5 : A^ => (ex$58: caps.Exists) -> Fun[C^{ex$58}]) | Required: A^ => (x: C^) -> C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:42:30 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:42:30 -------------------------- 42 | val _: A^ => (x: C^) => C = y6 // error | ^^ | Found: (y6 : A^ => (ex$64: caps.Exists) -> IFun[C^{ex$64}]) diff --git a/tests/neg/existential-mapping.scala b/tests/neg-custom-args/captures/existential-mapping.scala similarity index 100% rename from tests/neg/existential-mapping.scala rename to tests/neg-custom-args/captures/existential-mapping.scala diff --git a/tests/neg/i19470.check b/tests/neg-custom-args/captures/i19470.check similarity index 62% rename from tests/neg/i19470.check rename to tests/neg-custom-args/captures/i19470.check index fdb336bef7e5..68eba72c92bd 100644 --- a/tests/neg/i19470.check +++ b/tests/neg-custom-args/captures/i19470.check @@ -1,4 +1,4 @@ --- [E007] Type Mismatch Error: tests/neg/i19470.scala:9:12 ------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i19470.scala:9:12 ---------------------------------------- 9 | List(foo(f())) // error | ^^^^^^^^ | Found: Inv[box IO^{f?}] diff --git a/tests/neg/i19470.scala b/tests/neg-custom-args/captures/i19470.scala similarity index 100% rename from tests/neg/i19470.scala rename to tests/neg-custom-args/captures/i19470.scala diff --git a/tests/neg/i20503.scala b/tests/neg-custom-args/captures/i20503.scala similarity index 100% rename from tests/neg/i20503.scala rename to tests/neg-custom-args/captures/i20503.scala diff --git a/tests/neg/language-imports.scala b/tests/neg-custom-args/captures/language-imports.scala similarity index 100% rename from tests/neg/language-imports.scala rename to tests/neg-custom-args/captures/language-imports.scala diff --git a/tests/neg/leak-problem-unboxed.scala b/tests/neg-custom-args/captures/leak-problem-unboxed.scala similarity index 100% rename from tests/neg/leak-problem-unboxed.scala rename to tests/neg-custom-args/captures/leak-problem-unboxed.scala diff --git a/tests/neg/leak-problem.scala b/tests/neg-custom-args/captures/leak-problem.scala similarity index 100% rename from tests/neg/leak-problem.scala rename to tests/neg-custom-args/captures/leak-problem.scala diff --git a/tests/pos/annot-i20272a.scala b/tests/pos-custom-args/captures/annot-i20272a.scala similarity index 100% rename from tests/pos/annot-i20272a.scala rename to tests/pos-custom-args/captures/annot-i20272a.scala diff --git a/tests/pos/byname-purefuns-adapt/A_1.scala b/tests/pos-custom-args/captures/byname-purefuns-adapt/A_1.scala similarity index 100% rename from tests/pos/byname-purefuns-adapt/A_1.scala rename to tests/pos-custom-args/captures/byname-purefuns-adapt/A_1.scala diff --git a/tests/pos/byname-purefuns-adapt/B_2.scala b/tests/pos-custom-args/captures/byname-purefuns-adapt/B_2.scala similarity index 100% rename from tests/pos/byname-purefuns-adapt/B_2.scala rename to tests/pos-custom-args/captures/byname-purefuns-adapt/B_2.scala diff --git a/tests/pos/cc-experimental.scala b/tests/pos-custom-args/captures/cc-experimental.scala similarity index 100% rename from tests/pos/cc-experimental.scala rename to tests/pos-custom-args/captures/cc-experimental.scala diff --git a/tests/pos/cc-poly-1.scala b/tests/pos-custom-args/captures/cc-poly-1.scala similarity index 100% rename from tests/pos/cc-poly-1.scala rename to tests/pos-custom-args/captures/cc-poly-1.scala diff --git a/tests/pos/cc-poly-source-capability.scala b/tests/pos-custom-args/captures/cc-poly-source-capability.scala similarity index 100% rename from tests/pos/cc-poly-source-capability.scala rename to tests/pos-custom-args/captures/cc-poly-source-capability.scala diff --git a/tests/pos/cc-poly-source.scala b/tests/pos-custom-args/captures/cc-poly-source.scala similarity index 100% rename from tests/pos/cc-poly-source.scala rename to tests/pos-custom-args/captures/cc-poly-source.scala diff --git a/tests/pos/dotty-experimental.scala b/tests/pos-custom-args/captures/dotty-experimental.scala similarity index 100% rename from tests/pos/dotty-experimental.scala rename to tests/pos-custom-args/captures/dotty-experimental.scala diff --git a/tests/pos/i18699.scala b/tests/pos-custom-args/captures/i18699.scala similarity index 100% rename from tests/pos/i18699.scala rename to tests/pos-custom-args/captures/i18699.scala diff --git a/tests/pos/i18909.scala b/tests/pos-custom-args/captures/i18909.scala similarity index 100% rename from tests/pos/i18909.scala rename to tests/pos-custom-args/captures/i18909.scala diff --git a/tests/pos/i20135.scala b/tests/pos-custom-args/captures/i20135.scala similarity index 100% rename from tests/pos/i20135.scala rename to tests/pos-custom-args/captures/i20135.scala diff --git a/tests/pos/i21390.TrieMap.scala b/tests/pos-custom-args/captures/i21390.TrieMap.scala similarity index 100% rename from tests/pos/i21390.TrieMap.scala rename to tests/pos-custom-args/captures/i21390.TrieMap.scala diff --git a/tests/pos/infer-exists.scala b/tests/pos-custom-args/captures/infer-exists.scala similarity index 100% rename from tests/pos/infer-exists.scala rename to tests/pos-custom-args/captures/infer-exists.scala diff --git a/tests/pos/invariant-cc.scala b/tests/pos-custom-args/captures/invariant-cc.scala similarity index 100% rename from tests/pos/invariant-cc.scala rename to tests/pos-custom-args/captures/invariant-cc.scala diff --git a/tests/pos/polycap.scala b/tests/pos-custom-args/captures/polycap.scala similarity index 100% rename from tests/pos/polycap.scala rename to tests/pos-custom-args/captures/polycap.scala diff --git a/tests/pos/reach-capability.scala b/tests/pos-custom-args/captures/reach-capability.scala similarity index 100% rename from tests/pos/reach-capability.scala rename to tests/pos-custom-args/captures/reach-capability.scala diff --git a/tests/pos/reach-problem.scala b/tests/pos-custom-args/captures/reach-problem.scala similarity index 100% rename from tests/pos/reach-problem.scala rename to tests/pos-custom-args/captures/reach-problem.scala diff --git a/tests/pos/Buffer.scala b/tests/pos/Buffer.scala deleted file mode 100644 index 9ecd51ffa62a..000000000000 --- a/tests/pos/Buffer.scala +++ /dev/null @@ -1,22 +0,0 @@ -import language.experimental.captureChecking - -// Extract of the problem in collection.mutable.Buffers -trait Buffer[A]: - - def apply(i: Int): A = ??? - - def flatMapInPlace(f: A => IterableOnce[A]^): this.type = { - val g = f - val s = 10 - // capture checking: we need the copy since we box/unbox on g* on the next line - // TODO: This looks fishy, need to investigate - // Alternative would be to mark `f` as @use. It's not inferred - // since `^ appears in a function result, not under a box. - val newElems = new Array[(IterableOnce[A]^{f})](s) - var i = 0 - while i < s do - val x = g(this(i)) - newElems(i) = x - i += 1 - this - } \ No newline at end of file From f06b95f1ddd195a3ef8c2e8947b40d8954aeacfb Mon Sep 17 00:00:00 2001 From: Jan Chyb <48855024+jchyb@users.noreply.github.com> Date: Fri, 10 Jan 2025 13:39:53 +0100 Subject: [PATCH 173/202] Fix coverage serialization when encountering macro suspension (#22303) Fixes #22247 The fix is simple, as we mainly move the coverage object to a global ContextBase object, which persists it between runs. Initially I thought that appending the newly generated coverage indices would be enough, but if the macro suspends after the InstrumentCoverage phase runs, we end up with duplicate indices. For that reason, when generating indexes for a compilation unit, we also remove the previously generated ones for the same compilation unit. To support having multiple scala files compiled in the coverage tests I had to slightly adjust the suite. While doing that, I noticed that some check files for run tests were ignored, as they were incorrectly named. I added an assertion that throws when `.check` do not exist and renamed the files appropriately (having to add some additional ones as well). --- .../src/dotty/tools/dotc/core/Contexts.scala | 6 + .../dotty/tools/dotc/coverage/Coverage.scala | 12 ++ .../dotc/transform/InstrumentCoverage.scala | 18 +- .../tools/dotc/coverage/CoverageTests.scala | 41 +++-- .../dotty/tools/vulpix/ParallelTesting.scala | 24 ++- .../pos/macro-late-suspend/Test.scala | 13 ++ .../pos/macro-late-suspend/UsesTest.scala | 3 + .../macro-late-suspend/VisitorMacros.scala | 12 ++ .../macro-late-suspend/test.scoverage.check | 88 +++++++++ tests/coverage/run/i16940/i16940.check | 1 + ...0.scoverage.check => test.scoverage.check} | 0 .../coverage/run/i18233-min/i182233-min.check | 2 + ...n.scoverage.check => test.scoverage.check} | 0 tests/coverage/run/i18233/i18233.check | 1 + ...3.scoverage.check => test.scoverage.check} | 0 .../{test.check => JavaObject.check} | 0 tests/coverage/run/macro-suspend/Macro.check | 1 + tests/coverage/run/macro-suspend/Macro.scala | 8 + tests/coverage/run/macro-suspend/Test.scala | 4 + .../run/macro-suspend/test.scoverage.check | 173 ++++++++++++++++++ .../coverage/run/varargs/JavaVarargs_1.check | 3 + .../run/varargs/{test_1.check => test.check} | 0 ...1.scoverage.check => test.scoverage.check} | 0 23 files changed, 378 insertions(+), 32 deletions(-) create mode 100644 tests/coverage/pos/macro-late-suspend/Test.scala create mode 100644 tests/coverage/pos/macro-late-suspend/UsesTest.scala create mode 100644 tests/coverage/pos/macro-late-suspend/VisitorMacros.scala create mode 100644 tests/coverage/pos/macro-late-suspend/test.scoverage.check create mode 100644 tests/coverage/run/i16940/i16940.check rename tests/coverage/run/i16940/{i16940.scoverage.check => test.scoverage.check} (100%) create mode 100644 tests/coverage/run/i18233-min/i182233-min.check rename tests/coverage/run/i18233-min/{i18233-min.scoverage.check => test.scoverage.check} (100%) create mode 100644 tests/coverage/run/i18233/i18233.check rename tests/coverage/run/i18233/{i18233.scoverage.check => test.scoverage.check} (100%) rename tests/coverage/run/java-methods/{test.check => JavaObject.check} (100%) create mode 100644 tests/coverage/run/macro-suspend/Macro.check create mode 100644 tests/coverage/run/macro-suspend/Macro.scala create mode 100644 tests/coverage/run/macro-suspend/Test.scala create mode 100644 tests/coverage/run/macro-suspend/test.scoverage.check create mode 100644 tests/coverage/run/varargs/JavaVarargs_1.check rename tests/coverage/run/varargs/{test_1.check => test.check} (100%) rename tests/coverage/run/varargs/{test_1.scoverage.check => test.scoverage.check} (100%) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 7f5779bb6127..7c54d1392720 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -41,6 +41,7 @@ import util.Store import plugins.* import java.util.concurrent.atomic.AtomicInteger import java.nio.file.InvalidPathException +import dotty.tools.dotc.coverage.Coverage object Contexts { @@ -982,6 +983,11 @@ object Contexts { /** Was best effort file used during compilation? */ private[core] var usedBestEffortTasty = false + /** If coverage option is used, it stores all instrumented statements (for InstrumentCoverage). + * We need this information to be persisted across different runs, so it's stored here. + */ + private[dotc] var coverage: Coverage | Null = null + // Types state /** A table for hash consing unique types */ private[core] val uniques: Uniques = Uniques() diff --git a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala index 98e67178fb69..3061bfa4ee5c 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala @@ -2,15 +2,27 @@ package dotty.tools.dotc package coverage import scala.collection.mutable +import java.nio.file.Path /** Holds a list of statements to include in the coverage reports. */ class Coverage: private val statementsById = new mutable.LongMap[Statement](256) + private var statementId: Int = 0 + + def nextStatementId(): Int = + statementId += 1 + statementId - 1 + + def statements: Iterable[Statement] = statementsById.values def addStatement(stmt: Statement): Unit = statementsById(stmt.id) = stmt + def removeStatementsFromFile(sourcePath: Path) = + val removedIds = statements.filter(_.location.sourcePath == sourcePath).map(_.id.toLong) + removedIds.foreach(statementsById.remove) + /** * A statement that can be invoked, and thus counted as "covered" by code coverage tools. diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index f5e0f8c63b58..0229284a1b5f 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -38,12 +38,6 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: override def isEnabled(using ctx: Context) = ctx.settings.coverageOutputDir.value.nonEmpty - // counter to assign a unique id to each statement - private var statementId = 0 - - // stores all instrumented statements - private val coverage = Coverage() - private var coverageExcludeClasslikePatterns: List[Pattern] = Nil private var coverageExcludeFilePatterns: List[Pattern] = Nil @@ -61,12 +55,17 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: .foreach(_.nn.delete()) end if + // Initialise a coverage object if it does not exist yet + if ctx.base.coverage == null then + ctx.base.coverage = Coverage() + coverageExcludeClasslikePatterns = ctx.settings.coverageExcludeClasslikes.value.map(_.r.pattern) coverageExcludeFilePatterns = ctx.settings.coverageExcludeFiles.value.map(_.r.pattern) + ctx.base.coverage.nn.removeStatementsFromFile(ctx.compilationUnit.source.file.absolute.jpath) super.run - Serializer.serialize(coverage, outputPath, ctx.settings.sourceroot.value) + Serializer.serialize(ctx.base.coverage.nn, outputPath, ctx.settings.sourceroot.value) private def isClassIncluded(sym: Symbol)(using Context): Boolean = val fqn = sym.fullName.toText(ctx.printerFn(ctx)).show @@ -110,8 +109,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: * @return the statement's id */ private def recordStatement(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Int = - val id = statementId - statementId += 1 + val id = ctx.base.coverage.nn.nextStatementId() val sourceFile = pos.source val statement = Statement( @@ -127,7 +125,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: treeName = tree.getClass.getSimpleName.nn, branch ) - coverage.addStatement(statement) + ctx.base.coverage.nn.addStatement(statement) id /** diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala index 7efa1f6f564e..f6460180cab9 100644 --- a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -54,15 +54,27 @@ class CoverageTests: lines end fixWindowsPaths - def runOnFile(p: Path): Boolean = - scalaFile.matches(p) && - (Properties.testsFilter.isEmpty || Properties.testsFilter.exists(p.toString.contains)) + def runOnFileOrDir(p: Path): Boolean = + (scalaFile.matches(p) || Files.isDirectory(p)) + && (p != dir) + && (Properties.testsFilter.isEmpty || Properties.testsFilter.exists(p.toString.contains)) + + Files.walk(dir, 1).filter(runOnFileOrDir).forEach(path => { + // measurement files only exist in the "run" category + // as these are generated at runtime by the scala.runtime.coverage.Invoker + val (targetDir, expectFile, expectMeasurementFile) = + if Files.isDirectory(path) then + val dirName = path.getFileName().toString + assert(!Files.walk(path).filter(scalaFile.matches(_)).toArray.isEmpty, s"No scala files found in test directory: ${path}") + val targetDir = computeCoverageInTmp(path, isDirectory = true, dir, run) + (targetDir, path.resolve(s"test.scoverage.check"), path.resolve(s"test.measurement.check")) + else + val fileName = path.getFileName.toString.stripSuffix(".scala") + val targetDir = computeCoverageInTmp(path, isDirectory = false, dir, run) + (targetDir, path.resolveSibling(s"${fileName}.scoverage.check"), path.resolveSibling(s"${fileName}.measurement.check")) - Files.walk(dir).filter(runOnFile).forEach(path => { - val fileName = path.getFileName.toString.stripSuffix(".scala") - val targetDir = computeCoverageInTmp(path, dir, run) val targetFile = targetDir.resolve(s"scoverage.coverage") - val expectFile = path.resolveSibling(s"$fileName.scoverage.check") + if updateCheckFiles then Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING) else @@ -72,9 +84,6 @@ class CoverageTests: val instructions = FileDiff.diffMessage(expectFile.toString, targetFile.toString) fail(s"Coverage report differs from expected data.\n$instructions") - // measurement files only exist in the "run" category - // as these are generated at runtime by the scala.runtime.coverage.Invoker - val expectMeasurementFile = path.resolveSibling(s"$fileName.measurement.check") if run && Files.exists(expectMeasurementFile) then // Note that this assumes that the test invoked was single threaded, @@ -95,14 +104,20 @@ class CoverageTests: }) /** Generates the coverage report for the given input file, in a temporary directory. */ - def computeCoverageInTmp(inputFile: Path, sourceRoot: Path, run: Boolean)(using TestGroup): Path = + def computeCoverageInTmp(inputFile: Path, isDirectory: Boolean, sourceRoot: Path, run: Boolean)(using TestGroup): Path = val target = Files.createTempDirectory("coverage") val options = defaultOptions.and("-Ycheck:instrumentCoverage", "-coverage-out", target.toString, "-sourceroot", sourceRoot.toString) if run then - val test = compileDir(inputFile.getParent.toString, options) + val path = if isDirectory then inputFile.toString else inputFile.getParent.toString + val test = compileDir(path, options) + test.checkFilePaths.foreach { checkFilePath => + assert(checkFilePath.exists, s"Expected checkfile for $path $checkFilePath does not exist.") + } test.checkRuns() else - val test = compileFile(inputFile.toString, options) + val test = + if isDirectory then compileDir(inputFile.toString, options) + else compileFile(inputFile.toString, options) test.checkCompile() target diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index e7e5936a4b29..29e64163b833 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -258,15 +258,7 @@ trait ParallelTesting extends RunnerOrchestration { self => * For a given test source, returns a check file against which the result of the test run * should be compared. Is used by implementations of this trait. */ - final def checkFile(testSource: TestSource): Option[JFile] = (testSource match { - case ts: JointCompilationSource => - ts.files.collectFirst { - case f if !f.isDirectory => - new JFile(f.getPath.replaceFirst("\\.(scala|java)$", ".check")) - } - case ts: SeparateCompilationSource => - Option(new JFile(ts.dir.getPath + ".check")) - }).filter(_.exists) + final def checkFile(testSource: TestSource): Option[JFile] = (CompilationLogic.checkFilePath(testSource)).filter(_.exists) /** * Checks if the given actual lines are the same as the ones in the check file. @@ -343,6 +335,18 @@ trait ParallelTesting extends RunnerOrchestration { self => } } + object CompilationLogic { + private[ParallelTesting] def checkFilePath(testSource: TestSource) = testSource match { + case ts: JointCompilationSource => + ts.files.collectFirst { + case f if !f.isDirectory => + new JFile(f.getPath.replaceFirst("\\.(scala|java)$", ".check")) + } + case ts: SeparateCompilationSource => + Option(new JFile(ts.dir.getPath + ".check")) + } + } + /** Each `Test` takes the `testSources` and performs the compilation and assertions * according to the implementing class "neg", "run" or "pos". */ @@ -1157,6 +1161,8 @@ trait ParallelTesting extends RunnerOrchestration { self => def this(targets: List[TestSource]) = this(targets, 1, true, None, false, false) + def checkFilePaths: List[JFile] = targets.map(CompilationLogic.checkFilePath).flatten + def copy(targets: List[TestSource], times: Int = times, shouldDelete: Boolean = shouldDelete, diff --git a/tests/coverage/pos/macro-late-suspend/Test.scala b/tests/coverage/pos/macro-late-suspend/Test.scala new file mode 100644 index 000000000000..bf008583c7d9 --- /dev/null +++ b/tests/coverage/pos/macro-late-suspend/Test.scala @@ -0,0 +1,13 @@ +package example + +sealed trait Test + +object Test { + case object Foo extends Test + + val visitorType = mkVisitorType[Test] + + trait Visitor[A] { + type V[a] = visitorType.Out[a] + } +} diff --git a/tests/coverage/pos/macro-late-suspend/UsesTest.scala b/tests/coverage/pos/macro-late-suspend/UsesTest.scala new file mode 100644 index 000000000000..803e93c328c9 --- /dev/null +++ b/tests/coverage/pos/macro-late-suspend/UsesTest.scala @@ -0,0 +1,3 @@ +package example + +val _ = Test.Foo diff --git a/tests/coverage/pos/macro-late-suspend/VisitorMacros.scala b/tests/coverage/pos/macro-late-suspend/VisitorMacros.scala new file mode 100644 index 000000000000..49ada0ff2180 --- /dev/null +++ b/tests/coverage/pos/macro-late-suspend/VisitorMacros.scala @@ -0,0 +1,12 @@ +package example + +import scala.quoted.* + +private def mkVisitorTypeImpl[T: Type](using q: Quotes): Expr[VisitorType[T]] = + '{new VisitorType[T]{}} + +transparent inline def mkVisitorType[T]: VisitorType[T] = ${ mkVisitorTypeImpl[T] } + +trait VisitorType[T] { + type Out[A] +} diff --git a/tests/coverage/pos/macro-late-suspend/test.scoverage.check b/tests/coverage/pos/macro-late-suspend/test.scoverage.check new file mode 100644 index 000000000000..f962705ed2ce --- /dev/null +++ b/tests/coverage/pos/macro-late-suspend/test.scoverage.check @@ -0,0 +1,88 @@ +# 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 +# ------------------------------------------ +1 +macro-late-suspend/VisitorMacros.scala +example +VisitorMacros$package +Object +example.VisitorMacros$package +mkVisitorTypeImpl +124 +144 +6 +unpickleExprV2 +Apply +false +0 +false +new VisitorType[T]{} + +2 +macro-late-suspend/VisitorMacros.scala +example +VisitorMacros$package +Object +example.VisitorMacros$package +mkVisitorTypeImpl +40 +69 +5 +mkVisitorTypeImpl +DefDef +false +0 +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 +Object +example.UsesTest$package + +22 +22 +3 + +Literal +true +0 +false + + diff --git a/tests/coverage/run/i16940/i16940.check b/tests/coverage/run/i16940/i16940.check new file mode 100644 index 000000000000..0cfbf08886fc --- /dev/null +++ b/tests/coverage/run/i16940/i16940.check @@ -0,0 +1 @@ +2 diff --git a/tests/coverage/run/i16940/i16940.scoverage.check b/tests/coverage/run/i16940/test.scoverage.check similarity index 100% rename from tests/coverage/run/i16940/i16940.scoverage.check rename to tests/coverage/run/i16940/test.scoverage.check diff --git a/tests/coverage/run/i18233-min/i182233-min.check b/tests/coverage/run/i18233-min/i182233-min.check new file mode 100644 index 000000000000..a4bb477e9e1f --- /dev/null +++ b/tests/coverage/run/i18233-min/i182233-min.check @@ -0,0 +1,2 @@ +List() +List(abc, def) diff --git a/tests/coverage/run/i18233-min/i18233-min.scoverage.check b/tests/coverage/run/i18233-min/test.scoverage.check similarity index 100% rename from tests/coverage/run/i18233-min/i18233-min.scoverage.check rename to tests/coverage/run/i18233-min/test.scoverage.check diff --git a/tests/coverage/run/i18233/i18233.check b/tests/coverage/run/i18233/i18233.check new file mode 100644 index 000000000000..a68818270123 --- /dev/null +++ b/tests/coverage/run/i18233/i18233.check @@ -0,0 +1 @@ +Baz diff --git a/tests/coverage/run/i18233/i18233.scoverage.check b/tests/coverage/run/i18233/test.scoverage.check similarity index 100% rename from tests/coverage/run/i18233/i18233.scoverage.check rename to tests/coverage/run/i18233/test.scoverage.check diff --git a/tests/coverage/run/java-methods/test.check b/tests/coverage/run/java-methods/JavaObject.check similarity index 100% rename from tests/coverage/run/java-methods/test.check rename to tests/coverage/run/java-methods/JavaObject.check diff --git a/tests/coverage/run/macro-suspend/Macro.check b/tests/coverage/run/macro-suspend/Macro.check new file mode 100644 index 000000000000..80a62d1af279 --- /dev/null +++ b/tests/coverage/run/macro-suspend/Macro.check @@ -0,0 +1 @@ +>>> hello <<< diff --git a/tests/coverage/run/macro-suspend/Macro.scala b/tests/coverage/run/macro-suspend/Macro.scala new file mode 100644 index 000000000000..dffe91a9a9b1 --- /dev/null +++ b/tests/coverage/run/macro-suspend/Macro.scala @@ -0,0 +1,8 @@ +import scala.quoted.{Expr, Quotes} + +object Macro: + inline def decorate(inline s: String): String = ${ decorateQuotes('s) } + def decorateQuotes(s: Expr[String])(using Quotes): Expr[String] = '{ ">>> " + $s + " <<<" } + +object Greeting: + def greet() = "hello" diff --git a/tests/coverage/run/macro-suspend/Test.scala b/tests/coverage/run/macro-suspend/Test.scala new file mode 100644 index 000000000000..09cf1c36526c --- /dev/null +++ b/tests/coverage/run/macro-suspend/Test.scala @@ -0,0 +1,4 @@ +object Test: + def main(args: Array[String]): Unit = + println(Macro.decorate(Greeting.greet())) + diff --git a/tests/coverage/run/macro-suspend/test.scoverage.check b/tests/coverage/run/macro-suspend/test.scoverage.check new file mode 100644 index 000000000000..759897eb7747 --- /dev/null +++ b/tests/coverage/run/macro-suspend/test.scoverage.check @@ -0,0 +1,173 @@ +# 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 +macro-suspend/Macro.scala + +Macro +Object +.Macro +decorateQuotes +195 +215 +5 +unpickleExprV2 +Apply +false +0 +false +">>> " + $s + " <<<" + +1 +macro-suspend/Macro.scala + +Macro +Object +.Macro +$anonfun +205 +206 +5 +apply +Apply +false +0 +false +s + +2 +macro-suspend/Macro.scala + +Macro +Object +.Macro +decorateQuotes +126 +144 +5 +decorateQuotes +DefDef +false +0 +false +def decorateQuotes + +3 +macro-suspend/Macro.scala + +Greeting +Object +.Greeting +greet +238 +247 +8 +greet +DefDef +false +0 +false +def greet + +4 +macro-suspend/Test.scala + +Test +Object +.Test +main +57 +98 +3 +println +Apply +false +0 +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 + +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 +main +DefDef +false +0 +false +def main + diff --git a/tests/coverage/run/varargs/JavaVarargs_1.check b/tests/coverage/run/varargs/JavaVarargs_1.check new file mode 100644 index 000000000000..24f879b660ff --- /dev/null +++ b/tests/coverage/run/varargs/JavaVarargs_1.check @@ -0,0 +1,3 @@ +first0 +first0 +first3 diff --git a/tests/coverage/run/varargs/test_1.check b/tests/coverage/run/varargs/test.check similarity index 100% rename from tests/coverage/run/varargs/test_1.check rename to tests/coverage/run/varargs/test.check diff --git a/tests/coverage/run/varargs/test_1.scoverage.check b/tests/coverage/run/varargs/test.scoverage.check similarity index 100% rename from tests/coverage/run/varargs/test_1.scoverage.check rename to tests/coverage/run/varargs/test.scoverage.check From 5134eed38a6a02a5ba0ffa95e87e7ab146bb5d83 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Fri, 10 Jan 2025 15:07:46 +0100 Subject: [PATCH 174/202] fix: connect the input to the compiler in sbt --- project/Build.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/project/Build.scala b/project/Build.scala index a7be508ce11a..b67974f4405d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -301,6 +301,7 @@ object Build { // Avoid various sbt craziness involving classloaders and parallelism run / fork := true, + run / connectInput := true, Test / fork := true, Test / parallelExecution := false, From dd6b6db752839f44039a49d1eeec00d2aaf872f3 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Fri, 10 Jan 2025 15:31:36 +0100 Subject: [PATCH 175/202] chore: normalise the types for Type Mismatch Error E007 --- compiler/src/dotty/tools/dotc/reporting/messages.scala | 2 +- tests/neg/i22333.check | 7 +++++++ tests/neg/i22333.scala | 5 +++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i22333.check create mode 100644 tests/neg/i22333.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 692b87f68821..ab4f40677371 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -343,7 +343,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) - val (foundStr, expectedStr) = Formatting.typeDiff(found2, expected2) + val (foundStr, expectedStr) = Formatting.typeDiff(found2.normalized, expected2.normalized) i"""|Found: $foundStr |Required: $expectedStr${reported.notes}""" end msg diff --git a/tests/neg/i22333.check b/tests/neg/i22333.check new file mode 100644 index 000000000000..8a4e5acd5464 --- /dev/null +++ b/tests/neg/i22333.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/i22333.scala:5:33 ------------------------------------------------------------- +5 |val _ = Container[(1,2,3)].guard((6, 8): Tuple.Tail[(5, 6, 8)]) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: ((6 : Int), (8 : Int)) + | Required: ((2 : Int), (3 : Int)) + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i22333.scala b/tests/neg/i22333.scala new file mode 100644 index 000000000000..8cab77967d0c --- /dev/null +++ b/tests/neg/i22333.scala @@ -0,0 +1,5 @@ + +class Container[Rs <: Tuple]: + def guard(a: Tuple.Tail[Rs]): Unit = () + +val _ = Container[(1,2,3)].guard((6, 8): Tuple.Tail[(5, 6, 8)]) // error From c71b2539f97562fe5614e11930a6780d96ee6a97 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Fri, 10 Jan 2025 16:32:36 +0000 Subject: [PATCH 176/202] Rollback constraints in compareAppliedTypeParamRef Co-authored-by: Guillaume Martres --- .../dotty/tools/dotc/core/TypeComparer.scala | 5 +++-- tests/pos/20519.scala | 10 ++++++++++ tests/pos/20519b.scala | 16 +++++++++++++++ tests/pos/20519c.scala | 20 +++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 tests/pos/20519.scala create mode 100644 tests/pos/20519b.scala create mode 100644 tests/pos/20519c.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 8414c3795f49..bbe157d4a29b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1244,8 +1244,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling tl => otherTycon.appliedTo(bodyArgs(tl))) else otherTycon - (assumedTrue(tycon) || directionalIsSubType(tycon, adaptedTycon)) && - directionalRecur(adaptedTycon.appliedTo(args), other) + rollbackConstraintsUnless: + (assumedTrue(tycon) || directionalIsSubType(tycon, adaptedTycon)) + && directionalRecur(adaptedTycon.appliedTo(args), other) } } end compareAppliedTypeParamRef diff --git a/tests/pos/20519.scala b/tests/pos/20519.scala new file mode 100644 index 000000000000..866e24e8fb6f --- /dev/null +++ b/tests/pos/20519.scala @@ -0,0 +1,10 @@ +class Box[T](val value: T) + +def boo[F[_], A](e: F[Box[A]]): F[A] = ??? + +type Result[G[_], B] = G[Box[B]] + +def main = + val b: Result[Option, Int] = ??? + val c = boo(b) + c: Option[Int] diff --git a/tests/pos/20519b.scala b/tests/pos/20519b.scala new file mode 100644 index 000000000000..4dadf2aa70b6 --- /dev/null +++ b/tests/pos/20519b.scala @@ -0,0 +1,16 @@ +trait TCl[F[_]] + +def boo[F[_], A](e: F[Option[A]], ev: TCl[F]): Unit = () + +type Result[F[_], A] = F[Option[A]] + +@main def main = + summon[Result[Option, Int] =:= Option[Option[Int]]] + + val ev = new TCl[Option] {} + + val b: Result[Option, Int] = None + boo(b, ev) + + val b2: Option[Option[Int]] = None + boo(b2, ev) diff --git a/tests/pos/20519c.scala b/tests/pos/20519c.scala new file mode 100644 index 000000000000..91eb33a7b892 --- /dev/null +++ b/tests/pos/20519c.scala @@ -0,0 +1,20 @@ +object Main { + trait TCl[F[_]] + + implicit class Stx[F[_], A](e: F[Option[A]]) { + def boo(implicit ev: TCl[F]): Unit = () + } + + type Result[F[_], A] = F[Option[A]] + + implicit val t: TCl[Option] = new TCl[Option] {} + + def main(args: Array[String]): Unit = { + val b: Result[Option, Int] = None + b.boo + + // works without the alias: + val b2: Option[Option[Int]] = None + b2.boo + } +} From cc78467e3e1dcc0fbb5804e9e829808020fb2e1e Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 16 Aug 2024 09:03:21 -0700 Subject: [PATCH 177/202] Undo patch of double-block apply If `f{}{}` is candidate for rewrite, unpatch it. We don't know exact spans, so just check endpoints to remove. --- .../src/dotty/tools/dotc/parsing/Parsers.scala | 8 +++++++- .../dotty/tools/dotc/rewrites/Rewrites.scala | 17 ++++++++++++++++- .../dotty/tools/dotc/CompilationTests.scala | 1 + tests/rewrites/i21382.scala | 8 ++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tests/rewrites/i21382.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 7933cbbea12f..38f554c10b0a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -27,7 +27,7 @@ import ScriptParsers.* import Decorators.* import util.Chars import scala.annotation.tailrec -import rewrites.Rewrites.{patch, overlapsPatch} +import rewrites.Rewrites.{overlapsPatch, patch, unpatch} import reporting.* import config.Feature import config.Feature.{sourceVersion, migrateTo3} @@ -2779,6 +2779,12 @@ object Parsers { simpleExprRest(tapp, location, canApply = true) case LPAREN | LBRACE | INDENT if canApply => val app = atSpan(startOffset(t), in.offset) { mkApply(t, argumentExprs()) } + if in.rewriteToIndent then + app match + case Apply(Apply(_, List(Block(_, _))), List(blk @ Block(_, _))) => + unpatch(blk.srcPos.sourcePos.source, Span(blk.span.start, blk.span.start + 1)) + unpatch(blk.srcPos.sourcePos.source, Span(blk.span.end, blk.span.end + 1)) + case _ => simpleExprRest(app, location, canApply = true) case USCORE => atSpan(startOffset(t), in.skipToken()) { PostfixOp(t, Ident(nme.WILDCARD)) } diff --git a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala index 305f61dfa177..ebc50059a39e 100644 --- a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala +++ b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala @@ -32,11 +32,16 @@ object Rewrites { case class ActionPatch(srcPos: SourcePosition, replacement: String) private class Patches(source: SourceFile) { - private[Rewrites] val pbuf = new mutable.ListBuffer[Patch]() + private[Rewrites] val pbuf = mutable.ListBuffer.empty[Patch] def addPatch(span: Span, replacement: String): Unit = pbuf += Patch(span, replacement) + // remove patches which match either end point + def removePatch(span: Span): Unit = + def p(other: Span): Boolean = span.start == other.start || span.end == other.end + pbuf.filterInPlace(x => !p(x.span)) + def apply(cs: Array[Char]): Array[Char] = { val delta = pbuf.map(_.delta).sum val patches = pbuf.toList.sortBy(_.span.start) @@ -87,6 +92,16 @@ object Rewrites { def patch(span: Span, replacement: String)(using Context): Unit = patch(ctx.compilationUnit.source, span, replacement) + /** Delete patches matching the given span, + * where a match has the same start or end offset. + */ + def unpatch(source: SourceFile, span: Span)(using Context): Unit = + if ctx.reporter != Reporter.NoReporter // NoReporter is used for syntax highlighting + then ctx.settings.rewrite.value.foreach: rewrites => + rewrites.patched + .get(source) + .foreach(_.removePatch(span)) + /** Does `span` overlap with a patch region of `source`? */ def overlapsPatch(source: SourceFile, span: Span)(using Context): Boolean = ctx.settings.rewrite.value.exists(rewrites => diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 2c42204a4b4d..70f55e569784 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -81,6 +81,7 @@ class CompilationTests { compileFile("tests/rewrites/i21418.scala", unindentOptions.and("-rewrite", "-source:3.5-migration")), compileFile("tests/rewrites/infix-named-args.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), compileFile("tests/rewrites/ambigious-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), + compileFile("tests/rewrites/i21382.scala", defaultOptions.and("-indent", "-rewrite")), ).checkRewrites() } diff --git a/tests/rewrites/i21382.scala b/tests/rewrites/i21382.scala new file mode 100644 index 000000000000..75f4007be218 --- /dev/null +++ b/tests/rewrites/i21382.scala @@ -0,0 +1,8 @@ +def check(element: Any)(expected: String): Unit = ??? + +def test = + check { + ??? + }{ + 42.toString + } From 937bc4ceb323185433da8b773ddb92c491a319a1 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Sun, 12 Jan 2025 22:43:35 +0100 Subject: [PATCH 178/202] fix: correctly print litteral types in the refined printer --- .../tools/dotc/printing/RefinedPrinter.scala | 1 + tests/printing/i22349.check | 15 +++++++++++++++ tests/printing/i22349.scala | 7 +++++++ 3 files changed, 23 insertions(+) create mode 100644 tests/printing/i22349.check create mode 100644 tests/printing/i22349.scala diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index b7f2eef8c8f9..d460cec75115 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -572,6 +572,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case tree: TypeTree => typeText(toText(tree.typeOpt)) ~ Str("(inf)").provided(tree.isInferred && printDebug) + case SingletonTypeTree(ref: Literal) => toTextLocal(ref) case SingletonTypeTree(ref) => toTextLocal(ref) ~ "." ~ keywordStr("type") case RefinedTypeTree(tpt, refines) => diff --git a/tests/printing/i22349.check b/tests/printing/i22349.check new file mode 100644 index 000000000000..7869f5f7fc4f --- /dev/null +++ b/tests/printing/i22349.check @@ -0,0 +1,15 @@ +[[syntax trees at end of typer]] // tests/printing/i22349.scala +package { + final lazy module val i22349$package: i22349$package = new i22349$package() + final module class i22349$package() extends Object() { + this: i22349$package.type => + val x: Int = 0 + val _$1: x.type = x + inline val _$2: true = true + inline val _$3: "abc" = "abc" + inline val _$4: 'c' = 'c' + inline val _$5: 1.2f = 1.2f + inline val _$6: 1.2d = 1.2d + } +} + diff --git a/tests/printing/i22349.scala b/tests/printing/i22349.scala new file mode 100644 index 000000000000..33122f0b91e8 --- /dev/null +++ b/tests/printing/i22349.scala @@ -0,0 +1,7 @@ +val x = 0 +val _: x.type = x +inline val _: true = true // boolean literal +inline val _: "abc" = "abc" // string literal +inline val _: 'c' = 'c' // character literal +inline val _: 1.2f = 1.2f // floating point literal +inline val _: 1.2d = 1.2d // double number literal From 2b6dac7d18219a0e4bc1b26b7d18301908e467dd Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Mon, 13 Jan 2025 00:23:20 +0100 Subject: [PATCH 179/202] fix: drop jackson-module-scala from CB --- .../test/scala/dotty/communitybuild/CommunityBuildTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala index bf78c8e1a2cf..5c2ea408413c 100644 --- a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala +++ b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala @@ -68,7 +68,7 @@ class CommunityBuildTestC: @Test def fastparse = projects.fastparse.run() @Test def geny = projects.geny.run() @Test def intent = projects.intent.run() - @Test def jacksonModuleScala = projects.jacksonModuleScala.run() + //@Test def jacksonModuleScala = projects.jacksonModuleScala.run() @Test def libretto = projects.libretto.run() @Test def minitest = projects.minitest.run() //@Test def onnxScala = projects.onnxScala.run() From f6d6b1720504e70731a05c26e781cba52eaf720e Mon Sep 17 00:00:00 2001 From: David Hua Date: Mon, 13 Jan 2025 02:19:41 -0500 Subject: [PATCH 180/202] Fix crash when initializing val in ByName closure --- .../tools/dotc/transform/init/Objects.scala | 19 +++++++++++++------ tests/init/pos/byname.scala | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 tests/init/pos/byname.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 52760cf8b6c7..316213a94f8d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -405,11 +405,18 @@ class Objects(using Context @constructorOnly): def getVar(x: Symbol)(using data: Data, ctx: Context): Option[Heap.Addr] = data.getVar(x) - def of(ddef: DefDef, args: List[Value], outer: Data)(using Context): Data = + private[Env] def _of(argMap: Map[Symbol, Value], meth: Symbol, outer: Data): Data = + new LocalEnv(argMap, meth, outer)(valsMap = mutable.Map.empty, varsMap = mutable.Map.empty) + + def ofDefDef(ddef: DefDef, args: List[Value], outer: Data)(using Context): Data = val params = ddef.termParamss.flatten.map(_.symbol) assert(args.size == params.size, "arguments = " + args.size + ", params = " + params.size) assert(ddef.symbol.owner.isClass ^ (outer != NoEnv), "ddef.owner = " + ddef.symbol.owner.show + ", outer = " + outer + ", " + ddef.source) - new LocalEnv(params.zip(args).toMap, ddef.symbol, outer)(valsMap = mutable.Map.empty, varsMap = mutable.Map.empty) + _of(params.zip(args).toMap, ddef.symbol, outer) + + def ofByName(byNameParam: Symbol, outer: Data): Data = + assert(byNameParam.is(Flags.Param) && byNameParam.info.isInstanceOf[ExprType]); + _of(Map.empty, byNameParam, outer) def setLocalVal(x: Symbol, value: Value)(using data: Data, ctx: Context): Unit = assert(!x.isOneOf(Flags.Param | Flags.Mutable), "Only local immutable variable allowed") @@ -719,7 +726,7 @@ class Objects(using Context @constructorOnly): else Env.resolveEnv(meth.owner.enclosingMethod, ref, summon[Env.Data]).getOrElse(Cold -> Env.NoEnv) - val env2 = Env.of(ddef, args.map(_.value), outerEnv) + val env2 = Env.ofDefDef(ddef, args.map(_.value), outerEnv) extendTrace(ddef) { given Env.Data = env2 cache.cachedEval(ref, ddef.rhs, cacheResult = true) { expr => @@ -750,7 +757,7 @@ class Objects(using Context @constructorOnly): code match case ddef: DefDef => if meth.name == nme.apply then - given Env.Data = Env.of(ddef, args.map(_.value), env) + given Env.Data = Env.ofDefDef(ddef, args.map(_.value), env) extendTrace(code) { eval(ddef.rhs, thisV, klass, cacheResult = true) } else // The methods defined in `Any` and `AnyRef` are trivial and don't affect initialization. @@ -786,7 +793,7 @@ class Objects(using Context @constructorOnly): val ddef = ctor.defTree.asInstanceOf[DefDef] val argValues = args.map(_.value) - given Env.Data = Env.of(ddef, argValues, Env.NoEnv) + given Env.Data = Env.ofDefDef(ddef, argValues, Env.NoEnv) if ctor.isPrimaryConstructor then val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] extendTrace(cls.defTree) { eval(tpl, ref, cls, cacheResult = true) } @@ -1013,7 +1020,7 @@ class Objects(using Context @constructorOnly): if isByNameParam(sym) then value match case fun: Fun => - given Env.Data = fun.env + given Env.Data = Env.ofByName(sym, fun.env) eval(fun.code, fun.thisV, fun.klass) case Cold => report.warning("Calling cold by-name alias. " + Trace.show, Trace.position) diff --git a/tests/init/pos/byname.scala b/tests/init/pos/byname.scala new file mode 100644 index 000000000000..fdfbd101cc93 --- /dev/null +++ b/tests/init/pos/byname.scala @@ -0,0 +1,17 @@ +trait T: + def bar(i: => Int): Int + +class A extends T: + override def bar(i: => Int): Int = i + 1 + +class B extends T: + override def bar(i: => Int): Int = i + 2 + +object A: + val a: T = if ??? then new A else new B + def foo(b: List[Int]) = a.bar(b match { + case x :: xs => 1 + case Nil => 0 + }) + + val f = foo(Nil) From 74aa123442c6f55921752d450299c7f84c40feac Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 10 Jan 2025 17:32:15 +0100 Subject: [PATCH 181/202] Drop `EmptyTuple` handling from `NamedTupleDecomposition.apply` We previously needed to handle the case where the result of `toTuple` was an `EmptyTuple` separately from the rest, since `apply` used to be only defined for `NonEmptyTuple`s. This was changed in #21291, making the inline match in `NamedTupleDecomposition.apply` no longer necessary. The underlying issue with the proxies introduced to handle opaque types with inline defs is likely still present. Nevertheless, it is probably best to fix this specific instance of the problem with NamedTuples as soon as possible. Fixes #22324 --- library/src/scala/NamedTuple.scala | 4 +--- tests/pos/i22324.scala | 10 ++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 tests/pos/i22324.scala diff --git a/library/src/scala/NamedTuple.scala b/library/src/scala/NamedTuple.scala index 6da7f940dc47..0d1deffce513 100644 --- a/library/src/scala/NamedTuple.scala +++ b/library/src/scala/NamedTuple.scala @@ -139,9 +139,7 @@ object NamedTupleDecomposition: extension [N <: Tuple, V <: Tuple](x: NamedTuple[N, V]) /** The value (without the name) at index `n` of this tuple */ inline def apply(n: Int): Elem[NamedTuple[N, V], n.type] = - inline x.toTuple match - case tup: NonEmptyTuple => tup(n).asInstanceOf[Elem[NamedTuple[N, V], n.type]] - case tup => tup.productElement(n).asInstanceOf[Elem[NamedTuple[N, V], n.type]] + x.toTuple.apply(n).asInstanceOf[Elem[NamedTuple[N, V], n.type]] /** The number of elements in this tuple */ inline def size: Size[NamedTuple[N, V]] = x.toTuple.size diff --git a/tests/pos/i22324.scala b/tests/pos/i22324.scala new file mode 100644 index 000000000000..b35f82d52ac9 --- /dev/null +++ b/tests/pos/i22324.scala @@ -0,0 +1,10 @@ +import scala.language.experimental.namedTuples + +opaque type System = (wires: Any) + +extension (system: System) + inline def foo = system.wires +end extension + +val simulation: System = ??? +val _ = simulation.foo // was error From f23f7c6fc4ef6f196922b4751308a16e0b5cc87b Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 13 Jan 2025 11:52:08 +0000 Subject: [PATCH 182/202] Detail selectionType usage in Inliner typedSelect --- compiler/src/dotty/tools/dotc/inlines/Inliner.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index a5beceba3e46..2242174f78f2 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -790,7 +790,13 @@ class Inliner(val call: tpd.Tree)(using Context): override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { val locked = ctx.typerState.ownedVars val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)) - selectionType(tree, qual1) // side-effect + + // Make sure that the named type has the correct denotation. + // For instance in tests/pos/i22070 when we type `Featureful[?]#toFeatures`, + // `selectionType` will skolemize the prefix, find the denotation, + // and then set that denotation for the `TermRef(Featureful[?], symbol toFeatures)`. + selectionType(tree, qual1) + val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) val reducedProjection = reducer.reduceProjection(resNoReduce) if reducedProjection.isType then From af655c961573287059810f9dd00af150db9e7cbd Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 13 Jan 2025 13:50:47 +0100 Subject: [PATCH 183/202] Append instead of prepending import selectors for the current scope when collecting them in CheckUnused (#22314) Also make the wildcard selectors exclusion-aware in CheckUnused closes #21420 --- .../tools/dotc/transform/CheckUnused.scala | 24 +++++++++++++++---- tests/warn/i21420.scala | 13 ++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 tests/warn/i21420.scala diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index d647d50560d3..6e626fc5dd9e 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -1,6 +1,7 @@ package dotty.tools.dotc.transform import scala.annotation.tailrec +import scala.collection.mutable import dotty.tools.uncheckedNN import dotty.tools.dotc.ast.tpd @@ -24,7 +25,7 @@ import dotty.tools.dotc.core.Mode import dotty.tools.dotc.core.Types.{AnnotatedType, ConstantType, NoType, TermRef, Type, TypeTraverser} import dotty.tools.dotc.core.Flags.flagsString import dotty.tools.dotc.core.Flags -import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.core.Names.{Name, TermName} import dotty.tools.dotc.core.NameOps.isReplWrapperName import dotty.tools.dotc.transform.MegaPhase.MiniPhase import dotty.tools.dotc.core.Annotations @@ -211,7 +212,7 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke /** * This traverse is the **main** component of this phase * - * It traverse the tree the tree and gather the data in the + * It traverses the tree and gathers the data in the * corresponding context property */ private def traverser = new TreeTraverser: @@ -456,14 +457,21 @@ object CheckUnused: val (wildcardSels, nonWildcardSels) = imp.selectors.partition(_.isWildcard) nonWildcardSels ::: wildcardSels + val excludedMembers: mutable.Set[TermName] = mutable.Set.empty + val newDataInScope = for sel <- reorderdSelectors yield val data = new ImportSelectorData(qualTpe, sel) if shouldSelectorBeReported(imp, sel) || isImportExclusion(sel) || isImportIgnored(imp, sel) then // Immediately mark the selector as used data.markUsed() + if isImportExclusion(sel) then + excludedMembers += sel.name + if sel.isWildcard && excludedMembers.nonEmpty then + // mark excluded members for the wildcard import + data.markExcluded(excludedMembers.toSet) data - impInScope.top.prependAll(newDataInScope) + impInScope.top.appendAll(newDataInScope) end registerImport /** Register (or not) some `val` or `def` according to the context, scope and flags */ @@ -703,7 +711,7 @@ object CheckUnused: /** Given an import and accessibility, return selector that matches import<->symbol */ private def isInImport(selData: ImportSelectorData, altName: Option[Name], isDerived: Boolean)(using Context): Boolean = - assert(sym.exists) + assert(sym.exists, s"Symbol $sym does not exist") val selector = selData.selector @@ -719,7 +727,10 @@ object CheckUnused: selData.allSymbolsForNamed.contains(sym) else // Wildcard - if !selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then + if selData.excludedMembers.contains(altName.getOrElse(sym.name).toTermName) then + // Wildcard with exclusions that match the symbol + false + else if !selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then // The qualifier does not have the target symbol as a member false else @@ -832,11 +843,14 @@ object CheckUnused: final class ImportSelectorData(val qualTpe: Type, val selector: ImportSelector): private var myUsed: Boolean = false + var excludedMembers: Set[TermName] = Set.empty def markUsed(): Unit = myUsed = true def isUsed: Boolean = myUsed + def markExcluded(excluded: Set[TermName]): Unit = excludedMembers ++= excluded + private var myAllSymbols: Set[Symbol] | Null = null def allSymbolsForNamed(using Context): Set[Symbol] = diff --git a/tests/warn/i21420.scala b/tests/warn/i21420.scala new file mode 100644 index 000000000000..0ee4aa3f28f6 --- /dev/null +++ b/tests/warn/i21420.scala @@ -0,0 +1,13 @@ +//> using options -Wunused:imports + +object decisions4s{ + trait HKD + trait DecisionTable +} + +object DiagnosticsExample { + import decisions4s.HKD + val _ = new HKD {} + import decisions4s.* + val _ = new DecisionTable {} +} From 3d11e5e462b86ffa37c1945896faad38edcec1de Mon Sep 17 00:00:00 2001 From: aherlihy Date: Wed, 8 Jan 2025 15:52:05 -0500 Subject: [PATCH 184/202] Handle TypeProxy of Named Tuples, minimal fix without refactoring --- .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../src/dotty/tools/dotc/core/TypeUtils.scala | 20 +++++++++++--- .../tools/dotc/interactive/Completion.scala | 2 +- .../tools/dotc/printing/RefinedPrinter.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 4 +-- tests/run/i22150.check | 3 +++ tests/run/i22150.scala | 26 +++++++++++++++++++ 7 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 tests/run/i22150.check create mode 100644 tests/run/i22150.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 67e1885b511f..dca2fbeb0dea 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1744,7 +1744,7 @@ object desugar { def adaptPatternArgs(elems: List[Tree], pt: Type)(using Context): List[Tree] = def reorderedNamedArgs(wildcardSpan: Span): List[untpd.Tree] = - var selNames = pt.namedTupleElementTypes.map(_(0)) + var selNames = pt.namedTupleElementTypes(false).map(_(0)) if selNames.isEmpty && pt.classSymbol.is(CaseClass) then selNames = pt.classSymbol.caseAccessors.map(_.name.asTermName) val nameToIdx = selNames.zipWithIndex.toMap diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index 0a219fa6ddfd..e272c96c9d39 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -127,7 +127,7 @@ class TypeUtils: case Some(types) => TypeOps.nestedPairs(types) case None => throw new AssertionError("not a tuple") - def namedTupleElementTypesUpTo(bound: Int, normalize: Boolean = true)(using Context): List[(TermName, Type)] = + def namedTupleElementTypesUpTo(bound: Int, derived: Boolean, normalize: Boolean = true)(using Context): List[(TermName, Type)] = (if normalize then self.normalized else self).dealias match case defn.NamedTuple(nmes, vals) => val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: @@ -135,11 +135,25 @@ class TypeUtils: case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) names.zip(values) + case tp: TypeProxy if derived => + tp.superType.namedTupleElementTypesUpTo(bound - 1, normalize) + case tp: OrType if derived => + val lhs = tp.tp1.namedTupleElementTypesUpTo(bound - 1, normalize) + val rhs = tp.tp2.namedTupleElementTypesUpTo(bound - 1, normalize) + if (lhs.map(_._1) != rhs.map(_._1)) throw TypeError(em"Malformed Union Type: Named Tuple elements must be the same, but $lhs and $rhs were found.") + lhs.zip(rhs).map((lhs, rhs) => (lhs._1, lhs._2 | rhs._2)) + case tp: AndType if derived => + (tp.tp1.namedTupleElementTypesUpTo(bound - 1, normalize), tp.tp2.namedTupleElementTypesUpTo(bound - 1, normalize)) match + case (Nil, rhs) => rhs + case (lhs, Nil) => lhs + case (lhs, rhs) => + if (lhs.map(_._1) != rhs.map(_._1)) throw TypeError(em"Malformed Intersection Type: Named Tuple elements must be the same, but $lhs and $rhs were found.") + lhs.zip(rhs).map((lhs, rhs) => (lhs._1, lhs._2 & rhs._2)) case t => Nil - def namedTupleElementTypes(using Context): List[(TermName, Type)] = - namedTupleElementTypesUpTo(Int.MaxValue) + def namedTupleElementTypes(derived: Boolean)(using Context): List[(TermName, Type)] = + namedTupleElementTypesUpTo(Int.MaxValue, derived) def isNamedTupleType(using Context): Boolean = self match case defn.NamedTuple(_, _) => true diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index ff5716b227ca..6655998d026f 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -532,7 +532,7 @@ object Completion: def namedTupleCompletionsFromType(tpe: Type): CompletionMap = val freshCtx = ctx.fresh.setExploreTyperState() inContext(freshCtx): - tpe.namedTupleElementTypes + tpe.namedTupleElementTypes(true) .map { (name, tpe) => val symbol = newSymbol(owner = NoSymbol, name, EmptyFlags, tpe) val denot = SymDenotation(symbol, NoSymbol, name, EmptyFlags, tpe) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index d460cec75115..6ced4f075abc 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -248,7 +248,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def appliedText(tp: Type): Text = tp match case tp @ AppliedType(tycon, args) => val namedElems = - try tp.namedTupleElementTypesUpTo(200, normalize = false) + try tp.namedTupleElementTypesUpTo(200, false, normalize = false) // TODO: should the printer use derived or not? catch case ex: TypeError => Nil if namedElems.nonEmpty then toTextNamedTuple(namedElems) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 76b853c4aabd..6072c496e1bd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -799,7 +799,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Otherwise, try to expand a named tuple selection def tryNamedTupleSelection() = - val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes + val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes(true) val nameIdx = namedTupleElems.indexWhere(_._1 == selName) if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then typed( @@ -875,7 +875,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer then val pre = if !TypeOps.isLegalPrefix(qual.tpe) then SkolemType(qual.tpe) else qual.tpe val fieldsType = pre.select(tpnme.Fields).widenDealias.simplified - val fields = fieldsType.namedTupleElementTypes + val fields = fieldsType.namedTupleElementTypes(true) typr.println(i"try dyn select $qual, $selName, $fields") fields.find(_._1 == selName) match case Some((_, fieldType)) => diff --git a/tests/run/i22150.check b/tests/run/i22150.check new file mode 100644 index 000000000000..4539bbf2d22d --- /dev/null +++ b/tests/run/i22150.check @@ -0,0 +1,3 @@ +0 +1 +2 diff --git a/tests/run/i22150.scala b/tests/run/i22150.scala new file mode 100644 index 000000000000..6a01e5da85ba --- /dev/null +++ b/tests/run/i22150.scala @@ -0,0 +1,26 @@ +//> using options -experimental -language:experimental.namedTuples +import language.experimental.namedTuples + +val directionsNT = IArray( + (dx = 0, dy = 1), // up + (dx = 1, dy = 0), // right + (dx = 0, dy = -1), // down + (dx = -1, dy = 0), // left +) +val IArray(UpNT @ _, _, _, _) = directionsNT + +object NT: +// def foo[T <: (x: Int, y: String)](tup: T): Int = +// tup.x + + def union[T](tup: (x: Int, y: String) | (x: Int, y: String)): Int = + tup.x + + def intersect[T](tup: (x: Int, y: String) & T): Int = + tup.x + + +@main def Test = + println(UpNT.dx) + println(NT.union((1, "a"))) + println(NT.intersect((2, "b"))) From 5b31eda1b2ef24d16dd195eb5c60642d59f6b0bf Mon Sep 17 00:00:00 2001 From: aherlihy Date: Fri, 10 Jan 2025 15:16:07 -0500 Subject: [PATCH 185/202] Refactor check into NamedTuple.unapply for consistency --- .../dotty/tools/dotc/core/Definitions.scala | 23 ++++++++++--- .../src/dotty/tools/dotc/core/TypeUtils.scala | 32 ++++++------------- .../tools/dotc/interactive/Completion.scala | 2 +- .../tools/dotc/printing/RefinedPrinter.scala | 5 +-- .../dotty/tools/dotc/typer/Implicits.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/run/i22150.scala | 2 +- 7 files changed, 35 insertions(+), 33 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2890bdf306be..dd20c2db9192 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1337,10 +1337,25 @@ class Definitions { object NamedTuple: def apply(nmes: Type, vals: Type)(using Context): Type = AppliedType(NamedTupleTypeRef, nmes :: vals :: Nil) - def unapply(t: Type)(using Context): Option[(Type, Type)] = t match - case AppliedType(tycon, nmes :: vals :: Nil) if tycon.typeSymbol == NamedTupleTypeRef.symbol => - Some((nmes, vals)) - case _ => None + def unapply(t: Type)(using Context): Option[(Type, Type)] = + t match + case AppliedType(tycon, nmes :: vals :: Nil) if tycon.typeSymbol == NamedTupleTypeRef.symbol => + Some((nmes, vals)) + case tp: TypeProxy => + val t = unapply(tp.superType); t + case tp: OrType => + (unapply(tp.tp1), unapply(tp.tp2)) match + case (Some(lhsName, lhsVal), Some(rhsName, rhsVal)) if lhsName == rhsName => + Some(lhsName, lhsVal | rhsVal) + case _ => None + case tp: AndType => + (unapply(tp.tp1), unapply(tp.tp2)) match + case (Some(lhsName, lhsVal), Some(rhsName, rhsVal)) if lhsName == rhsName => + Some(lhsName, lhsVal & rhsVal) + case (lhs, None) => lhs + case (None, rhs) => rhs + case _ => None + case _ => None final def isCompiletime_S(sym: Symbol)(using Context): Boolean = sym.name == tpnme.S && sym.owner == CompiletimeOpsIntModuleClass diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index e272c96c9d39..14ccf32c7787 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -129,26 +129,21 @@ class TypeUtils: def namedTupleElementTypesUpTo(bound: Int, derived: Boolean, normalize: Boolean = true)(using Context): List[(TermName, Type)] = (if normalize then self.normalized else self).dealias match + // for desugaring and printer, ignore derived types to avoid infinite recursion in NamedTuple.unapply + case AppliedType(tycon, nmes :: vals :: Nil) if !derived && tycon.typeSymbol == defn.NamedTupleTypeRef.symbol => + val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: + case ConstantType(Constant(str: String)) => str.toTermName + case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") + val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) + names.zip(values) + case t if !derived => Nil + // default cause, used for post-typing case defn.NamedTuple(nmes, vals) => val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: case ConstantType(Constant(str: String)) => str.toTermName case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) names.zip(values) - case tp: TypeProxy if derived => - tp.superType.namedTupleElementTypesUpTo(bound - 1, normalize) - case tp: OrType if derived => - val lhs = tp.tp1.namedTupleElementTypesUpTo(bound - 1, normalize) - val rhs = tp.tp2.namedTupleElementTypesUpTo(bound - 1, normalize) - if (lhs.map(_._1) != rhs.map(_._1)) throw TypeError(em"Malformed Union Type: Named Tuple elements must be the same, but $lhs and $rhs were found.") - lhs.zip(rhs).map((lhs, rhs) => (lhs._1, lhs._2 | rhs._2)) - case tp: AndType if derived => - (tp.tp1.namedTupleElementTypesUpTo(bound - 1, normalize), tp.tp2.namedTupleElementTypesUpTo(bound - 1, normalize)) match - case (Nil, rhs) => rhs - case (lhs, Nil) => lhs - case (lhs, rhs) => - if (lhs.map(_._1) != rhs.map(_._1)) throw TypeError(em"Malformed Intersection Type: Named Tuple elements must be the same, but $lhs and $rhs were found.") - lhs.zip(rhs).map((lhs, rhs) => (lhs._1, lhs._2 & rhs._2)) case t => Nil @@ -159,15 +154,6 @@ class TypeUtils: case defn.NamedTuple(_, _) => true case _ => false - def derivesFromNamedTuple(using Context): Boolean = self match - case defn.NamedTuple(_, _) => true - case tp: MatchType => - tp.bound.derivesFromNamedTuple || tp.reduced.derivesFromNamedTuple - case tp: TypeProxy => tp.superType.derivesFromNamedTuple - case tp: AndType => tp.tp1.derivesFromNamedTuple || tp.tp2.derivesFromNamedTuple - case tp: OrType => tp.tp1.derivesFromNamedTuple && tp.tp2.derivesFromNamedTuple - case _ => false - /** Drop all named elements in tuple type */ def stripNamedTuple(using Context): Type = self.normalized.dealias match case defn.NamedTuple(_, vals) => diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 6655998d026f..333af6a26b3b 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -543,7 +543,7 @@ object Completion: .groupByName val qualTpe = qual.typeOpt - if qualTpe.derivesFromNamedTuple then + if qualTpe.isNamedTupleType then namedTupleCompletionsFromType(qualTpe) else if qualTpe.derivesFrom(defn.SelectableClass) then val pre = if !TypeOps.isLegalPrefix(qualTpe) then Types.SkolemType(qualTpe) else qualTpe diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 6ced4f075abc..27ab73f0fe4d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -248,8 +248,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def appliedText(tp: Type): Text = tp match case tp @ AppliedType(tycon, args) => val namedElems = - try tp.namedTupleElementTypesUpTo(200, false, normalize = false) // TODO: should the printer use derived or not? - catch case ex: TypeError => Nil + try tp.namedTupleElementTypesUpTo(200, false, normalize = false) + catch + case ex: TypeError => Nil if namedElems.nonEmpty then toTextNamedTuple(namedElems) else tp.tupleElementTypesUpTo(200, normalize = false) match diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 193cc443b4ae..9d273ebca866 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -876,7 +876,7 @@ trait Implicits: || inferView(dummyTreeOfType(from), to) (using ctx.fresh.addMode(Mode.ImplicitExploration).setExploreTyperState()).isSuccess // TODO: investigate why we can't TyperState#test here - || from.widen.derivesFromNamedTuple && to.derivesFrom(defn.TupleClass) + || from.widen.isNamedTupleType && to.derivesFrom(defn.TupleClass) && from.widen.stripNamedTuple <:< to ) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6072c496e1bd..9b7e4fe36668 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4663,7 +4663,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _: SelectionProto => tree // adaptations for selections are handled in typedSelect case _ if ctx.mode.is(Mode.ImplicitsEnabled) && tree.tpe.isValueType => - if tree.tpe.derivesFromNamedTuple && pt.derivesFrom(defn.TupleClass) then + if tree.tpe.isNamedTupleType && pt.derivesFrom(defn.TupleClass) then readapt(typed(untpd.Select(untpd.TypedSplice(tree), nme.toTuple))) else if pt.isRef(defn.AnyValClass, skipRefined = false) || pt.isRef(defn.ObjectClass, skipRefined = false) diff --git a/tests/run/i22150.scala b/tests/run/i22150.scala index 6a01e5da85ba..7c89b1de57c5 100644 --- a/tests/run/i22150.scala +++ b/tests/run/i22150.scala @@ -10,7 +10,7 @@ val directionsNT = IArray( val IArray(UpNT @ _, _, _, _) = directionsNT object NT: -// def foo[T <: (x: Int, y: String)](tup: T): Int = +// def foo[T <: (x: Int, y: String)](tup: T): Int = // TODO 3: this fails with similar error to https://github.com/scala/scala3/issues/22324 not sure if related? // tup.x def union[T](tup: (x: Int, y: String) | (x: Int, y: String)): Int = From be88246a292c4c54e55bfe4e44410253b6eb4e24 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Fri, 10 Jan 2025 16:55:08 -0500 Subject: [PATCH 186/202] remove normalize and rely on stackoverflow --- .../src/dotty/tools/dotc/core/TypeUtils.scala | 17 ++++++++--------- .../tools/dotc/printing/RefinedPrinter.scala | 3 ++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index 14ccf32c7787..bcd8ac42d9cc 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -127,22 +127,21 @@ class TypeUtils: case Some(types) => TypeOps.nestedPairs(types) case None => throw new AssertionError("not a tuple") - def namedTupleElementTypesUpTo(bound: Int, derived: Boolean, normalize: Boolean = true)(using Context): List[(TermName, Type)] = - (if normalize then self.normalized else self).dealias match - // for desugaring and printer, ignore derived types to avoid infinite recursion in NamedTuple.unapply + def namedTupleElementTypesUpTo(bound: Int, derived: Boolean)(using Context): List[(TermName, Type)] = + self.normalized.dealias match + // for desugaring, ignore derived types to avoid infinite recursion in NamedTuple.unapply case AppliedType(tycon, nmes :: vals :: Nil) if !derived && tycon.typeSymbol == defn.NamedTupleTypeRef.symbol => - val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: + val names = nmes.tupleElementTypesUpTo(bound).getOrElse(Nil).map(_.dealias).map: case ConstantType(Constant(str: String)) => str.toTermName case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") - val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) + val values = vals.tupleElementTypesUpTo(bound, true).getOrElse(Nil) names.zip(values) - case t if !derived => Nil // default cause, used for post-typing - case defn.NamedTuple(nmes, vals) => - val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: + case defn.NamedTuple(nmes, vals) if derived => + val names = nmes.tupleElementTypesUpTo(bound).getOrElse(Nil).map(_.dealias).map: case ConstantType(Constant(str: String)) => str.toTermName case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") - val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) + val values = vals.tupleElementTypesUpTo(bound, derived).getOrElse(Nil) names.zip(values) case t => Nil diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 27ab73f0fe4d..3d6077da24bc 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -248,9 +248,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def appliedText(tp: Type): Text = tp match case tp @ AppliedType(tycon, args) => val namedElems = - try tp.namedTupleElementTypesUpTo(200, false, normalize = false) + try tp.namedTupleElementTypesUpTo(200, true) catch case ex: TypeError => Nil + case ex: StackOverflowError => Nil if namedElems.nonEmpty then toTextNamedTuple(namedElems) else tp.tupleElementTypesUpTo(200, normalize = false) match From f54f5d32b39c59d546c21937e99141ec12c19647 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Fri, 10 Jan 2025 16:56:48 -0500 Subject: [PATCH 187/202] Revert "remove normalize and rely on stackoverflow" because test slowdown This reverts commit 2f03f86ba82b61e199c45eb4d4b7d04b7aa5f237. --- .../src/dotty/tools/dotc/core/TypeUtils.scala | 17 +++++++++-------- .../tools/dotc/printing/RefinedPrinter.scala | 3 +-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index bcd8ac42d9cc..14ccf32c7787 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -127,21 +127,22 @@ class TypeUtils: case Some(types) => TypeOps.nestedPairs(types) case None => throw new AssertionError("not a tuple") - def namedTupleElementTypesUpTo(bound: Int, derived: Boolean)(using Context): List[(TermName, Type)] = - self.normalized.dealias match - // for desugaring, ignore derived types to avoid infinite recursion in NamedTuple.unapply + def namedTupleElementTypesUpTo(bound: Int, derived: Boolean, normalize: Boolean = true)(using Context): List[(TermName, Type)] = + (if normalize then self.normalized else self).dealias match + // for desugaring and printer, ignore derived types to avoid infinite recursion in NamedTuple.unapply case AppliedType(tycon, nmes :: vals :: Nil) if !derived && tycon.typeSymbol == defn.NamedTupleTypeRef.symbol => - val names = nmes.tupleElementTypesUpTo(bound).getOrElse(Nil).map(_.dealias).map: + val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: case ConstantType(Constant(str: String)) => str.toTermName case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") - val values = vals.tupleElementTypesUpTo(bound, true).getOrElse(Nil) + val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) names.zip(values) + case t if !derived => Nil // default cause, used for post-typing - case defn.NamedTuple(nmes, vals) if derived => - val names = nmes.tupleElementTypesUpTo(bound).getOrElse(Nil).map(_.dealias).map: + case defn.NamedTuple(nmes, vals) => + val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: case ConstantType(Constant(str: String)) => str.toTermName case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") - val values = vals.tupleElementTypesUpTo(bound, derived).getOrElse(Nil) + val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) names.zip(values) case t => Nil diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 3d6077da24bc..27ab73f0fe4d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -248,10 +248,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def appliedText(tp: Type): Text = tp match case tp @ AppliedType(tycon, args) => val namedElems = - try tp.namedTupleElementTypesUpTo(200, true) + try tp.namedTupleElementTypesUpTo(200, false, normalize = false) catch case ex: TypeError => Nil - case ex: StackOverflowError => Nil if namedElems.nonEmpty then toTextNamedTuple(namedElems) else tp.tupleElementTypesUpTo(200, normalize = false) match From d42347f523c496a24a49e9dee05012e2320f6106 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Fri, 10 Jan 2025 17:10:38 -0500 Subject: [PATCH 188/202] Re-add test that will pass when #22340 is merged --- tests/run/i22150.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run/i22150.scala b/tests/run/i22150.scala index 7c89b1de57c5..80c2222a98e7 100644 --- a/tests/run/i22150.scala +++ b/tests/run/i22150.scala @@ -10,8 +10,8 @@ val directionsNT = IArray( val IArray(UpNT @ _, _, _, _) = directionsNT object NT: -// def foo[T <: (x: Int, y: String)](tup: T): Int = // TODO 3: this fails with similar error to https://github.com/scala/scala3/issues/22324 not sure if related? -// tup.x + def foo[T <: (x: Int, y: String)](tup: T): Int = + tup.x def union[T](tup: (x: Int, y: String) | (x: Int, y: String)): Int = tup.x From 83ae00dded56624fb840942825fc763bf8c5a008 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Mon, 13 Jan 2025 09:00:12 -0500 Subject: [PATCH 189/202] Update rebased --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index c2864093ff70..164df6aae5b8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -110,7 +110,7 @@ object Applications { } def namedTupleOrProductTypes(tp: Type)(using Context): List[Type] = - if tp.isNamedTupleType then tp.namedTupleElementTypes.map(_(1)) + if tp.isNamedTupleType then tp.namedTupleElementTypes(true).map(_(1)) else productSelectorTypes(tp, NoSourcePosition) def productSelectorTypes(tp: Type, errorPos: SrcPos)(using Context): List[Type] = { From f6b49dfa13ef2c73949509bd4226fcd12bd55ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Mon, 13 Jan 2025 15:55:58 +0100 Subject: [PATCH 190/202] Revert ListBuffer construction in Rewrites.scala --- compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala index ebc50059a39e..3c7216625a7c 100644 --- a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala +++ b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala @@ -32,7 +32,7 @@ object Rewrites { case class ActionPatch(srcPos: SourcePosition, replacement: String) private class Patches(source: SourceFile) { - private[Rewrites] val pbuf = mutable.ListBuffer.empty[Patch] + private[Rewrites] val pbuf = new mutable.ListBuffer[Patch]() def addPatch(span: Span, replacement: String): Unit = pbuf += Patch(span, replacement) From d5b5defe8b41bbf249f853dd90dccfac35b36592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 14 Jan 2025 11:57:26 +0100 Subject: [PATCH 191/202] Fix #21841: Check more that an `unapplySeq` on a `NonEmptyTuple` is valid. --- .../dotty/tools/dotc/typer/Applications.scala | 36 ++++++++++++++----- tests/neg/i21841.check | 6 ++++ tests/neg/i21841.scala | 22 ++++++++++++ 3 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 tests/neg/i21841.check create mode 100644 tests/neg/i21841.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index c2864093ff70..7b3134fb2003 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -68,6 +68,21 @@ object Applications { unapplySeqTypeElemTp(productSelectorTypes(tp, errorPos).last).exists } + /** Does `tp` fit the "product-seq match" conditions for a `NonEmptyTuple` as + * an unapply result type for a pattern with `numArgs` subpatterns? + * This is the case if (1) `tp` derives from `NonEmptyTuple`. + * (2) `tp.tupleElementTypes` exists. + * (3) `tp.tupleElementTypes.last` conforms to Seq match + */ + def isNonEmptyTupleSeqMatch(tp: Type, numArgs: Int, errorPos: SrcPos = NoSourcePosition)(using Context): Boolean = { + tp.derivesFrom(defn.NonEmptyTupleClass) + && tp.tupleElementTypes.exists { elemTypes => + val arity = elemTypes.size + arity > 0 && arity <= numArgs + 1 && + unapplySeqTypeElemTp(elemTypes.last).exists + } + } + /** Does `tp` fit the "get match" conditions as an unapply result type? * This is the case of `tp` has a `get` member as well as a * parameterless `isEmpty` member of result type `Boolean`. @@ -140,12 +155,17 @@ object Applications { sels.takeWhile(_.exists).toList } - def productSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] = { - val selTps = productSelectorTypes(tp, pos) - val arity = selTps.length - val elemTp = unapplySeqTypeElemTp(selTps.last) - (0 until argsNum).map(i => if (i < arity - 1) selTps(i) else elemTp).toList - } + def productSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] = + seqSelectors(productSelectorTypes(tp, pos), argsNum) + + def nonEmptyTupleSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] = + seqSelectors(tp.tupleElementTypes.get, argsNum) + + private def seqSelectors(selectorTypes: List[Type], argsNum: Int)(using Context): List[Type] = + val arity = selectorTypes.length + val elemTp = unapplySeqTypeElemTp(selectorTypes.last) + (0 until argsNum).map(i => if (i < arity - 1) selectorTypes(i) else elemTp).toList + end seqSelectors /** A utility class that matches results of unapplys with patterns. Two queriable members: * val argTypes: List[Type] @@ -176,8 +196,8 @@ object Applications { args.map(Function.const(elemTp)) else if isProductSeqMatch(tp, args.length, pos) then productSeqSelectors(tp, args.length, pos) - else if tp.derivesFrom(defn.NonEmptyTupleClass) then - tp.tupleElementTypes.getOrElse(Nil) + else if isNonEmptyTupleSeqMatch(tp, args.length, pos) then + nonEmptyTupleSeqSelectors(tp, args.length, pos) else fallback private def tryAdaptPatternArgs(elems: List[untpd.Tree], pt: Type)(using Context): Option[List[untpd.Tree]] = diff --git a/tests/neg/i21841.check b/tests/neg/i21841.check new file mode 100644 index 000000000000..e119d650b360 --- /dev/null +++ b/tests/neg/i21841.check @@ -0,0 +1,6 @@ +-- [E108] Declaration Error: tests/neg/i21841.scala:20:13 -------------------------------------------------------------- +20 | case v[T](l, r) => () // error + | ^^^^^^^^^^ + | Option[(Test.Expr[Test.T], Test.Expr[Test.T])] is not a valid result type of an unapplySeq method of an extractor. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i21841.scala b/tests/neg/i21841.scala new file mode 100644 index 000000000000..8a280abc2cf8 --- /dev/null +++ b/tests/neg/i21841.scala @@ -0,0 +1,22 @@ +object Test { + + sealed trait T + sealed trait Arrow[A, B] + + type ArgsTo[S1, Target] <: NonEmptyTuple = S1 match { + case Arrow[a, Target] => Tuple1[Expr[a]] + case Arrow[a, b] => Expr[a] *: ArgsTo[b, Target] + } + + sealed trait Expr[S] : + def unapplySeq[Target](e: Expr[Target]): Option[ArgsTo[S, Target]] = ??? + + case class Variable[S](id: String) extends Expr[S] + + val v = Variable[Arrow[T, Arrow[T, T]]]("v") + val e : Expr[T] = ??? + + e match + case v[T](l, r) => () // error + case _ => () +} From 1bcc03c302b406e5081af47ec8ea569b7fdabc82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Rochala?= <48657087+rochala@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:17:06 +0100 Subject: [PATCH 192/202] Validate named patterns for case classes (#22242) I found out that there is no validation happening for named patterns of case classes. https://scastie.scala-lang.org/W4p7RBrySwuteISEPuqSUw There were 2 different things that blocked the errors: 1. We actually did not run `checkWellFormedTupleElems` in that scenario, 2. We run `tryAdaptPatternArgs` in `tryEither` which has nested context that does not report errors which are not sticky. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 20 ++++++++++--------- tests/neg/named-tuples-4.check | 16 +++++++++++++++ tests/neg/named-tuples-4.scala | 16 +++++++++++++++ 4 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 tests/neg/named-tuples-4.check create mode 100644 tests/neg/named-tuples-4.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index dca2fbeb0dea..4ddd0006dc26 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1667,7 +1667,7 @@ object desugar { AppliedTypeTree( TypeTree(defn.throwsAlias.typeRef).withSpan(op.span), tpt :: excepts :: Nil) - private def checkWellFormedTupleElems(elems: List[Tree])(using Context): List[Tree] = + def checkWellFormedTupleElems(elems: List[Tree])(using Context): List[Tree] = val seen = mutable.Set[Name]() for case arg @ NamedArg(name, _) <- elems do if seen.contains(name) then diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 164df6aae5b8..92e0030ae8aa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -208,20 +208,22 @@ object Applications { else tp :: Nil private def productUnapplySelectors(tp: Type)(using Context): Option[List[Type]] = + val validatedTupleElements = desugar.checkWellFormedTupleElems(args) + if defn.isProductSubType(tp) && args.lengthCompare(productArity(tp)) <= 0 then - tryAdaptPatternArgs(args, tp) match + tryAdaptPatternArgs(validatedTupleElements, tp) match case Some(args1) if isProductMatch(tp, args1.length, pos) => args = args1 Some(productSelectorTypes(tp, pos)) case _ => None - else tp.widen.normalized.dealias match - case tp @ defn.NamedTuple(_, tt) => - tryAdaptPatternArgs(args, tp) match - case Some(args1) => - args = args1 - tt.tupleElementTypes - case _ => None - case _ => None + else tp.widen.normalized.dealias match + case tp @ defn.NamedTuple(_, tt) => + tryAdaptPatternArgs(validatedTupleElements, tp) match + case Some(args1) => + args = args1 + tt.tupleElementTypes + case _ => None + case _ => None /** The computed argument types which will be the scutinees of the sub-patterns. */ val argTypes: List[Type] = diff --git a/tests/neg/named-tuples-4.check b/tests/neg/named-tuples-4.check new file mode 100644 index 000000000000..2ebf9981b93c --- /dev/null +++ b/tests/neg/named-tuples-4.check @@ -0,0 +1,16 @@ +-- Error: tests/neg/named-tuples-4.scala:10:35 ------------------------------------------------------------------------- +10 | case PersonCaseClass(name = n, age) => () // error + | ^^^ + | Illegal combination of named and unnamed tuple elements +-- Error: tests/neg/named-tuples-4.scala:11:31 ------------------------------------------------------------------------- +11 | case PersonCaseClass(name, age = a) => () // error + | ^^^^^^^ + | Illegal combination of named and unnamed tuple elements +-- Error: tests/neg/named-tuples-4.scala:15:20 ------------------------------------------------------------------------- +15 | case (name = n, age) => () // error + | ^^^ + | Illegal combination of named and unnamed tuple elements +-- Error: tests/neg/named-tuples-4.scala:16:16 ------------------------------------------------------------------------- +16 | case (name, age = a) => () // error + | ^^^^^^^ + | Illegal combination of named and unnamed tuple elements diff --git a/tests/neg/named-tuples-4.scala b/tests/neg/named-tuples-4.scala new file mode 100644 index 000000000000..60621c57baf5 --- /dev/null +++ b/tests/neg/named-tuples-4.scala @@ -0,0 +1,16 @@ +import language.experimental.namedTuples +import scala.annotation.experimental + +@experimental object Test: + + case class PersonCaseClass(name: String, age: Int) + + val personCaseClass = PersonCaseClass("Bob", 33) + personCaseClass match + case PersonCaseClass(name = n, age) => () // error + case PersonCaseClass(name, age = a) => () // error + + val person = (name = "Bob", age = 33): (name: String, age: Int) + person match + case (name = n, age) => () // error + case (name, age = a) => () // error From 47733321f2d537bb8f6cf8b660715dfb47b18cee Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 15 Jan 2025 15:18:25 +0100 Subject: [PATCH 193/202] [CI]: Replace deprecated `actions/create-release` and `actions/upload-release-assert` actions (#22176) Both of these actions were archived in 2021. In the future they're going to fail, due to deprecation of `set-output` command in GitHub actions. We replace their usage with a single `gh release create` command. It also allows us to simplify the workflow [skip ci] --- .github/workflows/ci.yaml | 325 +++----------------------------------- 1 file changed, 23 insertions(+), 302 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index cc1eb5d40d97..b6bd9c319b1c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -736,7 +736,7 @@ jobs: publish_release: permissions: - contents: write # for actions/create-release to create a release + contents: write # for GH CLI to create a release runs-on: [self-hosted, Linux] container: image: lampepfl/dotty:2024-10-18 @@ -778,6 +778,7 @@ jobs: - name: Add SBT proxy repositories run: cp -vf .github/workflows/repositories /root/.sbt/ ; true + # Extract the release tag - name: Extract the release tag run : echo "RELEASE_TAG=${GITHUB_REF#*refs/tags/}" >> $GITHUB_ENV @@ -830,311 +831,31 @@ jobs: mv scala.msi "${msiInstaller}" sha256sum "${msiInstaller}" > "${msiInstaller}.sha256" + - name: Install GH CLI + uses: dev-hanz-ops/install-gh-cli-action@v0.2.0 + with: + gh-cli-version: 2.59.0 + # Create the GitHub release - name: Create GitHub Release - id: create_gh_release - uses: actions/create-release@latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token - with: - tag_name: ${{ github.ref }} - release_name: ${{ github.ref }} - body_path: ./changelogs/${{ env.RELEASE_TAG }}.md - draft: true - prerelease: ${{ contains(env.RELEASE_TAG, '-') }} - - # The following upload steps are generated using template: - # val baseFileName = "scala3-${{ env.RELEASE_TAG }}" - # def upload(kind: String, path: String, contentType: String, distribution: String) = - # s"""- name: Upload $kind to GitHub Release ($distribution) - # uses: actions/upload-release-asset@v1 - # env: - # GITHUB_TOKEN: $${{ secrets.GITHUB_TOKEN }} - # with: - # upload_url: $${{ steps.create_gh_release.outputs.upload_url }} - # asset_path: ./${path} - # asset_name: ${path} - # asset_content_type: ${contentType}""" - # def uploadSDK(distribution: String, suffix: String) = - # val filename = s"${baseFileName}${suffix}" - # s""" - # # $distribution - # ${upload("zip archive", s"$filename.zip", "application/zip", distribution)} - # ${upload("zip archive SHA", s"$filename.zip.sha256", "text/plain", distribution)} - # ${upload("tar.gz archive", s"$filename.tar.gz", "application/gzip", distribution)} - # ${upload("tar.gz archive SHA", s"$filename.tar.gz.sha256", "text/plain", distribution)} - # """ - # def uploadMSI() = - # val distribution = "Windows x86_64 MSI" - # s""" - # # $distribution - # ${upload(".msi file", s"${baseFileName}.msi", "application/x-msi", distribution)} - # ${upload(".msi file SHA", s"${baseFileName}.msi.sha256", "text/plain", distribution)} - # """ - # @main def gen = - # Seq( - # uploadSDK("Universal", ""), - # uploadSDK("Linux x86-64", "-x86_64-pc-linux"), - # uploadSDK("Linux aarch64", "-aarch64-pc-linux"), - # uploadSDK("Mac x86-64", "-x86_64-apple-darwin"), - # uploadSDK("Mac aarch64", "-aarch64-apple-darwin"), - # uploadSDK("Windows x86_64", "-x86_64-pc-win32"), - # uploadMSI() - # ).foreach(println) - - # Universal - - name: Upload zip archive to GitHub Release (Universal) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}.zip - asset_name: scala3-${{ env.RELEASE_TAG }}.zip - asset_content_type: application/zip - - name: Upload zip archive SHA to GitHub Release (Universal) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}.zip.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}.zip.sha256 - asset_content_type: text/plain - - name: Upload tar.gz archive to GitHub Release (Universal) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}.tar.gz - asset_name: scala3-${{ env.RELEASE_TAG }}.tar.gz - asset_content_type: application/gzip - - name: Upload tar.gz archive SHA to GitHub Release (Universal) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}.tar.gz.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}.tar.gz.sha256 - asset_content_type: text/plain - - - # Linux x86-64 - - name: Upload zip archive to GitHub Release (Linux x86-64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-x86_64-pc-linux.zip - asset_name: scala3-${{ env.RELEASE_TAG }}-x86_64-pc-linux.zip - asset_content_type: application/zip - - name: Upload zip archive SHA to GitHub Release (Linux x86-64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-x86_64-pc-linux.zip.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}-x86_64-pc-linux.zip.sha256 - asset_content_type: text/plain - - name: Upload tar.gz archive to GitHub Release (Linux x86-64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-x86_64-pc-linux.tar.gz - asset_name: scala3-${{ env.RELEASE_TAG }}-x86_64-pc-linux.tar.gz - asset_content_type: application/gzip - - name: Upload tar.gz archive SHA to GitHub Release (Linux x86-64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-x86_64-pc-linux.tar.gz.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}-x86_64-pc-linux.tar.gz.sha256 - asset_content_type: text/plain - - - # Linux aarch64 - - name: Upload zip archive to GitHub Release (Linux aarch64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-aarch64-pc-linux.zip - asset_name: scala3-${{ env.RELEASE_TAG }}-aarch64-pc-linux.zip - asset_content_type: application/zip - - name: Upload zip archive SHA to GitHub Release (Linux aarch64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-aarch64-pc-linux.zip.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}-aarch64-pc-linux.zip.sha256 - asset_content_type: text/plain - - name: Upload tar.gz archive to GitHub Release (Linux aarch64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-aarch64-pc-linux.tar.gz - asset_name: scala3-${{ env.RELEASE_TAG }}-aarch64-pc-linux.tar.gz - asset_content_type: application/gzip - - name: Upload tar.gz archive SHA to GitHub Release (Linux aarch64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-aarch64-pc-linux.tar.gz.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}-aarch64-pc-linux.tar.gz.sha256 - asset_content_type: text/plain - - - # Mac x86-64 - - name: Upload zip archive to GitHub Release (Mac x86-64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-x86_64-apple-darwin.zip - asset_name: scala3-${{ env.RELEASE_TAG }}-x86_64-apple-darwin.zip - asset_content_type: application/zip - - name: Upload zip archive SHA to GitHub Release (Mac x86-64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-x86_64-apple-darwin.zip.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}-x86_64-apple-darwin.zip.sha256 - asset_content_type: text/plain - - name: Upload tar.gz archive to GitHub Release (Mac x86-64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-x86_64-apple-darwin.tar.gz - asset_name: scala3-${{ env.RELEASE_TAG }}-x86_64-apple-darwin.tar.gz - asset_content_type: application/gzip - - name: Upload tar.gz archive SHA to GitHub Release (Mac x86-64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-x86_64-apple-darwin.tar.gz.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}-x86_64-apple-darwin.tar.gz.sha256 - asset_content_type: text/plain - - - # Mac aarch64 - - name: Upload zip archive to GitHub Release (Mac aarch64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-aarch64-apple-darwin.zip - asset_name: scala3-${{ env.RELEASE_TAG }}-aarch64-apple-darwin.zip - asset_content_type: application/zip - - name: Upload zip archive SHA to GitHub Release (Mac aarch64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-aarch64-apple-darwin.zip.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}-aarch64-apple-darwin.zip.sha256 - asset_content_type: text/plain - - name: Upload tar.gz archive to GitHub Release (Mac aarch64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-aarch64-apple-darwin.tar.gz - asset_name: scala3-${{ env.RELEASE_TAG }}-aarch64-apple-darwin.tar.gz - asset_content_type: application/gzip - - name: Upload tar.gz archive SHA to GitHub Release (Mac aarch64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-aarch64-apple-darwin.tar.gz.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}-aarch64-apple-darwin.tar.gz.sha256 - asset_content_type: text/plain - - - # Windows x86_64 - - name: Upload zip archive to GitHub Release (Windows x86_64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-x86_64-pc-win32.zip - asset_name: scala3-${{ env.RELEASE_TAG }}-x86_64-pc-win32.zip - asset_content_type: application/zip - - name: Upload zip archive SHA to GitHub Release (Windows x86_64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-x86_64-pc-win32.zip.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}-x86_64-pc-win32.zip.sha256 - asset_content_type: text/plain - - name: Upload tar.gz archive to GitHub Release (Windows x86_64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-x86_64-pc-win32.tar.gz - asset_name: scala3-${{ env.RELEASE_TAG }}-x86_64-pc-win32.tar.gz - asset_content_type: application/gzip - - name: Upload tar.gz archive SHA to GitHub Release (Windows x86_64) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}-x86_64-pc-win32.tar.gz.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}-x86_64-pc-win32.tar.gz.sha256 - asset_content_type: text/plain - - - # Windows x86_64 MSI - - name: Upload .msi file to GitHub Release (Windows x86_64 MSI) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}.msi - asset_name: scala3-${{ env.RELEASE_TAG }}.msi - asset_content_type: application/x-msi - - name: Upload .msi file SHA to GitHub Release (Windows x86_64 MSI) - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_gh_release.outputs.upload_url }} - asset_path: ./scala3-${{ env.RELEASE_TAG }}.msi.sha256 - asset_name: scala3-${{ env.RELEASE_TAG }}.msi.sha256 - asset_content_type: text/plain + shell: bash + run: | + # We need to config safe.directory in every step that might reference git + # It is not persisted between steps + git config --global --add safe.directory /__w/scala3/scala3 + gh release create \ + --draft \ + --title "${{ env.RELEASE_TAG }}" \ + --notes-file ./changelogs/${{ env.RELEASE_TAG }}.md \ + --latest=${{ !contains(env.RELEASE_TAG, '-RC') }} \ + --prerelease=${{ contains(env.RELEASE_TAG, '-RC') }} \ + --verify-tag ${{ env.RELEASE_TAG }} \ + scala3-${{ env.RELEASE_TAG }}*.zip \ + scala3-${{ env.RELEASE_TAG }}*.tar.gz \ + scala3-${{ env.RELEASE_TAG }}*.sha256 \ + scala3-${{ env.RELEASE_TAG }}.msi - name: Publish Release run: ./project/scripts/sbtPublish ";project scala3-bootstrapped ;publishSigned ;sonatypeBundleUpload" From b455651049118064b4b7cbc41a0e2a621bdd1830 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 16 Jan 2025 13:05:32 +0100 Subject: [PATCH 194/202] Add changelog for 3.6.4-RC1 --- changelogs/3.6.4-RC1.md | 157 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 changelogs/3.6.4-RC1.md diff --git a/changelogs/3.6.4-RC1.md b/changelogs/3.6.4-RC1.md new file mode 100644 index 000000000000..d2889b89a0ab --- /dev/null +++ b/changelogs/3.6.4-RC1.md @@ -0,0 +1,157 @@ +# Highlights of the release + +- Add REPL init script setting [#22206](https://github.com/scala/scala3/pull/22206) +- Support for JDK 24 [#22250](https://github.com/scala/scala3/pull/22250) +- Merge -Xno-decode-stacktraces with -Xno-enrich-error-messages [#22208](https://github.com/scala/scala3/pull/22208) +- Do not lift annotation arguments [#22035](https://github.com/scala/scala3/pull/22035) + +# Other changes and fixes + +## Annotations + +- Make sure symbols in annotation trees are fresh before pickling [#22002](https://github.com/scala/scala3/pull/22002) +- Consider all arguments in Annotations.refersToParamOf [#22001](https://github.com/scala/scala3/pull/22001) +- Do not lift annotation arguments (bis) [#22046](https://github.com/scala/scala3/pull/22046) + +## Desugaring + +- Fix #22051: only trust the type application part for case class unapplies [#22099](https://github.com/scala/scala3/pull/22099) + +## Documentation + +- Update example code linked to obsolete content in macros-spec.md [#22256](https://github.com/scala/scala3/pull/22256) + +## Experimental: Capture Checking + +- Fix #21868, #21869, and #21870: handle CapsOf in more places [#21875](https://github.com/scala/scala3/pull/21875) +- Refine rules for capture parameters and members [#22000](https://github.com/scala/scala3/pull/22000) +- Add a hint for using CC with REPL [#22220](https://github.com/scala/scala3/pull/22220) +- Consolidate CC [#21863](https://github.com/scala/scala3/pull/21863) + +## Experimental: Global Initialization + +- Fix crash when initializing val in ByName closure [#22354](https://github.com/scala/scala3/pull/22354) + +## Experimental: Named Tuples + +- Handle TypeProxy of Named Tuples in unapply [#22325](https://github.com/scala/scala3/pull/22325) +- Fail more eagerly when trying to adapt named unapply patterns [#22315](https://github.com/scala/scala3/pull/22315) +- Widen singleton types when computing fields from .Fields [#22149](https://github.com/scala/scala3/pull/22149) +- Fix .toTuple insertion [#22028](https://github.com/scala/scala3/pull/22028) + +## Extension Methods + +- Tweak ExtensionNullifiedByMember [#22268](https://github.com/scala/scala3/pull/22268) +- Nowarn extension matching nonpublic member [#21825](https://github.com/scala/scala3/pull/21825) + +## Implicits + +- Rollback constraints in compareAppliedTypeParamRef [#22339](https://github.com/scala/scala3/pull/22339) +- Try implicit searching after finding dynamic select [#22318](https://github.com/scala/scala3/pull/22318) + +## Inline + +- Drop phase.isTyper use in isLegalPrefix/asf [#21954](https://github.com/scala/scala3/pull/21954) + +## Linting + +- Allow discarding "Discarded non-Unit" warnings with `: Unit` [#21927](https://github.com/scala/scala3/pull/21927) + +## Match Types + +- Fix #21841: Check more that an `unapplySeq` on a `NonEmptyTuple` is valid. [#22366](https://github.com/scala/scala3/pull/22366) +- Type avoidance in MT bound inference [#22142](https://github.com/scala/scala3/pull/22142) + +## Metaprogramming + +- Rethrow SuspendExceptions caught in CodeGen phase [#22009](https://github.com/scala/scala3/pull/22009) + +## Metaprogramming: Compile-time + +- Extend compiletime.testing.typechecks with certain transform phases [#21185](https://github.com/scala/scala3/pull/21185) + +## Nullability + +- Fix #21619: Refactor NotNullInfo to record every reference which is retracted once. [#21624](https://github.com/scala/scala3/pull/21624) + +## Presentation Compiler + +- Use new infer expected type for singleton complations [#21421](https://github.com/scala/scala3/pull/21421) +- Fix match error in keyword completions [#22138](https://github.com/scala/scala3/pull/22138) + +## Reflection + +- Do not return java outline dummy constructor in `primaryConstructor` [#22104](https://github.com/scala/scala3/pull/22104) + +## Reporting + +- Normalise the types for Type Mismatch Error (E007) [#22337](https://github.com/scala/scala3/pull/22337) +- Improve given search preference warning [#22189](https://github.com/scala/scala3/pull/22189) +- Better error messages when an enum derives from AnyVal [#22236](https://github.com/scala/scala3/pull/22236) +- Correctly print litteral types in the refined printer [#22351](https://github.com/scala/scala3/pull/22351) + +## Rewrites + +- Undo patch of double-block apply [#21982](https://github.com/scala/scala3/pull/21982) + +## Scaladoc + +- Scaladoc: Add support for named tuples [#22263](https://github.com/scala/scala3/pull/22263) + +## Settings + +- Limit exposure to ConcurrentModificationException when sys props are replaced or mutated [#22180](https://github.com/scala/scala3/pull/22180) + +## Specification + +- Align the spec to allow the marker [#22323](https://github.com/scala/scala3/pull/22323) +- Integrate the specification for match types. [#22164](https://github.com/scala/scala3/pull/22164) + +## Transform + +- Fix #22226: Use `classOf[BoxedUnit]` for Unit array in `ArrayConstructors`. [#22238](https://github.com/scala/scala3/pull/22238) + +## Typer + +- Fixes for isLegalPrefix change [#22241](https://github.com/scala/scala3/pull/22241) +- Resolve name when named imp is behind wild imps [#21888](https://github.com/scala/scala3/pull/21888) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.6.3..3.6.4-RC1` these are: + +``` + 46 Martin Odersky + 33 noti0na1 + 17 Wojciech Mazur + 14 Dale Wijnand + 13 Matt Bovel + 11 Hamza Remmal + 7 Jan Chyb + 6 aherlihy + 5 Kacper Korban + 5 Seth Tisue + 5 Som Snytt + 4 Oliver Bračevac + 4 Yichen Xu + 3 Sébastien Doeraene + 3 dependabot[bot] + 3 kasiaMarek + 2 João Ferreira + 1 David Hua + 1 Eugene Flesselle + 1 Eugene Yokota + 1 Florian3k + 1 Jędrzej Rochala + 1 Kenji Yoshida + 1 Mathias + 1 Natsu Kagami + 1 Oleg Zenzin + 1 Piotr Chabelski + 1 Rui Chen + 1 philippus + 1 rochala + 1 xiaoshihou +``` From bc3e415a8182c8529f138a781bb60e2ab76bc539 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 16 Jan 2025 13:09:44 +0100 Subject: [PATCH 195/202] Release 3.6.4-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 b67974f4405d..39893a95633f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -98,7 +98,7 @@ object Build { * * Warning: Change of this variable needs to be consulted with `expectedTastyVersion` */ - val referenceVersion = "3.6.3-RC2" + val referenceVersion = "3.6.3" /** Version of the Scala compiler targeted in the current release cycle * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes @@ -136,7 +136,7 @@ object Build { * - in release candidate branch is experimental if {patch == 0} * - in stable release is always non-experimetnal */ - val expectedTastyVersion = "28.7-experimental-1" + val expectedTastyVersion = "28.6" 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 8ff590fefec5..8da8879185f5 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 = 7 + final val MinorVersion: Int = 6 /** 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 c4a18969d54d1fd3256efe0407ff4f74727e0446 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 24 Feb 2025 14:47:16 +0100 Subject: [PATCH 196/202] Revert "Drop phase.isTyper use in isLegalPrefix/asf" This reverts commit 26ecda540b93fbe1fc7be030559a78dd2db364f2. --- .../dotty/tools/dotc/core/TypeComparer.scala | 5 +- .../src/dotty/tools/dotc/core/TypeOps.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 29 ++++---- .../dotty/tools/dotc/transform/Recheck.scala | 4 +- .../test/dotc/pos-test-pickling.blacklist | 1 - tests/neg/6314-6.check | 4 +- tests/neg/i6225.scala | 2 +- tests/pos/i17222.2.scala | 30 -------- tests/pos/i17222.3.scala | 39 ---------- tests/pos/i17222.4.scala | 71 ------------------- tests/pos/i17222.5.scala | 26 ------- tests/pos/i17222.8.scala | 18 ----- tests/pos/i17222.scala | 33 --------- 13 files changed, 24 insertions(+), 240 deletions(-) delete mode 100644 tests/pos/i17222.2.scala delete mode 100644 tests/pos/i17222.3.scala delete mode 100644 tests/pos/i17222.4.scala delete mode 100644 tests/pos/i17222.5.scala delete mode 100644 tests/pos/i17222.8.scala delete mode 100644 tests/pos/i17222.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index bbe157d4a29b..ca3df65625a8 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -369,8 +369,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling } compareWild case tp2: LazyRef => - isBottom(tp1) - || !tp2.evaluating && recur(tp1, tp2.ref) + isBottom(tp1) || !tp2.evaluating && recur(tp1, tp2.ref) case CapturingType(_, _) => secondTry case tp2: AnnotatedType if !tp2.isRefining => @@ -490,7 +489,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // If `tp1` is in train of being evaluated, don't force it // because that would cause an assertionError. Return false instead. // See i859.scala for an example where we hit this case. - tp2.isAny + tp2.isRef(AnyClass, skipRefined = false) || !tp1.evaluating && recur(tp1.ref, tp2) case AndType(tp11, tp12) => if tp11.stripTypeVar eq tp12.stripTypeVar then recur(tp11, tp2) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index a7f41a71d7ce..e3168ca5a27d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -124,7 +124,7 @@ object TypeOps: } def isLegalPrefix(pre: Type)(using Context): Boolean = - pre.isStable + pre.isStable || !ctx.phase.isTyper /** Implementation of Types#simplified */ def simplify(tp: Type, theMap: SimplifyMap | Null)(using Context): Type = { diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c5937074f4bc..f77f268d6ee6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -98,8 +98,12 @@ object Types extends TypeUtils { // ----- Tests ----------------------------------------------------- // // debug only: a unique identifier for a type -// val uniqId = { nextId = nextId + 1; nextId } -// if uniqId == 19555 then trace.dumpStack() +// val uniqId = { +// nextId = nextId + 1 +// if (nextId == 19555) +// println("foo") +// nextId +// } /** A cache indicating whether the type was still provisional, last time we checked */ @sharable private var mightBeProvisional = true @@ -5578,25 +5582,24 @@ object Types extends TypeUtils { } def & (that: TypeBounds)(using Context): TypeBounds = - val lo1 = this.lo.stripLazyRef - val lo2 = that.lo.stripLazyRef - val hi1 = this.hi.stripLazyRef - val hi2 = that.hi.stripLazyRef - // This will try to preserve the FromJavaObjects type in upper bounds. // For example, (? <: FromJavaObjects | Null) & (? <: Any), // we want to get (? <: FromJavaObjects | Null) intead of (? <: Any), // because we may check the result <:< (? <: Object | Null) later. - if hi1.containsFromJavaObject && (hi1 frozen_<:< hi2) && (lo2 frozen_<:< lo1) then + if this.hi.containsFromJavaObject + && (this.hi frozen_<:< that.hi) + && (that.lo frozen_<:< this.lo) then // FromJavaObject in tp1.hi guarantees tp2.hi <:< tp1.hi // prefer tp1 if FromJavaObject is in its hi this - else if hi2.containsFromJavaObject && (hi2 frozen_<:< hi1) && (lo1 frozen_<:< lo2) then + else if that.hi.containsFromJavaObject + && (that.hi frozen_<:< this.hi) + && (this.lo frozen_<:< that.lo) then // Similarly, prefer tp2 if FromJavaObject is in its hi that - else if (lo1 frozen_<:< lo2) && (hi2 frozen_<:< hi1) then that - else if (lo2 frozen_<:< lo1) && (hi1 frozen_<:< hi2) then this - else TypeBounds(lo1 | lo2, hi1 & hi2) + else if (this.lo frozen_<:< that.lo) && (that.hi frozen_<:< this.hi) then that + else if (that.lo frozen_<:< this.lo) && (this.hi frozen_<:< that.hi) then this + else TypeBounds(this.lo | that.lo, this.hi & that.hi) def | (that: TypeBounds)(using Context): TypeBounds = if ((this.lo frozen_<:< that.lo) && (that.hi frozen_<:< this.hi)) this @@ -5605,7 +5608,7 @@ object Types extends TypeUtils { override def & (that: Type)(using Context): Type = that match { case that: TypeBounds => this & that - case _ => super.&(that) + case _ => super.& (that) } override def | (that: Type)(using Context): Type = that match { diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 172ae337d6e6..9631136a1c4e 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -219,10 +219,10 @@ abstract class Recheck extends Phase, SymTransformer: sharpen: Denotation => Denotation)(using Context): Type = if name.is(OuterSelectName) then tree.tpe else - val pre = ta.maybeSkolemizePrefix(qualType, name) + //val pre = ta.maybeSkolemizePrefix(qualType, name) val mbr = sharpen( - qualType.findMember(name, pre, + qualType.findMember(name, qualType, excluded = if tree.symbol.is(Private) then EmptyFlags else Private )).suchThat(tree.symbol == _) val newType = tree.tpe match diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 23c79affada0..07c157793f5d 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -24,7 +24,6 @@ t5031_2.scala i16997.scala i7414.scala i17588.scala -i8300.scala i9804.scala i13433.scala i16649-irrefutable.scala diff --git a/tests/neg/6314-6.check b/tests/neg/6314-6.check index df988f1db9dd..7d6bd182173d 100644 --- a/tests/neg/6314-6.check +++ b/tests/neg/6314-6.check @@ -4,7 +4,7 @@ |object creation impossible, since def apply(fa: String): Int in trait XX in object Test3 is not defined |(Note that | parameter String in def apply(fa: String): Int in trait XX in object Test3 does not match - | parameter Test3.Bar[X & (X & Y)] in def apply(fa: Test3.Bar[X & YY.this.Foo]): Test3.Bar[Y & YY.this.Foo] in trait YY in object Test3 + | parameter Test3.Bar[X & Object with Test3.YY {...}#Foo] in def apply(fa: Test3.Bar[X & YY.this.Foo]): Test3.Bar[Y & YY.this.Foo] in trait YY in object Test3 | ) -- Error: tests/neg/6314-6.scala:52:3 ---------------------------------------------------------------------------------- 52 | (new YY {}).boom // error: object creation impossible @@ -12,5 +12,5 @@ |object creation impossible, since def apply(fa: String): Int in trait XX in object Test4 is not defined |(Note that | parameter String in def apply(fa: String): Int in trait XX in object Test4 does not match - | parameter Test4.Bar[X & (X & Y)] in def apply(fa: Test4.Bar[X & YY.this.FooAlias]): Test4.Bar[Y & YY.this.FooAlias] in trait YY in object Test4 + | parameter Test4.Bar[X & Object with Test4.YY {...}#FooAlias] in def apply(fa: Test4.Bar[X & YY.this.FooAlias]): Test4.Bar[Y & YY.this.FooAlias] in trait YY in object Test4 | ) diff --git a/tests/neg/i6225.scala b/tests/neg/i6225.scala index bb936c9a79b1..148a484fd0f1 100644 --- a/tests/neg/i6225.scala +++ b/tests/neg/i6225.scala @@ -1,4 +1,4 @@ -object O1 { // error: cannot be instantiated +object O1 { type A[X] = X opaque type T = A // error: opaque type alias must be fully applied } diff --git a/tests/pos/i17222.2.scala b/tests/pos/i17222.2.scala deleted file mode 100644 index 34db494750c4..000000000000 --- a/tests/pos/i17222.2.scala +++ /dev/null @@ -1,30 +0,0 @@ -import scala.compiletime.* - -trait Reader[-In, Out] - -trait A: - type T - type F[X] - type Q = F[T] - -object Reader: - - given [X]: Reader[A { type Q = X }, X] with {} - -object Test: - - trait B[X] extends A: - type T = X - - trait C extends A: - type F[X] = X - - trait D[X] extends B[X] with C - - val d = new D[Int] {} - val bc = new B[Int] with C - - summonAll[(Reader[d.type, Int], Reader[d.type, Int])] // works - summonAll[(Reader[bc.type, Int], Reader[bc.type, Int])] // error - summonInline[Reader[d.type, Int]] // works - summonInline[Reader[bc.type, Int]] // works?? diff --git a/tests/pos/i17222.3.scala b/tests/pos/i17222.3.scala deleted file mode 100644 index 7ca85f65278f..000000000000 --- a/tests/pos/i17222.3.scala +++ /dev/null @@ -1,39 +0,0 @@ -import scala.compiletime.* - -trait Reader[-In, Out] - -trait A: - type T - type F[X] - type Q = F[T] - -object Reader: - - given [X]: Reader[A { type Q = X }, X] with {} - -object Test: - - trait B[X] extends A: - type T = X - - trait C extends A: - type F[X] = X - - trait D[X] extends B[X] with C - - val d = new D[Int] {} - val bc = new B[Int] with C - - case class Box[T](value: T) - - /** compiletime.summonAll, but with one case */ - inline def summonOne[T <: Box[?]]: T = - val res = - inline erasedValue[T] match - case _: Box[t] => summonInline[t] - end match - Box(res).asInstanceOf[T] - end summonOne - - summonOne[Box[Reader[d.type, Int]]] // works - summonOne[Box[Reader[bc.type, Int]]] // errors diff --git a/tests/pos/i17222.4.scala b/tests/pos/i17222.4.scala deleted file mode 100644 index 209425d47915..000000000000 --- a/tests/pos/i17222.4.scala +++ /dev/null @@ -1,71 +0,0 @@ -import scala.compiletime.* - -trait Reader[-In, Out] - -trait A: - type T - type F[X] - type Q = F[T] - -given [X]: Reader[A { type Q = X }, X] with {} - -case class Box[T](x: T) - -/** compiletime.summonAll, but with one case */ -inline def summonOne[T]: T = - val res = - inline erasedValue[T] match - case _: Box[t] => summonInline[t] - end match - Box(res).asInstanceOf[T] -end summonOne - - -@main def main = - - - trait B[X] extends A: - type T = X - - trait C extends A: - type F[X] = X - - - val bc = new B[Int] with C - - summonOne[Box[Reader[bc.type, Int]]] // errors - - - val bc2: A { type Q = Int } = new B[Int] with C - - summonOne[Box[Reader[bc2.type, Int]]] // works - - - object BC extends B[Int] with C - - summonOne[Box[Reader[BC.type, Int]]] // works - - - val a = new A: - type T = Int - type F[X] = X - - summonOne[Box[Reader[a.type, Int]]] // works - - - val b = new B[Int]: - type F[X] = X - - summonOne[Box[Reader[b.type, Int]]] // works - - - val ac = new A with C: - type T = Int - - summonOne[Box[Reader[ac.type, Int]]] // works - - - trait D[X] extends B[X] with C - val d = new D[Int] {} - - summonOne[Box[Reader[d.type, Int]]] // works diff --git a/tests/pos/i17222.5.scala b/tests/pos/i17222.5.scala deleted file mode 100644 index dc608e94235c..000000000000 --- a/tests/pos/i17222.5.scala +++ /dev/null @@ -1,26 +0,0 @@ -import scala.compiletime.* - -trait Reader[-In, Out] - -trait A: - type T - type F[X] - type Q = F[T] - -given [X]: Reader[A { type Q = X }, X] with {} - -case class Box[T](x: T) - -inline def summonOne[T]: T = - summonInline[T] -end summonOne - -@main def main = - trait B[X] extends A: - type T = X - trait C extends A: - type F[X] = X - - val bc = new B[Int] with C - summonInline[Reader[bc.type, Int]] // (I) Works - summonOne[Reader[bc.type, Int]] // (II) Errors diff --git a/tests/pos/i17222.8.scala b/tests/pos/i17222.8.scala deleted file mode 100644 index a415a78e0703..000000000000 --- a/tests/pos/i17222.8.scala +++ /dev/null @@ -1,18 +0,0 @@ -import scala.compiletime.* - -trait A: - type F - type Q = F - -trait Reader[-In, Out] -object Reader: - given [X]: Reader[A { type Q = X }, X] with {} - -class Test: - //type BC = A { type F = Int } & A // ok - type BC = A & A { type F = Int } // fail, also ok when manually de-aliased - - inline def summonOne: Unit = summonInline[Reader[BC, Int]] - - def t1(): Unit = summonInline[Reader[BC, Int]] // ok - def t2(): Unit = summonOne // error diff --git a/tests/pos/i17222.scala b/tests/pos/i17222.scala deleted file mode 100644 index 2af9fc2861a8..000000000000 --- a/tests/pos/i17222.scala +++ /dev/null @@ -1,33 +0,0 @@ -import scala.deriving.Mirror -import scala.compiletime.* - -trait Reader[-In, Out] - -trait A: - type T - type F[X] - type Q = F[T] - -object Reader: - - given [X]: Reader[A { type Q = X }, X] with {} - - type Map2[Tup1 <: Tuple, Tup2 <: Tuple, F[_, _]] <: Tuple = (Tup1, Tup2) match - case (h1 *: t1, h2 *: t2) => F[h1, h2] *: Map2[t1, t2, F] - case (EmptyTuple, EmptyTuple) => EmptyTuple - - inline given productReader[In <: Product, Out <: Product](using mi: Mirror.ProductOf[In])(using mo: Mirror.ProductOf[Out]): Reader[In, Out] = - summonAll[Map2[mi.MirroredElemTypes, mo.MirroredElemTypes, Reader]] - ??? - -object Test: - - trait B[X] extends A: - type T = X - - trait C extends A: - type F[X] = X - - val bc = new B[Int] with C - - summon[Reader[(bc.type, bc.type), (Int, Int)]] // fails From e25316c24ad01bc5629973be13ed314deb4878dd Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 24 Feb 2025 14:55:38 +0100 Subject: [PATCH 197/202] Adjust captures/lazylist test --- tests/neg-custom-args/captures/lazylist.check | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index 65fed0c4ec7e..bc95a445f3f4 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -29,7 +29,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:41:42 ------------------------------------- 41 | val ref4c: LazyList[Int]^{cap1, ref3} = ref4 // error | ^^^^ - | Found: (ref4 : lazylists.LazyList[Int]^{cap3, ref1, ref2}) + | Found: (ref4 : lazylists.LazyList[Int]^{cap3, cap2, ref1, cap1}) | Required: lazylists.LazyList[Int]^{cap1, ref3} | | longer explanation available when compiling with `-explain` From 27a73e915acd080eba4ccb6d2feac4f605bda70e Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 20 Jan 2025 16:17:04 +0100 Subject: [PATCH 198/202] Fix chocolatey-test when used in stable releases --- .github/workflows/ci.yaml | 4 ++-- .github/workflows/test-chocolatey.yml | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b6bd9c319b1c..c70eaaaa0e3e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -911,14 +911,14 @@ jobs: uses: ./.github/workflows/build-chocolatey.yml needs: [ build-sdk-package ] with: - version: 3.6.0-local # TODO: FIX THIS + version: 3.6.0-SNAPSHOT # Fake version, used only for choco tests url : https://api.github.com/repos/scala/scala3/actions/artifacts/${{ needs.build-sdk-package.outputs.win-x86_64-id }}/zip digest : ${{ needs.build-sdk-package.outputs.win-x86_64-digest }} test-chocolatey-package: uses: ./.github/workflows/test-chocolatey.yml with: - version : 3.6.0-local # TODO: FIX THIS + version : 3.6.0-SNAPSHOT # Fake version, used only for choco tests java-version: 8 if: github.event_name == 'pull_request' && contains(github.event.pull_request.body, '[test_chocolatey]') needs: [ build-chocolatey-package ] diff --git a/.github/workflows/test-chocolatey.yml b/.github/workflows/test-chocolatey.yml index b6ca9bf74b12..e302968b9129 100644 --- a/.github/workflows/test-chocolatey.yml +++ b/.github/workflows/test-chocolatey.yml @@ -21,7 +21,10 @@ on: env: CHOCOLATEY-REPOSITORY: chocolatey-pkgs - DOTTY_CI_INSTALLATION: ${{ secrets.GITHUB_TOKEN }} + # Controls behaviour of chocolatey{Install,Uninstall}.ps1 scripts + # During snapshot releases it uses a different layout and requires access token to GH Actions artifacts + # During stable releases it uses publically available archives + DOTTY_CI_INSTALLATION: ${{ endsWith(inputs.version, '-SNAPSHOT') && secrets.GITHUB_TOKEN || '' }} jobs: test: From e3b2838214fe528b87341b7f4ca90875e07d4dec Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 26 Feb 2025 10:41:12 +0100 Subject: [PATCH 199/202] Add changelog for 3.6.4-RC2 --- changelogs/3.6.4-RC2.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 changelogs/3.6.4-RC2.md diff --git a/changelogs/3.6.4-RC2.md b/changelogs/3.6.4-RC2.md new file mode 100644 index 000000000000..1edfad6321ee --- /dev/null +++ b/changelogs/3.6.4-RC2.md @@ -0,0 +1,13 @@ +# Reverted changes + +- Revert "Drop phase.isTyper use in isLegalPrefix/asf" from Scala 3.6.4 [#22653](https://github.com/lampepfl/dotty/pull/22653) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.6.4-RC1..3.6.4-RC2` these are: + +``` + 5 Wojciech Mazur +``` From 9d7f439403feb3c87b98bf26892600a5038c92ac Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 26 Feb 2025 10:42:08 +0100 Subject: [PATCH 200/202] Release 3.6.4-RC2 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 39893a95633f..d20bcff08ce6 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -118,7 +118,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 4377e99063c4bf7cbe81cac91dd4dff1a476f63c Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 5 Mar 2025 23:03:29 +0100 Subject: [PATCH 201/202] Add changelog for 3.6.4 --- changelogs/3.6.4.md | 168 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 changelogs/3.6.4.md diff --git a/changelogs/3.6.4.md b/changelogs/3.6.4.md new file mode 100644 index 000000000000..43d2502f7218 --- /dev/null +++ b/changelogs/3.6.4.md @@ -0,0 +1,168 @@ + +# Highlights of the release + +- Support for JDK 24 [#22250](https://github.com/scala/scala3/pull/22250) +- REPL `:silent` command to toggle automatic printing of outputs [#22248](https://github.com/scala/scala3/pull/22248) +- REPL `--repl-init-script:` setting to run a code on startup [#22206](https://github.com/scala/scala3/pull/22206) +- Deprecated setting `-Xno-decode-stacktraces` is now an alias to `-Xno-enrich-error-messages` [#22208](https://github.com/scala/scala3/pull/22208) +- Annotation arguments are no longer lifted [#22035](https://github.com/scala/scala3/pull/22035) +- Experimental Capture Checking: Implement tracked members [#21761](https://github.com/scala/scala3/pull/21761) + +## Breaking changes + +- Align `@implicitNotFound` and `@implicitAmbigous` with the language specification [#22371](https://github.com/scala/scala3/pull/22371) + + This change may impact users who previously used these annotations using variables or string interpolation. + + Previously, a bug in the Scala 3 compiler allowed non-literal strings to be passed as arguments to the `@implicitNotFound` and `@implicitAmbiguous` annotations. + This could have affected how failed implicit search results were reported by the compiler. + + Starting from Scala 3.6.4, the arguments for these annotations must be string literals. + If a message is too long, it can be concatenated using the `+` operator, allowing for constant folding. + +# Other changes and fixes + +## Annotations + +- Make sure symbols in annotation trees are fresh before pickling [#22002](https://github.com/scala/scala3/pull/22002) +- Consider all arguments in Annotations.refersToParamOf [#22001](https://github.com/scala/scala3/pull/22001) +- Do not lift annotation arguments (bis) [#22046](https://github.com/scala/scala3/pull/22046) + +## Desugaring + +- Fix #22051: only trust the type application part for case class unapplies [#22099](https://github.com/scala/scala3/pull/22099) + +## Documentation + +- Update example code linked to obsolete content in macros-spec.md [#22256](https://github.com/scala/scala3/pull/22256) + +## Experimental: Capture Checking + +- Fix #21868, #21869, and #21870: handle CapsOf in more places [#21875](https://github.com/scala/scala3/pull/21875) +- Refine rules for capture parameters and members [#22000](https://github.com/scala/scala3/pull/22000) +- Add a hint for using CC with REPL [#22220](https://github.com/scala/scala3/pull/22220) +- Consolidate CC [#21863](https://github.com/scala/scala3/pull/21863) + +## Experimental: Global Initialization + +- Fix crash when initializing val in ByName closure [#22354](https://github.com/scala/scala3/pull/22354) + +## Experimental: Named Tuples + +- Handle TypeProxy of Named Tuples in unapply [#22325](https://github.com/scala/scala3/pull/22325) +- Fail more eagerly when trying to adapt named unapply patterns [#22315](https://github.com/scala/scala3/pull/22315) +- Widen singleton types when computing fields from .Fields [#22149](https://github.com/scala/scala3/pull/22149) +- Fix .toTuple insertion [#22028](https://github.com/scala/scala3/pull/22028) + +## Extension Methods + +- Tweak ExtensionNullifiedByMember [#22268](https://github.com/scala/scala3/pull/22268) +- Nowarn extension matching nonpublic member [#21825](https://github.com/scala/scala3/pull/21825) + +## Implicits + +- Rollback constraints in compareAppliedTypeParamRef [#22339](https://github.com/scala/scala3/pull/22339) +- Try implicit searching after finding dynamic select [#22318](https://github.com/scala/scala3/pull/22318) + +## Linting + +- Allow discarding "Discarded non-Unit" warnings with `: Unit` [#21927](https://github.com/scala/scala3/pull/21927) + +## Match Types + +- Fix #21841: Check more that an `unapplySeq` on a `NonEmptyTuple` is valid. [#22366](https://github.com/scala/scala3/pull/22366) +- Type avoidance in MT bound inference [#22142](https://github.com/scala/scala3/pull/22142) + +## Metaprogramming + +- Rethrow SuspendExceptions caught in CodeGen phase [#22009](https://github.com/scala/scala3/pull/22009) + +## Metaprogramming: Compile-time + +- Extend compiletime.testing.typechecks with certain transform phases [#21185](https://github.com/scala/scala3/pull/21185) + +## Nullability + +- Fix #21619: Refactor NotNullInfo to record every reference which is retracted once. [#21624](https://github.com/scala/scala3/pull/21624) + +## Presentation Compiler + +- Use new infer expected type for singleton complations [#21421](https://github.com/scala/scala3/pull/21421) +- Fix match error in keyword completions [#22138](https://github.com/scala/scala3/pull/22138) + +## Reflection + +- Do not return java outline dummy constructor in `primaryConstructor` [#22104](https://github.com/scala/scala3/pull/22104) + +## Reporting + +- Normalise the types for Type Mismatch Error (E007) [#22337](https://github.com/scala/scala3/pull/22337) +- Improve given search preference warning [#22189](https://github.com/scala/scala3/pull/22189) +- Better error messages when an enum derives from AnyVal [#22236](https://github.com/scala/scala3/pull/22236) +- Correctly print litteral types in the refined printer [#22351](https://github.com/scala/scala3/pull/22351) + +## Rewrites + +- Undo patch of double-block apply [#21982](https://github.com/scala/scala3/pull/21982) + +## Scaladoc + +- Scaladoc: Add support for named tuples [#22263](https://github.com/scala/scala3/pull/22263) + +## Settings + +- Limit exposure to ConcurrentModificationException when sys props are replaced or mutated [#22180](https://github.com/scala/scala3/pull/22180) + +## Specification + +- Align the spec to allow the marker [#22323](https://github.com/scala/scala3/pull/22323) +- Integrate the specification for match types. [#22164](https://github.com/scala/scala3/pull/22164) + +## Transform + +- Fix #22226: Use `classOf[BoxedUnit]` for Unit array in `ArrayConstructors`. [#22238](https://github.com/scala/scala3/pull/22238) + +## Typer + +- Fixes for isLegalPrefix change [#22241](https://github.com/scala/scala3/pull/22241) +- Resolve name when named imp is behind wild imps [#21888](https://github.com/scala/scala3/pull/21888) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.6.3..3.6.4` these are: + +``` + 46 Martin Odersky + 33 noti0na1 + 24 Wojciech Mazur + 14 Dale Wijnand + 13 Matt Bovel + 11 Hamza Remmal + 7 Jan Chyb + 6 aherlihy + 5 Kacper Korban + 5 Seth Tisue + 5 Som Snytt + 4 Oliver Bračevac + 4 Yichen Xu + 3 Sébastien Doeraene + 3 dependabot[bot] + 3 kasiaMarek + 2 João Ferreira + 1 David Hua + 1 Eugene Flesselle + 1 Eugene Yokota + 1 Florian3k + 1 Jędrzej Rochala + 1 Kenji Yoshida + 1 Mathias + 1 Natsu Kagami + 1 Oleg Zenzin + 1 Piotr Chabelski + 1 Rui Chen + 1 philippus + 1 rochala + 1 xiaoshihou +``` From 6b2b8819191891f2057a1e11bda45035a4ca03ad Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 5 Mar 2025 23:05:41 +0100 Subject: [PATCH 202/202] Release 3.6.4 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index d20bcff08ce6..60f8e4d87be1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -118,7 +118,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 = 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: