From d4d6cec68e0ccab24525a868f9fc9497ec43473c Mon Sep 17 00:00:00 2001 From: kasiaMarek Date: Fri, 23 Aug 2024 11:20:58 +0200 Subject: [PATCH 01/38] 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 cb08b469025d96cbc8b454d588f14c9dd91448d5 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Thu, 21 Nov 2024 21:11:16 +0100 Subject: [PATCH 02/38] 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 03/38] 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 688ed4f168d2bd40acf478f637d27f6e03c1a7d2 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Wed, 27 Nov 2024 23:01:26 +0000 Subject: [PATCH 04/38] 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 05/38] 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 2d2b2ad4b27db3676389909c026791ea5f09d812 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 2 Dec 2024 16:16:47 +0000 Subject: [PATCH 06/38] 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 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 07/38] 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 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 08/38] 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 09/38] 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 10/38] 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 11/38] 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 12/38] 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 13/38] 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 14/38] 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 15/38] 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 16/38] 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 17/38] 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 18/38] 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 19/38] 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 20/38] 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 21/38] 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 22/38] 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 23/38] 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 24/38] 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 25/38] 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 26/38] 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 27/38] 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 28/38] 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 29/38] 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 30/38] 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 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 31/38] 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 32/38] 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 33/38] 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 0041987fdc19922ef0fabfe51f6f5621a9da54cf Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Thu, 19 Dec 2024 16:21:35 +0100 Subject: [PATCH 34/38] 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 35/38] 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 36/38] 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 37/38] 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 38/38] 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 ```