diff --git a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala index db5e98c9386a..2d085ee6afdf 100644 --- a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala +++ b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala @@ -298,6 +298,17 @@ class CommunityBuildTest: * @param arguments Arguments to pass to the testing program */ def test(project: String, command: String, arguments: Seq[String]): Unit = { + @annotation.tailrec + def execTimes(task: => Int, timesToRerun: Int): Boolean = + val exitCode = task + if exitCode == 0 + then true + else if timesToRerun == 0 + then false + else + log(s"Rerunning tests in $project because of a previous run failure.") + execTimes(task, timesToRerun - 1) + log(s"Building $project with dotty-bootstrapped $compilerVersion...") val projectDir = communitybuildDir.resolve("community-projects").resolve(project) @@ -312,9 +323,9 @@ class CommunityBuildTest: |""".stripMargin) } - val exitCode = exec(projectDir, command, arguments: _*) + val testsCompletedSuccessfully = execTimes(exec(projectDir, command, arguments: _*), 3) - if (exitCode != 0) { + if (!testsCompletedSuccessfully) { fail(s""" | |$command exited with an error code. To reproduce without JUnit, use: @@ -344,10 +355,10 @@ class CommunityBuildTest: @Test def ScalaPB = projects.ScalaPB.run() @Test def minitest = projects.minitest.run() @Test def fastparse = projects.fastparse.run() - @Test def utest = projects.utest.run() - @Test def sourcecode = projects.sourcecode.run() - @Test def oslib = projects.oslib.run() - @Test def ujson = projects.ujson.run() + //@Test def utest = projects.utest.run() + //@Test def sourcecode = projects.sourcecode.run() + //@Test def oslib = projects.oslib.run() + //@Test def ujson = projects.ujson.run() // @Test def oslibWatch = projects.oslibWatch.run() @Test def stdLib213 = projects.stdLib213.run() @Test def shapeless = projects.shapeless.run() diff --git a/compiler/src/dotty/tools/dotc/core/StagingContext.scala b/compiler/src/dotty/tools/dotc/core/StagingContext.scala index 6caba252cc90..0926ca85220d 100644 --- a/compiler/src/dotty/tools/dotc/core/StagingContext.scala +++ b/compiler/src/dotty/tools/dotc/core/StagingContext.scala @@ -1,8 +1,13 @@ package dotty.tools.dotc.core +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.util.Property +import dotty.tools.dotc.transform.PCPCheckAndHeal import scala.collection.mutable @@ -16,6 +21,8 @@ object StagingContext { */ private val QuoteContextStack = new Property.Key[List[tpd.Tree]] + private val TaggedTypes = new Property.Key[PCPCheckAndHeal.QuoteTypeTags] + /** All enclosing calls that are currently inlined, from innermost to outermost. */ def level(implicit ctx: Context): Int = ctx.property(QuotationLevel).getOrElse(0) @@ -34,6 +41,12 @@ object StagingContext { def spliceContext(implicit ctx: Context): Context = ctx.fresh.setProperty(QuotationLevel, level - 1) + def contextWithQuoteTypeTags(taggedTypes: PCPCheckAndHeal.QuoteTypeTags)(implicit ctx: Context) = + ctx.fresh.setProperty(TaggedTypes, taggedTypes) + + def getQuoteTypeTags(implicit ctx: Context): PCPCheckAndHeal.QuoteTypeTags = + ctx.property(TaggedTypes).get + /** Context with a decremented quotation level and pops the Some of top of the quote context stack or None if the stack is empty. * The quotation stack could be empty if we are in a top level splice or an eroneous splice directly witin a top level splice. */ diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 2fdc12375c78..6295e2a389f5 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2018,7 +2018,7 @@ object SymDenotations { var names = Set[Name]() def maybeAdd(name: Name) = if (keepOnly(thisType, name)) names += name try { - for (p <- classParents) + for (p <- classParents if p.classSymbol.isClass) for (name <- p.classSymbol.asClass.memberNames(keepOnly)) maybeAdd(name) val ownSyms = @@ -2383,7 +2383,7 @@ object SymDenotations { /** is the cache valid in current run at given phase? */ def isValidAt(phase: Phase)(implicit ctx: Context): Boolean - /** Render invalid this cache and all cache that depend on it */ + /** Render invalid this cache and all caches that depend on it */ def invalidate(): Unit } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 1f23faa69fd5..2a15090724d6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -24,6 +24,7 @@ import util.SimpleIdentitySet import reporting.diagnostic.Message import ast.tpd._ import ast.TreeTypeMap +import ast.untpd import printing.Texts._ import printing.Printer import Hashable._ @@ -39,6 +40,7 @@ import java.lang.ref.WeakReference import scala.annotation.internal.sharable import scala.annotation.threadUnsafe +import dotty.tools.dotc.ast.Trees.Untyped import dotty.tools.dotc.transform.SymUtils._ @@ -1113,7 +1115,7 @@ object Types { * re-lubbing it while allowing type parameters to be constrained further. * Any remaining union types are replaced by their joins. * - * For instance, if `A` is an unconstrained type variable, then + * For instance, if `A` is an unconstrained type variable, then * * ArrayBuffer[Int] | ArrayBuffer[A] * @@ -1122,6 +1124,8 @@ object Types { * * Exception (if `-YexplicitNulls` is set): if this type is a nullable union (i.e. of the form `T | Null`), * then the top-level union isn't widened. This is needed so that type inference can infer nullable types. + * + * Another exception is when there is an explicit type ascription, then the union isn't widened. */ def widenUnion(implicit ctx: Context): Type = widen match { case tp @ OrNull(tp1): OrType => @@ -1136,7 +1140,14 @@ object Types { def widenUnionWithoutNull(implicit ctx: Context): Type = widen match { case tp @ OrType(lhs, rhs) => ctx.typeComparer.lub(lhs.widenUnionWithoutNull, rhs.widenUnionWithoutNull, canConstrain = true) match { - case union: OrType => union.join + case union: OrType => + val keepUnion = ctx.tree match { + case DefDef(_, _, _, untpd.TypedSplice(_), _) => true + case ValDef(name, untpd.InfixOp(_, op, _), _) => op.symbol == ctx.definitions.orType + case _ => false + } + if (keepUnion) union else union.join + case res => res } case tp @ AndType(tp1, tp2) => diff --git a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index 8ebc2386353a..bcbe4625d8c8 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -52,7 +52,8 @@ object PickledQuotes { private def dealiasTypeTags(tp: Type)(implicit ctx: Context): Type = new TypeMap() { override def apply(tp: Type): Type = { val tp1 = tp match { - case tp: TypeRef if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => tp.dealias + case tp: TypeRef if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => + tp.symbol.info.hiBound case _ => tp } mapOver(tp1) diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 0af442b2c089..12f8a150f1c5 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -595,12 +595,11 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder if (!inlineBody.isEmpty) { // FIXME: If the body of an inlineable method changes, all the reverse // dependencies of this method need to be recompiled. sbt has no way - // of tracking method bodies, so as a hack we include the pretty-printed - // typed tree of the method as part of the signature we send to sbt. + // of tracking method bodies, so as a hack we include the printed + // tree of the method as part of the signature we send to sbt. // To do this properly we would need a way to hash trees and types in // dotty itself. - val printTypesCtx = ctx.fresh.setSetting(ctx.settings.XprintTypes, true) - annots += marker(inlineBody.show(printTypesCtx)) + annots += marker(inlineBody.toString) } // In the Scala2 ExtractAPI phase we only extract annotations that extend @@ -618,12 +617,12 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder // FIXME: To faithfully extract an API we should extract the annotation tree, // sbt instead wants us to extract the annotation type and its arguments, // to do this properly we would need a way to hash trees and types in dotty itself, - // instead we pretty-print the annotation tree. + // instead we use the raw string representation of the annotation tree. // However, we still need to extract the annotation type in the way sbt expect // because sbt uses this information to find tests to run (for example // junit tests are annotated @org.junit.Test). api.Annotation.of( apiType(annot.tree.tpe), // Used by sbt to find tests to run - Array(api.AnnotationArgument.of("FULLTREE", annot.tree.show))) + Array(api.AnnotationArgument.of("FULLTREE", annot.tree.toString))) } } diff --git a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala index dda62deed5f2..37d86c95cf08 100644 --- a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala +++ b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala @@ -21,6 +21,7 @@ import dotty.tools.dotc.transform.TreeMapWithStages._ import dotty.tools.dotc.typer.Checking import dotty.tools.dotc.typer.Implicits.SearchFailureType import dotty.tools.dotc.typer.Inliner +import dotty.tools.dotc.core.Annotations._ import scala.collection.mutable import dotty.tools.dotc.util.SourcePosition @@ -54,10 +55,21 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( /** Transform quoted trees while maintaining phase correctness */ override protected def transformQuotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { + val taggedTypes = new PCPCheckAndHeal.QuoteTypeTags(quote.span)(using ctx) + if (ctx.property(InAnnotation).isDefined) ctx.error("Cannot have a quote in an annotation", quote.sourcePos) - val body1 = transform(body)(quoteContext) - super.transformQuotation(body1, quote) + + val contextWithQuote = + if level == 0 then contextWithQuoteTypeTags(taggedTypes)(quoteContext) + else quoteContext + val body1 = transform(body)(contextWithQuote) + val body2 = + taggedTypes.getTypeTags match + case Nil => body1 + case tags => tpd.Block(tags, body1).withSpan(body.span) + + super.transformQuotation(body2, quote) } /** Transform splice @@ -73,7 +85,9 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( // internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...) val tp = checkType(splice.sourcePos).apply(splice.tpe.widenTermRefExpr) cpy.Apply(splice)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: qctx :: Nil), body1 :: Nil) - case splice: Select => cpy.Select(splice)(body1, splice.name) + case splice: Select => + val tagRef = getQuoteTypeTags.getTagRef(splice.qualifier.tpe.asInstanceOf[TermRef]) + ref(tagRef).withSpan(splice.span) } } @@ -120,11 +134,14 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( case tp: TypeRef if tp.symbol.isSplice => if (tp.isTerm) ctx.error(i"splice outside quotes", pos) - tp + if level > 0 then getQuoteTypeTags.getTagRef(tp.prefix.asInstanceOf[TermRef]) + else tp case tp: TypeRef if tp.symbol == defn.QuotedTypeClass.typeParams.head => - // Adapt direct references to the type of the type parameter T of a quoted.Type[T]. - // Replace it with a properly encoded type splice. This is the normal for expected for type splices. - tp.prefix.select(tpnme.splice) + if level > 0 then + // Adapt direct references to the type of the type parameter T of a quoted.Type[T]. + // Replace it with a properly encoded type splice. This is the normal form expected for type splices. + getQuoteTypeTags.getTagRef(tp.prefix.asInstanceOf[TermRef]) + else tp case tp: NamedType => checkSymLevel(tp.symbol, tp, pos) match { case Some(tpRef) => tpRef.tpe @@ -201,7 +218,9 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( sym.isClass // reference to this in inline methods ) case None => - sym.is(Package) || sym.owner.isStaticOwner || levelOK(sym.owner) + sym.is(Package) || sym.owner.isStaticOwner || + (sym.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) && level > 0) || + levelOK(sym.owner) } /** Try to heal reference to type `T` used in a higher level than its definition. @@ -212,10 +231,11 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = { val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp) val tag = ctx.typer.inferImplicitArg(reqType, pos.span) + tag.tpe match case tp: TermRef => checkStable(tp, pos) - Some(tag.select(tpnme.splice)) + Some(ref(getQuoteTypeTags.getTagRef(tp))) case _: SearchFailureType => levelError(sym, tp, pos, i""" @@ -242,3 +262,34 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( } } +object PCPCheckAndHeal { + import tpd._ + + class QuoteTypeTags(span: Span)(using ctx: Context) { + + private val tags = collection.mutable.LinkedHashMap.empty[Symbol, TypeDef] + + def getTagRef(spliced: TermRef): TypeRef = { + val typeDef = tags.getOrElseUpdate(spliced.symbol, mkTagSymbolAndAssignType(spliced)) + typeDef.symbol.typeRef + } + + def getTypeTags: List[TypeDef] = tags.valuesIterator.toList + + private def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = { + val splicedTree = tpd.ref(spliced).withSpan(span) + val rhs = splicedTree.select(tpnme.splice).withSpan(span) + val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs, EmptyTree) + val local = ctx.newSymbol( + owner = ctx.owner, + name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_").toTermName).toTypeName, + flags = Synthetic, + info = TypeAlias(splicedTree.tpe.select(tpnme.splice)), + coord = span).asType + local.addAnnotation(Annotation(defn.InternalQuoted_QuoteTypeTagAnnot)) + ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local) + } + + } + +} diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 9cafe398161c..8c0b652846d7 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -119,57 +119,6 @@ class ReifyQuotes extends MacroTransform { new QuoteReifier(this, capturers, nestedEmbedded, ctx.owner)(ctx) } - /** Assuming contains types `${}, ..., ${}`, the expression - * - * { @quoteTypeTag type = ${} - * ... - * @quoteTypeTag type = ${} - * - * } - * - * references to `TypeI` in `expr` are rewired to point to the locally - * defined versions. As a side effect, prepend the expressions `tag1, ..., `tagN` - * as splices. - */ - private def addTags(expr: Tree)(implicit ctx: Context): Tree = { - - def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = { - val splicedTree = tpd.ref(spliced).withSpan(expr.span) - val rhs = transform(splicedTree.select(tpnme.splice)) - val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs, EmptyTree) - val local = ctx.newSymbol( - owner = ctx.owner, - name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_").toTermName).toTypeName, - flags = Synthetic, - info = TypeAlias(splicedTree.tpe.select(tpnme.splice)), - coord = spliced.termSymbol.coord).asType - local.addAnnotation(Annotation(defn.InternalQuoted_QuoteTypeTagAnnot)) - ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local) - } - - val tagDefCache = new mutable.LinkedHashMap[Symbol, TypeDef]() - - def typeTagMap = new TypeMap() { - def apply(tp: Type): Type = tp match { - case tp: TypeRef if tp.symbol.isSplice => - tp.prefix match { - case prefix: TermRef => - val tagDef = tagDefCache.getOrElseUpdate(prefix.symbol, mkTagSymbolAndAssignType(prefix)) - tagDef.symbol.typeRef - } - case AnnotatedType(parent, _) => - apply(parent) // Only keep the Annotated tree - case _ => - mapOver(tp) - } - } - - val tagedTree = new TreeTypeMap(typeMap = typeTagMap).apply(expr) - - if (tagDefCache.isEmpty) expr - else Block(tagDefCache.valuesIterator.toList, tagedTree) - } - /** Split `body` into a core and a list of embedded splices. * Then if inside a splice, make a hole from these parts. * If outside a splice, generate a call tp `scala.quoted.Unpickler.unpickleType` or @@ -229,7 +178,7 @@ class ReifyQuotes extends MacroTransform { def pickleAsTasty() = { val meth = if (isType) ref(defn.Unpickler_unpickleType).appliedToType(originalTp) - else ref(defn.Unpickler_unpickleExpr).appliedToType(originalTp.widen) + else ref(defn.Unpickler_unpickleExpr).appliedToType(originalTp.widen.dealias) val pickledQuoteStrings = liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType) val splicesList = liftList(splices, defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), defn.AnyType)) meth.appliedTo(pickledQuoteStrings, splicesList) @@ -362,10 +311,10 @@ class ReifyQuotes extends MacroTransform { level == 1 && levelOf(sym).contains(1) && capturers.contains(sym) /** Transform `tree` and return the resulting tree and all `embedded` quotes - * or splices as a pair, after performing the `addTags` transform. + * or splices as a pair. */ private def splitQuote(tree: Tree)(implicit ctx: Context): (Tree, List[Tree]) = { - val tree1 = addTags(transform(tree)) + val tree1 = stipTypeAnnotations(transform(tree)) (tree1, embedded.getTrees) } @@ -374,6 +323,9 @@ class ReifyQuotes extends MacroTransform { (tree1, embedded.getTrees) } + private def stipTypeAnnotations(tree: Tree)(using Context): Tree = + new TreeTypeMap(typeMap = _.stripAnnots).apply(tree) + /** Register `body` as an `embedded` quote or splice * and return a hole with `splices` as arguments and the given type `tpe`. */ diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 0800eca3e598..708bf418925d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -48,7 +48,9 @@ class Staging extends MacroTransform { if (sym.is(ModuleClass)) sym.sourceModule.show else i"${sym.name}.this" val errMsg = s"\nin ${ctx.owner.fullName}" - assert(false, + assert( + ctx.owner.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) || + (sym.isType && levelOf(sym).getOrElse(0) > 0), em"""access to $symStr from wrong staging level: | - the definition is at level ${levelOf(sym).getOrElse(0)}, | - but the access is at level $level.$errMsg""") diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index c2fda21e3ac3..47749a28265c 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -6,7 +6,7 @@ import core._ import Contexts._, Types._, Symbols._, Names._, Decorators._, ProtoTypes._ import Flags._, SymDenotations._ import NameKinds.FlatName -import config.Printers.implicits +import config.Printers.{implicits, implicitsDetailed} import util.Spans.Span import ast.{untpd, tpd} import Implicits.{hasExtMethod, Candidate} @@ -97,7 +97,7 @@ trait ImportSuggestions: def rootsIn(ref: TermRef)(using Context): List[TermRef] = if seen.contains(ref) then Nil else - implicits.println(i"search for suggestions in ${ref.symbol.fullName}") + implicitsDetailed.println(i"search for suggestions in ${ref.symbol.fullName}") seen += ref ref :: rootsStrictlyIn(ref) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index d1c65000726c..45e33741172f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -559,19 +559,19 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { def issueError() = callValueArgss match { case (msgArg :: Nil) :: Nil => - msgArg.tpe match { - case ConstantType(Constant(msg: String)) => - // Usually `error` is called from within a rewrite method. In this - // case we need to report the error at the point of the outermost enclosing inline - // call. This way, a defensively written rewrite methid can always - // report bad inputs at the point of call instead of revealing its internals. - val callToReport = if (enclosingInlineds.nonEmpty) enclosingInlineds.last else call - val ctxToReport = ctx.outersIterator.dropWhile(enclosingInlineds(_).nonEmpty).next - def issueInCtx(implicit ctx: Context) = - ctx.error(msg, callToReport.sourcePos) - issueInCtx(ctxToReport) - case _ => + val message = msgArg.tpe match { + case ConstantType(Constant(msg: String)) => msg + case _ => s"A literal string is expected as an argument to `compiletime.error`. Got ${msgArg.show}" } + // Usually `error` is called from within a rewrite method. In this + // case we need to report the error at the point of the outermost enclosing inline + // call. This way, a defensively written rewrite methid can always + // report bad inputs at the point of call instead of revealing its internals. + val callToReport = if (enclosingInlineds.nonEmpty) enclosingInlineds.last else call + val ctxToReport = ctx.outersIterator.dropWhile(enclosingInlineds(_).nonEmpty).next + def issueInCtx(implicit ctx: Context) = + ctx.error(message, callToReport.sourcePos) + issueInCtx(ctxToReport) case _ => } @@ -1316,4 +1316,3 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } }.apply(Nil, tree) } - diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e4fb2d353f90..096815b68b92 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -265,6 +265,49 @@ class Namer { typer: Typer => sym } + /** Check that a new definition with given name and privacy status + * in current context would not conflict with existing currently + * compiled definitions. + * The logic here is very subtle and fragile due to the fact that + * we are not allowed to force anything. + */ + def checkNoConflict(name: Name, isPrivate: Boolean, span: Span)(using ctx: Context): Name = + val owner = ctx.owner + var conflictsDetected = false + + def conflict(conflicting: Symbol) = + val where: String = + if conflicting.owner == owner then "" + else if conflicting.owner.isPackageObject then i" in ${conflicting.associatedFile}" + else i" in ${conflicting.owner}" + ctx.error(i"$name is already defined as $conflicting$where", ctx.source.atSpan(span)) + conflictsDetected = true + + def checkNoConflictIn(owner: Symbol) = + val preExisting = owner.unforcedDecls.lookup(name) + if (preExisting.isDefinedInCurrentRun || preExisting.lastKnownDenotation.is(Package)) + && (!preExisting.lastKnownDenotation.is(Private) || preExisting.owner.is(Package)) + then conflict(preExisting) + + def pkgObjs(pkg: Symbol) = + pkg.denot.asInstanceOf[PackageClassDenotation].packageObjs.map(_.symbol) + + if owner.is(PackageClass) then + checkNoConflictIn(owner) + for pkgObj <- pkgObjs(owner) do + checkNoConflictIn(pkgObj) + else + def preExisting = ctx.effectiveScope.lookup(name) + if (!owner.isClass || name.isTypeName) && preExisting.exists then + conflict(preExisting) + else if owner.isPackageObject && !isPrivate && name != nme.CONSTRUCTOR then + checkNoConflictIn(owner.owner) + for pkgObj <- pkgObjs(owner.owner) if pkgObj != owner do + checkNoConflictIn(pkgObj) + + if conflictsDetected then name.freshened else name + end checkNoConflict + /** If this tree is a member def or an import, create a symbol of it * and store in symOfTree map. */ @@ -302,49 +345,6 @@ class Namer { typer: Typer => typr.println(i"creating symbol for $tree in ${ctx.mode}") - /** Check that a new definition with given name and privacy status - * in current context would not conflict with existing currently - * compiled definitions. - * The logic here is very subtle and fragile due to the fact that - * we are not allowed to force anything. - */ - def checkNoConflict(name: Name, isPrivate: Boolean): Name = - val owner = ctx.owner - var conflictsDetected = false - - def conflict(conflicting: Symbol) = - val where: String = - if conflicting.owner == owner then "" - else if conflicting.owner.isPackageObject then i" in ${conflicting.associatedFile}" - else i" in ${conflicting.owner}" - ctx.error(i"$name is already defined as $conflicting$where", tree.sourcePos) - conflictsDetected = true - - def checkNoConflictIn(owner: Symbol) = - val preExisting = owner.unforcedDecls.lookup(name) - if (preExisting.isDefinedInCurrentRun || preExisting.lastKnownDenotation.is(Package)) - && (!preExisting.lastKnownDenotation.is(Private) || preExisting.owner.is(Package)) - then conflict(preExisting) - - def pkgObjs(pkg: Symbol) = - pkg.denot.asInstanceOf[PackageClassDenotation].packageObjs.map(_.symbol) - - if owner.is(PackageClass) then - checkNoConflictIn(owner) - for pkgObj <- pkgObjs(owner) do - checkNoConflictIn(pkgObj) - else - def preExisting = ctx.effectiveScope.lookup(name) - if (!owner.isClass || name.isTypeName) && preExisting.exists then - conflict(preExisting) - else if owner.isPackageObject && !isPrivate && name != nme.CONSTRUCTOR then - checkNoConflictIn(owner.owner) - for pkgObj <- pkgObjs(owner.owner) if pkgObj != owner do - checkNoConflictIn(pkgObj) - - if conflictsDetected then name.freshened else name - end checkNoConflict - /** Create new symbol or redefine existing symbol under lateCompile. */ def createOrRefine[S <: Symbol]( tree: MemberDef, name: Name, flags: FlagSet, owner: Symbol, infoFn: S => Type, @@ -376,7 +376,7 @@ class Namer { typer: Typer => tree match { case tree: TypeDef if tree.isClassDef => val flags = checkFlags(tree.mods.flags &~ GivenOrImplicit) - val name = checkNoConflict(tree.name, flags.is(Private)).asTypeName + val name = checkNoConflict(tree.name, flags.is(Private), tree.span).asTypeName val cls = createOrRefine[ClassSymbol](tree, name, flags, ctx.owner, cls => adjustIfModule(new ClassCompleter(cls, tree)(ctx), tree), @@ -385,7 +385,7 @@ class Namer { typer: Typer => cls case tree: MemberDef => var flags = checkFlags(tree.mods.flags) - val name = checkNoConflict(tree.name, flags.is(Private)) + val name = checkNoConflict(tree.name, flags.is(Private), tree.span) tree match case tree: ValOrDefDef => if tree.unforcedRhs == EmptyTree @@ -725,7 +725,7 @@ class Namer { typer: Typer => moduleDef.getOrElse(name.moduleClassName, EmptyTree) match { case t: TypeDef => createLinks(cdef, t) - case EmptyTree => + case Thicket(_) => } // If a top-level object or class has no companion in the current run, we @@ -898,28 +898,25 @@ class Namer { typer: Typer => def registerIfChild(denot: SymDenotation)(implicit ctx: Context): Unit = { val sym = denot.symbol - def register(child: Symbol, parent: Type) = { - val cls = parent.classSymbol - if (cls.is(Sealed)) - if ((child.isInaccessibleChildOf(cls) || child.isAnonymousClass) && !sym.hasAnonymousChild) - addChild(cls, cls) - else if (!cls.is(ChildrenQueried)) - addChild(cls, child) + def register(child: Symbol, parentCls: ClassSymbol) = { + if (parentCls.is(Sealed)) + if ((child.isInaccessibleChildOf(parentCls) || child.isAnonymousClass) && !sym.hasAnonymousChild) + addChild(parentCls, parentCls) + else if (!parentCls.is(ChildrenQueried)) + addChild(parentCls, child) else - ctx.error(em"""children of $cls were already queried before $sym was discovered. - |As a remedy, you could move $sym on the same nesting level as $cls.""", + ctx.error(em"""children of $parentCls were already queried before $sym was discovered. + |As a remedy, you could move $sym on the same nesting level as $parentCls.""", child.sourcePos) } - if (denot.isClass && !sym.isEnumAnonymClass && !sym.isRefinementClass) - denot.asClass.classParents.foreach { parent => - val child = if (denot.is(Module)) denot.sourceModule else denot.symbol - register(child, parent) - } - else if (denot.is(CaseVal, butNot = Method | Module)) { + if denot.isClass && !sym.isEnumAnonymClass && !sym.isRefinementClass then + val child = if (denot.is(Module)) denot.sourceModule else denot.symbol + denot.asClass.classParents.foreach { parent => register(child, parent.classSymbol.asClass) } + else if denot.is(CaseVal, butNot = Method | Module) then assert(denot.is(Enum), denot) - register(denot.symbol, denot.info) - } + denot.info.classSymbols.foreach { parent => register(denot.symbol, parent) } + end if } /** Intentionally left without `implicit ctx` parameter. We need @@ -1079,9 +1076,10 @@ class Namer { typer: Typer => if (whyNoForwarder(mbr) == "") { val sym = mbr.symbol val forwarder = - if (mbr.isType) + if mbr.isType then + val forwarderName = checkNoConflict(alias.toTypeName, isPrivate = false, span) ctx.newSymbol( - cls, alias.toTypeName, + cls, forwarderName, Exported | Final, TypeAlias(path.tpe.select(sym)), coord = span) @@ -1096,7 +1094,8 @@ class Namer { typer: Typer => else (EmptyFlags, mbr.info.ensureMethodic) val mbrFlags = Exported | Method | Final | maybeStable | sym.flags & RetainedExportFlags - ctx.newSymbol(cls, alias, mbrFlags, mbrInfo, coord = span) + val forwarderName = checkNoConflict(alias, isPrivate = false, span) + ctx.newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span) } forwarder.info = avoidPrivateLeaks(forwarder) val forwarderDef = @@ -1146,9 +1145,9 @@ class Namer { typer: Typer => forwarders } - val forwarderss = - for (exp @ Export(_, _) <- rest) yield exportForwarders(exp) - forwarderss.foreach(_.foreach(fwdr => fwdr.symbol.entered)) + for case exp @ Export(_, _) <- rest do + for forwarder <- exportForwarders(exp) do + forwarder.symbol.entered } /** Ensure constructor is completed so that any parameter accessors diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 38a87bb2063f..e813b87c978f 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -87,7 +87,7 @@ trait QuotesAndSplices { if (c.owner eq c.outer.owner) markAsMacro(c.outer) else if (c.owner.isInlineMethod) c.owner.setFlag(Macro) else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) - else assert(false) // Did not find inline def to mark as macro + else assert(ctx.reporter.hasErrors) // Did not find inline def to mark as macro markAsMacro(ctx) } diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index a5315400efb1..fde38930f66b 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -169,7 +169,7 @@ object RefChecks { * TODO This still needs to be cleaned up; the current version is a straight port of what was there * before, but it looks too complicated and method bodies are far too large. */ - private def checkAllOverrides(clazz: Symbol)(implicit ctx: Context): Unit = { + private def checkAllOverrides(clazz: ClassSymbol)(implicit ctx: Context): Unit = { val self = clazz.thisType val upwardsSelf = upwardsThisType(clazz) var hasErrors = false @@ -470,33 +470,52 @@ object RefChecks { } } - def ignoreDeferred(member: SingleDenotation) = - member.isType || { - val mbr = member.symbol - mbr.isSuperAccessor || // not yet synthesized - ShortcutImplicits.isImplicitShortcut(mbr) || // only synthesized when referenced, see Note in ShortcutImplicits - mbr.is(JavaDefined) && hasJavaErasedOverriding(mbr) - } + def ignoreDeferred(mbr: Symbol) = + mbr.isType + || mbr.isSuperAccessor // not yet synthesized + || ShortcutImplicits.isImplicitShortcut(mbr) // only synthesized when referenced, see Note in ShortcutImplicits + || mbr.is(JavaDefined) && hasJavaErasedOverriding(mbr) + + def isImplemented(mbr: Symbol) = + val mbrType = clazz.thisType.memberInfo(mbr) + def (sym: Symbol).isConcrete = sym.exists && !sym.is(Deferred) + clazz.nonPrivateMembersNamed(mbr.name) + .filterWithPredicate( + impl => impl.symbol.isConcrete && mbrType.matchesLoosely(impl.info)) + .exists + + /** The term symbols in this class and its baseclasses that are + * abstract in this class. We can't use memberNames for that since + * a concrete member might have the same signature as an abstract + * member in a base class, yet might not override it. + */ + def missingTermSymbols: List[Symbol] = + val buf = new mutable.ListBuffer[Symbol] + for bc <- clazz.baseClasses + sym <- bc.info.decls.toList + if sym.is(DeferredTerm) && !isImplemented(sym) && !ignoreDeferred(sym) + do buf += sym + buf.toList // 2. Check that only abstract classes have deferred members def checkNoAbstractMembers(): Unit = { // Avoid spurious duplicates: first gather any missing members. - val missing = clazz.thisType.abstractTermMembers.filterNot(ignoreDeferred) + val missing = missingTermSymbols // Group missing members by the name of the underlying symbol, // to consolidate getters and setters. - val grouped = missing.groupBy(_.symbol.underlyingSymbol.name) + val grouped = missing.groupBy(_.underlyingSymbol.name) val missingMethods = grouped.toList flatMap { case (name, syms) => - val withoutSetters = syms filterNot (_.symbol.isSetter) + val withoutSetters = syms filterNot (_.isSetter) if (withoutSetters.nonEmpty) withoutSetters else syms } def stubImplementations: List[String] = { // Grouping missing methods by the declaring class - val regrouped = missingMethods.groupBy(_.symbol.owner).toList - def membersStrings(members: List[SingleDenotation]) = - members.sortBy(_.symbol.name.toString).map(_.showDcl + " = ???") + val regrouped = missingMethods.groupBy(_.owner).toList + def membersStrings(members: List[Symbol]) = + members.sortBy(_.name.toString).map(_.showDcl + " = ???") if (regrouped.tail.isEmpty) membersStrings(regrouped.head._2) @@ -520,10 +539,9 @@ object RefChecks { } for (member <- missing) { - val memberSym = member.symbol def undefined(msg: String) = abstractClassError(false, s"${member.showDcl} is not defined $msg") - val underlying = memberSym.underlyingSymbol + val underlying = member.underlyingSymbol // Give a specific error message for abstract vars based on why it fails: // It could be unimplemented, have only one accessor, or be uninitialized. @@ -531,11 +549,11 @@ object RefChecks { val isMultiple = grouped.getOrElse(underlying.name(ctx), Nil).size > 1 // If both getter and setter are missing, squelch the setter error. - if (memberSym.isSetter && isMultiple) () + if (member.isSetter && isMultiple) () else undefined( - if (memberSym.isSetter) "\n(Note that an abstract var requires a setter in addition to the getter)" - else if (memberSym.isGetter && !isMultiple) "\n(Note that an abstract var requires a getter in addition to the setter)" - else err.abstractVarMessage(memberSym)) + if (member.isSetter) "\n(Note that an abstract var requires a setter in addition to the getter)" + else if (member.isGetter && !isMultiple) "\n(Note that an abstract var requires a getter in addition to the setter)" + else err.abstractVarMessage(member)) } else if (underlying.is(Method)) { // If there is a concrete method whose name matches the unimplemented @@ -961,7 +979,7 @@ class RefChecks extends MiniPhase { thisPhase => } override def transformTemplate(tree: Template)(implicit ctx: Context): Tree = try { - val cls = ctx.owner + val cls = ctx.owner.asClass checkOverloadedRestrictions(cls) checkParents(cls) if (cls.is(Trait)) tree.parents.foreach(checkParentPrefix(cls, _)) diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 3d112e422a7a..7bfe50ed231b 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -11,6 +11,7 @@ t3612.scala reference scala-days-2019-slides i7048e.scala +i8052.scala # Stale symbol: package object scala seqtype-cycle diff --git a/compiler/test/dotty/tools/repl/LoadTests.scala b/compiler/test/dotty/tools/repl/LoadTests.scala index a94d94183f11..6786f8da16b5 100644 --- a/compiler/test/dotty/tools/repl/LoadTests.scala +++ b/compiler/test/dotty/tools/repl/LoadTests.scala @@ -1,13 +1,14 @@ package dotty.tools.repl -import java.nio.file.{ Path, Files } +import java.nio.file.{Path, Files} import java.util.Comparator +import java.util.regex.Pattern -import org.junit.{ Test, BeforeClass, AfterClass } +import org.junit.{Test, BeforeClass, AfterClass} import org.junit.Assert.assertEquals class LoadTests extends ReplTest { - import LoadTests._ + import LoadTests._, ReplCompilerTests._ @Test def helloworld = loadTest( file = """|def helloWorld = "Hello, World!" @@ -55,9 +56,9 @@ class LoadTests extends ReplTest { def loadTest(file: String, defs: String, runCode: String, output: String) = eval(s":load ${writeFile(file)}").andThen { implicit s => - assertEquals(defs, storedOutput()) + assertMultiLineEquals(defs, storedOutput()) run(runCode) - assertEquals(output, storedOutput()) + assertMultiLineEquals(output, storedOutput()) } private def eval(code: String): State = diff --git a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala index e738d38a074a..2d190836c707 100644 --- a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala +++ b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala @@ -1,9 +1,12 @@ package dotty.tools.repl +import java.util.regex.Pattern + import org.junit.Assert.{assertTrue => assert, _} import org.junit.{Ignore, Test} class ReplCompilerTests extends ReplTest { + import ReplCompilerTests._ private def lines() = storedOutput().trim.linesIterator.toList @@ -157,7 +160,7 @@ class ReplCompilerTests extends ReplTest { |} """.stripMargin) } .andThen { implicit state => - assertEquals( + assertMultiLineEquals( """// defined trait Ord |// defined object IntOrd""".stripMargin, storedOutput().trim @@ -173,7 +176,7 @@ class ReplCompilerTests extends ReplTest { @Test def testSingletonPrint = fromInitialState { implicit state => run("""val a = "hello"; val x: a.type = a""") - assertEquals("val a: String = hello\nval x: a.type = hello", storedOutput().trim) + assertMultiLineEquals("val a: String = hello\nval x: a.type = hello", storedOutput().trim) } @Test def i6574 = fromInitialState { implicit state => @@ -181,3 +184,16 @@ class ReplCompilerTests extends ReplTest { assertEquals("val a: 1 | 0 = 1", storedOutput().trim) } } + +object ReplCompilerTests { + + private val pattern = Pattern.compile("\\r[\\n]?|\\n"); + + // Ensure 'expected' and 'actual' contain the same line separator(s). + def assertMultiLineEquals(expected: String, actual: String): Unit = { + val expected0 = pattern.matcher(expected).replaceAll(System.lineSeparator) + val actual0 = pattern.matcher(actual).replaceAll(System.lineSeparator) + assertEquals(expected0, actual0) + } + +} diff --git a/docs/docs/reference/changed-features/operators.md b/docs/docs/reference/changed-features/operators.md index dee50420d3a2..5ff0d12a3e13 100644 --- a/docs/docs/reference/changed-features/operators.md +++ b/docs/docs/reference/changed-features/operators.md @@ -10,6 +10,8 @@ Furthermore, a syntax change allows infix operators to be written on the left in An `@alpha` annotation on a method definition defines an alternate name for the implementation of that method: Example: ```scala +import scala.annotation.alpha + object VecOps { @alpha("append") def (xs: Vec[T]) ++= [T] (ys: Vec[T]): Vec[T] = ... } @@ -47,7 +49,7 @@ The `@alpha` annotation serves a dual purpose: 5. Definitions with names in backticks that are not legal host platform names should have an `@alpha` annotation. Lack of such an annotation will raise a deprecation warning. - 6. @alpha annotations must agree: If two definitions are members of an object or class with the same name and matching types, then either none of them has an `@alpha` annotation, or both have `@alpha` annotations with the same name. + 6. `@alpha` annotations must agree: If two definitions are members of an object or class with the same name and matching types, then either none of them has an `@alpha` annotation, or both have `@alpha` annotations with the same name. 7. There must be a one-to-one relationship between external and internal names: If two definitions are members of an object or class with matching types and both have `@alpha` annotations with the same external name, then their internal method names must also be the same. @@ -56,6 +58,8 @@ The `@alpha` annotation serves a dual purpose: An `@infix` annotation on a method definition allows using the method as an infix operation. Example: ```scala +import scala.annotation.alpha + trait MultiSet[T] { @infix @@ -70,6 +74,7 @@ trait MultiSet[T] { val s1, s2: MultiSet[Int] s1 union s2 // OK +s1 `union` s2 // also OK but unusual s1.union(s2) // also OK s1.difference(s2) // OK @@ -77,6 +82,7 @@ s1 `difference` s2 // OK s1 difference s2 // gives a deprecation warning s1 * s2 // OK +s1 `*` s2 // also OK, but unusual s1.*(s2) // also OK, but unusual ``` Infix operations involving alphanumeric operators are deprecated, unless @@ -91,7 +97,7 @@ any unicode character `c` for which `java.lang.Character.isIdentifierPart(c)` re Infix operations involving symbolic operators are always allowed, so `@infix` is redundant for methods with symbolic names. -The @infix annotation can also be given to a type: +The `@infix` annotation can also be given to a type: ``` @infix type or[X, Y] val x: String or Int = ... @@ -118,7 +124,7 @@ The purpose of the `@infix` annotation is to achieve consistency across a code b @infix def (x: A) op (y1: B, y2: B): R // error: two parameters ``` - 4. @infix annotations can also be given to type, trait or class definitions that have exactly two type parameters. An infix type like + 4. `@infix` annotations can also be given to type, trait or class definitions that have exactly two type parameters. An infix type like ```scala @infix type op[X, Y] @@ -126,7 +132,7 @@ The purpose of the `@infix` annotation is to achieve consistency across a code b can be applied using infix syntax, i.e. `A op B`. - 5. To smooth migration to Scala 3.0, alphanumeric operations will only be deprecated from Scala 3.1 onwards, + 5. To smooth migration to Scala 3.0, alphanumeric operators will only be deprecated from Scala 3.1 onwards, or if the `-strict` option is given in Dotty/Scala 3. ## Syntax Change diff --git a/docs/docs/reference/contextual/context-functions.md b/docs/docs/reference/contextual/context-functions.md index e8e7034da15b..bd20a6de022a 100644 --- a/docs/docs/reference/contextual/context-functions.md +++ b/docs/docs/reference/contextual/context-functions.md @@ -11,7 +11,7 @@ type Executable[T] = ExecutionContext ?=> T ``` Context function are written using `?=>` as the "arrow" sign. They are applied to synthesized arguments, in -the same way methods with context parameters is applied. For instance: +the same way methods with context parameters are applied. For instance: ```scala given ec as ExecutionContext = ... diff --git a/docs/docs/reference/metaprogramming/macros.md b/docs/docs/reference/metaprogramming/macros.md index e6047779cba7..f34a8a8fc095 100644 --- a/docs/docs/reference/metaprogramming/macros.md +++ b/docs/docs/reference/metaprogramming/macros.md @@ -377,7 +377,7 @@ val program = { ${ Macros.assertImpl('{ x != 0) } } } ``` -The example is only phase correct because Macros is a global value and +The example is only phase correct because `Macros` is a global value and as such not subject to phase consistency checking. Conceptually that’s a bit unsatisfactory. If the PCP is so fundamental, it should be applicable without the global value exception. But in the example as @@ -529,7 +529,7 @@ val arr: Array[Int] = Array.apply(1, [2,3 : Int]:Int*) var sum = 0 val f = x => '{sum += $x} -${ _root_.Macros.map(arr, '[Int], 'f)} +${ _root_.Macros.map(arr, 'f)('[Int])} sum ``` @@ -616,10 +616,10 @@ It is possible to deconstruct or extract values out of `Expr` using pattern matc #### scala.quoted.matching -In `scala.quoted.matching` contains object that can help extract values from `Expr`. +`scala.quoted.matching` contains objects that can help extracting values from `Expr`. -* `scala.quoted.matching.Const`: matches an expression a literal value and returns the value. -* `scala.quoted.matching.Value`: matches an expression a value and returns the value. +* `scala.quoted.matching.Const`: matches an expression of a literal value and returns the value. +* `scala.quoted.matching.Value`: matches an expression of a value and returns the value. * `scala.quoted.matching.ExprSeq`: matches an explicit sequence of expresions and returns them. These sequences are useful to get individual `Expr[T]` out of a varargs expression of type `Expr[Seq[T]]`. * `scala.quoted.matching.ConstSeq`: matches an explicit sequence of literal values and returns them. * `scala.quoted.matching.ValueSeq`: matches an explicit sequence of values and returns them. diff --git a/docs/docs/reference/metaprogramming/staging.md b/docs/docs/reference/metaprogramming/staging.md index 3444e3a0be79..f5a59f4f946a 100644 --- a/docs/docs/reference/metaprogramming/staging.md +++ b/docs/docs/reference/metaprogramming/staging.md @@ -8,13 +8,13 @@ multi-staging programming. We can think of compile-time meta-programming as a two stage compilation process: one that we write the code in top-level splices, that will be used for code generation (macros) and one that will perform all necessecary evaluations at compile-time and an object program that we will run -as usual. What if we could synthesize code at runtime and offer one extra stage -to the programmer? Then we can have a value of type `Expr[T]` at runtime that we +as usual. What if we could synthesize code at run-time and offer one extra stage +to the programmer? Then we can have a value of type `Expr[T]` at run-time that we can essentially treat as a typed-syntax tree that we can either _show_ as a string (pretty-print) or compile and run. If the number of quotes exceeds the -number of splices more than one (effectively handling at run-time values of type -`Expr[Expr[T]]`, `Expr[Expr[Expr[T]]]`, ... we talk about Multi-Stage -Programming). +number of splices by more than one (effectively handling at run-time values of type +`Expr[Expr[T]]`, `Expr[Expr[Expr[T]]]`, ...) then we talk about Multi-Stage +Programming. The motivation behind this _paradigm_ is to let runtime information affect or guide code-generation. @@ -22,11 +22,11 @@ guide code-generation. Intuition: The phase in which code is run is determined by the difference between the number of splice scopes and quote scopes in which it is embedded. - - If there are more splices than quotes, the code is run at "compile-time" i.e. + - If there are more splices than quotes, the code is run at compile-time i.e. as a macro. In the general case, this means running an interpreter that evaluates the code, which is represented as a typed abstract syntax tree. The interpreter can fall back to reflective calls when evaluating an application - of a previously compiled method. If the splice excess is more than one, it + of a previously compiled method. If the splice excess is more than one, it would mean that a macro’s implementation code (as opposed to the code it expands to) invokes other macros. If macros are realized by interpretation, this would lead to towers of interpreters, where the first interpreter would @@ -61,7 +61,7 @@ to be executed at a later stage. To run that code, there is another method in class `Expr` called `run`. Note that `$` and `run` both map from `Expr[T]` to `T` but only `$` is subject to the PCP, whereas `run` is just a normal method. Run provides a `QuoteContext` that can be used to show the expression in the scope of `run`. -On the other hand `withQuoteContext` provides a `QuoteContext` without evauating the expression. +On the other hand `withQuoteContext` provides a `QuoteContext` without evaluating the expression. ```scala package scala.quoted.staging @@ -81,12 +81,25 @@ From [lampepfl/dotty-staging.g8](https://github.com/lampepfl/dotty-staging.g8). It will create a project with the necessary dependencies and some examples. +In case you prefer to create the project on your own, make sure to define the following dependency in your build.sbt + +```scala +libraryDependencies += "ch.epfl.lamp" %% "dotty-staging" % scalaVersion.value +``` + +and in case you use `dotc`/`dotr` directly, then use the `-with-compiler` flag for both: + +```shell +dotc -with-compiler -d out Test.scala +dotr -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 generated code at run-time and pass +do not want to pass an array 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 4 below. Using `run { ... }` we can evaluate an +`Expr[Array[Int] => Int]` in line 6 below. Using `run { ... }` we can evaluate an expression at runtime. Within the scope of `run` we can also invoke `show` on an expression to get a source-like representation of the expression. @@ -104,24 +117,3 @@ val f: Array[Int] => Int = run { f.apply(Array(1, 2, 3)) // Returns 6 ``` - -Note that if we need to run the main (in an object called `Test`) after -compilation we need make available the compiler to the runtime: - -```shell -dotc -with-compiler -d out Test.scala -dotr -with-compiler -classpath out Test -``` - -Or, from SBT: - -```scala -libraryDependencies += "ch.epfl.lamp" %% "dotty-staging" % scalaVersion.value -``` - -## Template project -Using sbt version `1.1.5+`, do: -``` -sbt new lampepfl/dotty-staging.g8 -``` -in the folder where you want to clone the template. diff --git a/docs/docs/reference/new-types/dependent-function-types.md b/docs/docs/reference/new-types/dependent-function-types.md index dc1154cb8372..b7003d7c66fc 100644 --- a/docs/docs/reference/new-types/dependent-function-types.md +++ b/docs/docs/reference/new-types/dependent-function-types.md @@ -17,7 +17,7 @@ val extractor: (e: Entry) => e.Key = extractKey // a dependent function value ``` Scala already has _dependent methods_, i.e. methods where the result type refers to some of the parameters of the method. Method -`extractKey` is an example. Its result type, `e.Key` refers its +`extractKey` is an example. Its result type, `e.Key` refers to its parameter `e` (we also say, `e.Key` _depends_ on `e`). But so far it was not possible to turn such methods into function values, so that they can be passed as parameters to other functions, or returned as diff --git a/docs/docs/reference/other-new-features/creator-applications.md b/docs/docs/reference/other-new-features/creator-applications.md index 6f2c5a830008..ce461927bc20 100644 --- a/docs/docs/reference/other-new-features/creator-applications.md +++ b/docs/docs/reference/other-new-features/creator-applications.md @@ -13,13 +13,13 @@ class StringBuilder(s: String) { StringBuilder("abc") // same as new StringBuilder("abc") StringBuilder() // same as new StringBuilder() ``` -Creator applications generalize a functionality provided so far only for case classes, but the mechanism how this is achieved is different. Instead generating an apply method, the compiler adds a new possible interpretation to a function call `f(args)`. The previous rules are: +Creator applications generalize a functionality provided so far only for case classes, but the mechanism how this is achieved is different. Instead of generating an apply method, the compiler adds a new possible interpretation to a function call `f(args)`. The previous rules are: Given a function call `f(args)`, - if `f` is a method applicable to `args`, typecheck `f(args)` unchanged, - otherwise, if `f` has an `apply` method applicable to `args` as a member, continue with `f.apply(args)`, - - otherwise, if `f` is of the form `p.m` and there is an implicit conversion `c` applicable to `p` so that `c(p).m` is applicable to `args`, continue with `c(p).m(args)` + - otherwise, if `f` is of the form `p.m` and there is an implicit conversion `c` applicable to `p` so that `c(p).m` is applicable to `args`, continue with `c(p).m(args)` There's now a fourth rule following these rules: @@ -40,4 +40,4 @@ caused numerous problems, including - overloading ambiguities - overriding errors - - shadowing of user-defined `apply` methods by more specific auto-generated ones. \ No newline at end of file + - shadowing of user-defined `apply` methods by more specific auto-generated ones. diff --git a/docs/docs/reference/other-new-features/export.md b/docs/docs/reference/other-new-features/export.md index 481f255d966f..11ed1c55c229 100644 --- a/docs/docs/reference/other-new-features/export.md +++ b/docs/docs/reference/other-new-features/export.md @@ -58,7 +58,7 @@ of one of the following forms: A member is _eligible_ if all of the following holds: - - its owner is not a base class of the class(\*) containing the export clause, + - its owner is not a base class of the class[(\*)](#note_class) containing the export clause, - the member does not override a concrete definition that has as owner a base class of the class containing the export clause. - it is accessible at the export clause, @@ -87,6 +87,7 @@ def f: c.T = ... Export clauses can appear in classes or they can appear at the top-level. An export clause cannot appear as a statement in a block. + (\*) Note: Unless otherwise stated, the term "class" in this discussion also includes object and trait definitions. ### Motivation diff --git a/docs/docs/reference/other-new-features/parameter-untupling.md b/docs/docs/reference/other-new-features/parameter-untupling.md index 41d27b723ac5..c0765aa57350 100644 --- a/docs/docs/reference/other-new-features/parameter-untupling.md +++ b/docs/docs/reference/other-new-features/parameter-untupling.md @@ -31,5 +31,6 @@ function type of the form `((T_1, ..., T_n)) => U`. ### Reference For more info see: + * [More details](./parameter-untupling-spec.md) * [Issue #897](https://github.com/lampepfl/dotty/issues/897). diff --git a/docs/docs/reference/other-new-features/trait-parameters.md b/docs/docs/reference/other-new-features/trait-parameters.md index e625b6dd8ad0..16a6438173b4 100644 --- a/docs/docs/reference/other-new-features/trait-parameters.md +++ b/docs/docs/reference/other-new-features/trait-parameters.md @@ -26,7 +26,7 @@ class D extends C with Greeting("Bill") // error: parameter passed twice ``` Should this print "Bob" or "Bill"? In fact this program is illegal, -because it violates one of the following rules for trait parameters: +because it violates the second rule of the following for trait parameters: 1. If a class `C` extends a parameterized trait `T`, and its superclass does not, `C` _must_ pass arguments to `T`. diff --git a/library/src-non-bootstrapped/scala/internal/quoted/CompileTime.scala b/library/src-non-bootstrapped/scala/internal/quoted/CompileTime.scala deleted file mode 100644 index 2ecba1d6a9ba..000000000000 --- a/library/src-non-bootstrapped/scala/internal/quoted/CompileTime.scala +++ /dev/null @@ -1,48 +0,0 @@ -package scala.internal.quoted - -import scala.annotation.{Annotation, compileTimeOnly} -import scala.quoted._ - -@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime`") -object CompileTime { - - /** A term quote is desugared by the compiler into a call to this method */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.exprQuote`") - def exprQuote[T](x: T): QuoteContext ?=> Expr[T] = ??? - - /** A term splice is desugared by the compiler into a call to this method */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.exprSplice`") - def exprSplice[T](x: QuoteContext ?=> Expr[T]): T = ??? - - /** A type quote is desugared by the compiler into a call to this method */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.typeQuote`") - def typeQuote[T <: AnyKind]: Type[T] = ??? - - /** A splice in a quoted pattern is desugared by the compiler into a call to this method */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.patternHole`") - def patternHole[T]: T = ??? - - /** A splice of a name in a quoted pattern is desugared by wrapping getting this annotation */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.patternBindHole`") - class patternBindHole extends Annotation - - /** A splice of a name in a quoted pattern is that marks the definition of a type splice */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.patternType`") - class patternType extends Annotation - - /** A type pattern that must be aproximated from above */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.fromAbove`") - class fromAbove extends Annotation - - /** Artifact of pickled type splices - * - * During quote reification a quote `'{ ... F[$t] ... }` will be transformed into - * `'{ @quoteTypeTag type T$1 = $t ... F[T$1] ... }` to have a tree for `$t`. - * This artifact is removed during quote unpickling. - * - * See ReifyQuotes.scala and PickledQuotes.scala - */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.quoteTypeTag`") - class quoteTypeTag extends Annotation - -} diff --git a/library/src-bootstrapped/scala/internal/quoted/CompileTime.scala b/library/src/scala/internal/quoted/quoted/CompileTime.scala similarity index 100% rename from library/src-bootstrapped/scala/internal/quoted/CompileTime.scala rename to library/src/scala/internal/quoted/quoted/CompileTime.scala diff --git a/library/src/scala/quoted/util/ExprMap.scala b/library/src/scala/quoted/util/ExprMap.scala index 80f83a96c49e..7475fecdc8bf 100644 --- a/library/src/scala/quoted/util/ExprMap.scala +++ b/library/src/scala/quoted/util/ExprMap.scala @@ -64,9 +64,11 @@ trait ExprMap { val tp = tpt.tpe match // TODO improve code case AppliedType(TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), ""), List(tp0: Type)) => + // TODO rewrite without using quotes type T - val a = tp0.seal.asInstanceOf[quoted.Type[T]] - '[Seq[$a]].unseal.tpe + val qtp: quoted.Type[T] = tp0.seal.asInstanceOf[quoted.Type[T]] + given qtp.type = qtp + '[Seq[T]].unseal.tpe case tp => tp Typed.copy(tree)(transformTerm(expr, tp), transformTypeTree(tpt)) case tree: NamedArg => diff --git a/library/src/scala/quoted/util/Var.scala b/library/src/scala/quoted/util/Var.scala index 207766fd5ce1..de2325197182 100644 --- a/library/src/scala/quoted/util/Var.scala +++ b/library/src/scala/quoted/util/Var.scala @@ -17,6 +17,7 @@ object Var { /** Create a variable initialized with `init` and used in `body`. * `body` receives a `Var[T]` argument which exposes `get` and `update`. * + * ``` * Var('{7}) { * x => '{ * while(0 < ${x.get}) @@ -33,6 +34,7 @@ object Var { * x = x - 1 * x * } + * ``` */ def apply[T: Type, U: Type](init: Expr[T])(body: Var[T] => Expr[U])(using qctx: QuoteContext): Expr[U] = '{ var x = $init diff --git a/project/Build.scala b/project/Build.scala index 3de2076dc2df..40daa1ec6974 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -62,7 +62,7 @@ object MyScalaJSPlugin extends AutoPlugin { } object Build { - val referenceVersion = "0.22.0-RC1" + val referenceVersion = "0.23.0-bin-20200222-811dc19-NIGHTLY" val baseVersion = "0.23.0" val baseSbtDottyVersion = "0.4.0" diff --git a/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/build.sbt b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/build.sbt new file mode 100644 index 000000000000..108f70f83cca --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/build.sbt @@ -0,0 +1,12 @@ +val dottyVersion = "0.22.0-RC1" + +lazy val root = project + .in(file(".")) + .settings( + name := "dotty-simple", + version := "0.1.0", + + scalaVersion := dottyVersion, + + libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" + ) diff --git a/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/build.properties b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/build.properties new file mode 100644 index 000000000000..a919a9b5f46b --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.3.8 diff --git a/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/plugins.sbt b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/plugins.sbt new file mode 100644 index 000000000000..b7e929c23e19 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.4.0") diff --git a/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/src/main/scala/Main.scala b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/src/main/scala/Main.scala new file mode 100644 index 000000000000..e1e0b803d9b0 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/src/main/scala/Main.scala @@ -0,0 +1,4 @@ +@main +def testMethod(): Unit = { + println("Hello world!") +} diff --git a/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/test b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/test new file mode 100644 index 000000000000..62ea636c177f --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/test @@ -0,0 +1 @@ +> run diff --git a/tests/run-staging/quote-macro-in-splice/quoted_1.scala b/tests/disabled/run-staging/quote-macro-in-splice/quoted_1.scala similarity index 100% rename from tests/run-staging/quote-macro-in-splice/quoted_1.scala rename to tests/disabled/run-staging/quote-macro-in-splice/quoted_1.scala diff --git a/tests/run-staging/quote-macro-in-splice/quoted_2.scala b/tests/disabled/run-staging/quote-macro-in-splice/quoted_2.scala similarity index 100% rename from tests/run-staging/quote-macro-in-splice/quoted_2.scala rename to tests/disabled/run-staging/quote-macro-in-splice/quoted_2.scala diff --git a/tests/explicit-nulls/neg/override-java-object-arg.scala b/tests/explicit-nulls/neg/override-java-object-arg.scala index 7a45cb1d6199..ccce4af660c7 100644 --- a/tests/explicit-nulls/neg/override-java-object-arg.scala +++ b/tests/explicit-nulls/neg/override-java-object-arg.scala @@ -7,7 +7,7 @@ import javax.management.{Notification, NotificationEmitter, NotificationListener class Foo { def bar(): Unit = { - val listener = new NotificationListener() { + val listener = new NotificationListener() { // error: object creation impossible override def handleNotification(n: Notification|Null, emitter: Object): Unit = { // error: method handleNotification overrides nothing } } @@ -17,7 +17,7 @@ class Foo { } } - val listener3 = new NotificationListener() { + val listener3 = new NotificationListener() { // error: object creation impossible override def handleNotification(n: Notification, emitter: Object|Null): Unit = { // error: method handleNotification overrides nothing } } diff --git a/tests/neg-custom-args/erased/erased-case-class.scala b/tests/neg-custom-args/erased/erased-case-class.scala index 692534d772b6..d23a09ba24f0 100644 --- a/tests/neg-custom-args/erased/erased-case-class.scala +++ b/tests/neg-custom-args/erased/erased-case-class.scala @@ -1 +1 @@ -case class Foo1(erased x: Int) // error // error +case class Foo1(erased x: Int) // error diff --git a/tests/neg/i7048e.scala b/tests/neg/i7048e.scala index 610d0c7711e9..0b9a67fad9e7 100644 --- a/tests/neg/i7048e.scala +++ b/tests/neg/i7048e.scala @@ -9,7 +9,7 @@ abstract class Test { def foo(using QuoteContext): Expr[Any] = { - val r = '{Option.empty[T]} // error + val r = '{Option.empty[T]} // error: is not stable { val t: Test = this @@ -22,14 +22,14 @@ abstract class Test { { val r1 = '{Option.empty[${T}]} // works val r2 = '{Option.empty[List[${T}]]} // works - // val r3 = '{summon[Type[${T}]]} // access to Test.this from wrong staging level - val r4 = '{summon[${T} <:< Any]} // error + val r3 = '{summon[Type[${T}]]} // error: is not stable + val r4 = '{summon[${T} <:< Any]} // error: is not stable } { - val s = '{Option.empty[${T}]} + val s = '{Option.empty[${T}]} // works val r = '{identity($s)} // works - val r2 = '{identity(${s: Expr[Option[T]]})} // error // error + val r2 = '{identity(${s: Expr[Option[T]]})} // error // error : is not stable } r diff --git a/tests/neg/i7597.scala b/tests/neg/i7597.scala new file mode 100644 index 000000000000..c4c8f189fcfc --- /dev/null +++ b/tests/neg/i7597.scala @@ -0,0 +1,14 @@ +object Test extends App { + def foo[S <: String]: String => Int = + new (String => Int) { def apply(s: S): Int = 0 } // error + + trait Fn[A, B] { + def apply(x: A): B + } + + class C[S <: String] extends Fn[String, Int] { // error + def apply(s: S): Int = 0 + } + + foo("") +} diff --git a/tests/neg/i8052.scala b/tests/neg/i8052.scala new file mode 100644 index 000000000000..6f5cddb8b415 --- /dev/null +++ b/tests/neg/i8052.scala @@ -0,0 +1,19 @@ +import scala.deriving._ +import scala.quoted._ +import scala.quoted.matching._ + +object Macro2 { + trait TC[T] { + def test(): Unit + } + + object TC { + def derived[T: Type](ev: Expr[Mirror.Of[T]])(using qctx: QuoteContext): Expr[TC[T]] = '{ + new TC[T] { + def encode(): Unit = $ev match { + case '{ $m: Mirror.ProductOf[T] } => ??? // error + } + } + } + } +} diff --git a/tests/neg/i8333.scala b/tests/neg/i8333.scala new file mode 100644 index 000000000000..12a35ac2d636 --- /dev/null +++ b/tests/neg/i8333.scala @@ -0,0 +1,13 @@ +class A: + type T = Int // can also be class T +class B(x: A, y: A): + export x._ + export y._ // error: duplicate +class C(x: A): + type T = String + export x._ // error: duplicate +class D(x: A): + export x._ // error: duplicate + type T = String + + diff --git a/tests/neg/i8362.scala b/tests/neg/i8362.scala new file mode 100644 index 000000000000..c1b498fbbb9a --- /dev/null +++ b/tests/neg/i8362.scala @@ -0,0 +1,5 @@ +object Test { + inline def foo: String = scala.compiletime.error(s"") + + def main(args: Array[String]): Unit = println(foo) // error +} diff --git a/tests/neg/union.scala b/tests/neg/union.scala index c594e83d74bc..fc37b68291a4 100644 --- a/tests/neg/union.scala +++ b/tests/neg/union.scala @@ -3,26 +3,14 @@ object Test { class A class B extends A class C extends A - class D extends A val b = true - val x = if (b) new B else new C - val y: B | C = x // error -} - -object O { - class A - class B - def f[T](x: T, y: T): T = x - - val x: A = f(new A { }, new A) - - val y1: A | B = f(new A { }, new B) // error - val y2: A | B = f[A | B](new A { }, new B) // ok - val z = if (???) new A{} else new B + val x: B | C = if (b) new B else new C - val z1: A | B = z // error + val y: B | C = x + val ok: B | C = y // ok, as x and y are ascribed with unions - val z2: A | B = if (???) new A else new B // ok + val z = x + val error: B | C = z // error, as z is not ascribed with an union } diff --git a/tests/patmat/i8203.scala b/tests/patmat/i8203.scala new file mode 100644 index 000000000000..484ad2723454 --- /dev/null +++ b/tests/patmat/i8203.scala @@ -0,0 +1,19 @@ +sealed trait Pretty { self: Color => } +sealed trait Dull { self: Color => } +enum Color { + case Pink extends Color with Pretty + case Red extends Color with Dull +} + +def describe(c: Color) = c match { + case Color.Pink => "Amazing!" + case Color.Red => "Yawn..." +} + +def describe2(c: Pretty) = c match { + case Color.Pink => "Amazing!" +} + +def describe3(c: Dull) = c match { + case Color.Red => "Yawn..." +} diff --git a/tests/pos/i4867.scala b/tests/pos/i4867.scala new file mode 100644 index 000000000000..3c03340dd067 --- /dev/null +++ b/tests/pos/i4867.scala @@ -0,0 +1,15 @@ +object Main_i4867 { + + given extension [AB](ab: AB) { + def id: AB = ab + } + + def main(args : Array[String]) : Unit = { + val int : Int = 2 + val foundInt : Int = int.id + + val stringOrInt : String | Int = 1 + val foundAny : String | Int = stringOrInt.id + } + +} diff --git a/tests/pos/i7521.scala b/tests/pos/i7521.scala new file mode 100644 index 000000000000..3dfa925dedf5 --- /dev/null +++ b/tests/pos/i7521.scala @@ -0,0 +1,10 @@ +import scala.quoted._ +import scala.annotation.StaticAnnotation + +object Test { + inline def quote[T]: Unit = ${ quoteImpl[T] } + def quoteImpl[T: Type](using qctx: QuoteContext): Expr[Unit] = '{ + class Annot extends StaticAnnotation + var test: T @Annot = ??? + } +} diff --git a/tests/pos/i8052.scala b/tests/pos/i8052.scala new file mode 100644 index 000000000000..f6ac68657c47 --- /dev/null +++ b/tests/pos/i8052.scala @@ -0,0 +1,21 @@ +import scala.deriving._ +import scala.quoted._ +import scala.quoted.matching._ + +object Macro2 { + trait TC[T] { + def test(): Unit + } + + object TC { + def derived[T: Type](ev: Expr[Mirror.Of[T]])(using qctx: QuoteContext): Expr[TC[T]] = '{ + new TC[T] { + def encode(): Unit = ${ + ev match { + case '{ $m: Mirror.ProductOf[T] } => ??? + } + } + } + } + } +} diff --git a/tests/pos/i8302.scala b/tests/pos/i8302.scala new file mode 100644 index 000000000000..83ea6a285071 --- /dev/null +++ b/tests/pos/i8302.scala @@ -0,0 +1,8 @@ +import scala.quoted._ +def foo[T](using qctx: QuoteContext, tpe: Type[T]): Expr[Any] = + '{ + type TT = T + val t = '[TT] + ??? + } + diff --git a/tests/pos/union.scala b/tests/pos/union.scala new file mode 100644 index 000000000000..bdec7d46277c --- /dev/null +++ b/tests/pos/union.scala @@ -0,0 +1,27 @@ +object TestUnion { + + class A + class B extends A + class C extends A + class D extends A + + val b = true + val x: B | C = if (b) new B else new C + val y: B | C = x +} + +object TestUnion2 { + class A + class B + def f[T](x: T, y: T): T = x + + val x: A = f(new A { }, new A) + + val y1: A | B = f(new A { }, new B) + val y2: A | B = f[A | B](new A { }, new B) + + val z: A | B = if (???) new A{} else new B + + val z1: A | B = z + val z2: A | B = if (???) new A else new B +} diff --git a/tests/pos/using-quote-context.scala b/tests/pos/using-quote-context.scala new file mode 100644 index 000000000000..51205190ecbd --- /dev/null +++ b/tests/pos/using-quote-context.scala @@ -0,0 +1,6 @@ +import scala.quoted._ + +class Test { + def fold[W: Type](s: Expr[W]): QuoteContext ?=> Expr[W] = + '{ ???; $s } +} diff --git a/tests/run-staging/i5247.check b/tests/run-staging/i5247.check index 63524be90ce2..620b21ddf88d 100644 --- a/tests/run-staging/i5247.check +++ b/tests/run-staging/i5247.check @@ -1,2 +1,2 @@ null.asInstanceOf[java.lang.Object] -null.asInstanceOf[scala.List[java.lang.Object]] +null.asInstanceOf[scala.collection.immutable.List[java.lang.Object]] diff --git a/tests/run-staging/quote-owners-2.check b/tests/run-staging/quote-owners-2.check index fc038fc486a3..323ce64b7bc7 100644 --- a/tests/run-staging/quote-owners-2.check +++ b/tests/run-staging/quote-owners-2.check @@ -1,7 +1,7 @@ { def ff: scala.Int = { - val a: scala.List[scala.Int] = { - type T = scala.List[scala.Int] + val a: scala.collection.immutable.List[scala.Int] = { + type T = scala.collection.immutable.List[scala.Int] val b: T = scala.Nil.::[scala.Int](3) (b: scala.collection.immutable.List[scala.Int])