diff --git a/.gitignore b/.gitignore index ba47c2dd4cb3..3cdfd112b86d 100644 --- a/.gitignore +++ b/.gitignore @@ -89,5 +89,9 @@ community-build/sbt-scalajs-sbt *.check.out !/dist/bin/ +# semanticdb test output files +*.expect.scala.out +*.expect.out + # Bloop .bsp diff --git a/bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala b/bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala index fa6fb63e73ad..ac96509d233d 100644 --- a/bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala +++ b/bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala @@ -50,7 +50,7 @@ class TupleOps { def tupleMergeSort(tuple: Tuple): Tuple = if (tuple.size <= 1) tuple else { - val (tuple1, tuple2) = tuple.splitAt(tuple.size / 2) + val (tuple1, tuple2) = tuple.splitAt(tuple.size / 2): (Tuple, Tuple)// TODO remove ascriptions (issue with type variable constraints) val (sorted1, sorted2) = (tupleMergeSort(tuple1), tupleMergeSort(tuple2)) tupleMerge(sorted1, sorted2) } diff --git a/community-build/community-projects/izumi-reflect b/community-build/community-projects/izumi-reflect index c368dcc9025b..54051d0bca92 160000 --- a/community-build/community-projects/izumi-reflect +++ b/community-build/community-projects/izumi-reflect @@ -1 +1 @@ -Subproject commit c368dcc9025b455944e797dffa1b78d3ecf83752 +Subproject commit 54051d0bca921706ef0a3f9f63264f6f57d50ef0 diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index 69e29ed19e6f..e58ec52f69df 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit 69e29ed19e6fe12e9acf6f748d0693bdd957cfd0 +Subproject commit e58ec52f69df080972a93386f849f33f18e83596 diff --git a/community-build/community-projects/shapeless b/community-build/community-projects/shapeless index 3058735a54a2..bfa5dba99d64 160000 --- a/community-build/community-projects/shapeless +++ b/community-build/community-projects/shapeless @@ -1 +1 @@ -Subproject commit 3058735a54a23df67246ecce5b09f6a6cd3dfaec +Subproject commit bfa5dba99d6402336a1fbaf687f5cb29b3e78fe2 diff --git a/community-build/community-projects/utest b/community-build/community-projects/utest index 654376f41f83..f1ca7b12897f 160000 --- a/community-build/community-projects/utest +++ b/community-build/community-projects/utest @@ -1 +1 @@ -Subproject commit 654376f41f8380a44fdb70f693016f25633ae1be +Subproject commit f1ca7b12897f0fedccc757c17552ebbade72c6a2 diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 657ef01c3bb3..a4f219c88510 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -38,15 +38,26 @@ class CompilationUnit protected (val source: SourceFile) { */ val freshNames: FreshNameCreator = new FreshNameCreator.Default + /** Will be set to `true` if there are inline call that must be inlined after typer. + * The information is used in phase `Inlining` in order to avoid traversing trees that need no transformations. + */ + var needsInlining: Boolean = false + /** Will be set to `true` if contains `Quote`. * The information is used in phase `Staging` in order to avoid traversing trees that need no transformations. */ var needsStaging: Boolean = false + /** Will be set to `true` if contains `Quote` that needs to be pickled + * The information is used in phase `PickleQuotes` in order to avoid traversing trees that need no transformations. + */ + var needsQuotePickling: Boolean = false + /** A structure containing a temporary map for generating inline accessors */ val inlineAccessors: InlineAccessors = new InlineAccessors var suspended: Boolean = false + var suspendedAtInliningPhase: Boolean = false /** Can this compilation unit be suspended */ def isSuspendable: Boolean = true @@ -61,6 +72,8 @@ class CompilationUnit protected (val source: SourceFile) { report.echo(i"suspended: $this") suspended = true ctx.run.suspendedUnits += this + if ctx.phase == Phases.inliningPhase then + suspendedAtInliningPhase = true throw CompilationUnit.SuspendException() private var myAssignmentSpans: Map[Int, List[Span]] = null @@ -90,7 +103,9 @@ object CompilationUnit { if (forceTrees) { val force = new Force force.traverse(unit1.tpdTree) - unit1.needsStaging = force.needsStaging + unit1.needsStaging = force.containsQuote + unit1.needsQuotePickling = force.containsQuote + unit1.needsInlining = force.containsInline } unit1 } @@ -116,10 +131,13 @@ object CompilationUnit { /** Force the tree to be loaded */ private class Force extends TreeTraverser { - var needsStaging = false + var containsQuote = false + var containsInline = false def traverse(tree: Tree)(using Context): Unit = { if (tree.symbol.isQuote) - needsStaging = true + containsQuote = true + if tree.symbol.is(Flags.Inline) then + containsInline = true traverseChildren(tree) } } diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index f24df22d673d..e1fa879df834 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -50,6 +50,7 @@ class Compiler { /** Phases dealing with TASTY tree pickling and unpickling */ protected def picklerPhases: List[List[Phase]] = List(new Pickler) :: // Generate TASTY info + List(new Inlining) :: // Inline and execute macros List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures Nil @@ -61,6 +62,7 @@ class Compiler { new CookComments, // Cook the comments: expand variables, doc, etc. new CheckStatic, // Check restrictions that apply to @static members new BetaReduce, // Reduce closure applications + new InlineVals, // Check right hand-sides of an `inline val`s new ExpandSAMs, // Expand single abstract method closures to anonymous classes new init.Checker) :: // Check initialization of objects List(new ElimRepeated, // Rewrite vararg parameters and arguments diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 4ee8475b41d0..6b51908c37d7 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -101,11 +101,27 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint */ def units: List[CompilationUnit] = myUnits - var suspendedUnits: mutable.ListBuffer[CompilationUnit] = mutable.ListBuffer() - private def units_=(us: List[CompilationUnit]): Unit = myUnits = us + var suspendedUnits: mutable.ListBuffer[CompilationUnit] = mutable.ListBuffer() + + def checkSuspendedUnits(newUnits: List[CompilationUnit])(using Context): Unit = + if newUnits.isEmpty && suspendedUnits.nonEmpty && !ctx.reporter.errorsReported then + val where = + if suspendedUnits.size == 1 then i"in ${suspendedUnits.head}." + else i"""among + | + | ${suspendedUnits.toList}%, % + |""" + val enableXprintSuspensionHint = + if ctx.settings.XprintSuspension.value then "" + else "\n\nCompiling with -Xprint-suspension gives more information." + report.error(em"""Cyclic macro dependencies $where + |Compilation stopped since no further progress can be made. + | + |To fix this, place macros in one set of files and their callers in another.$enableXprintSuspensionHint""") + /** The files currently being compiled (active or suspended). * This may return different results over time. * These files do not have to be source files since it's possible to compile diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 3c039eb2212a..2abe3a750079 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -227,6 +227,8 @@ class ScalaSettings extends Settings.SettingGroup with CommonScalaSettings { val Yinstrument: Setting[Boolean] = BooleanSetting("-Yinstrument", "Add instrumentation code that counts allocations and closure creations.") val YinstrumentDefs: Setting[Boolean] = BooleanSetting("-Yinstrument-defs", "Add instrumentation code that counts method calls; needs -Yinstrument to be set, too.") + val YforceInlineWhileTyping: Setting[Boolean] = BooleanSetting("-Yforce-inline-while-typing", "Make non-transparent inline methods inline when typing. Emulates the old inlining behavior of 3.0.0-M3.") + /** Dottydoc specific settings that are not used in scaladoc */ val docSnapshot: Setting[Boolean] = BooleanSetting("-doc-snapshot", "Generate a documentation snapshot for the current Dotty version") diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 864fcd79fe69..a42493fad2f1 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -122,4 +122,9 @@ object Mode { /** Are we enforcing null safety */ val SafeNulls = newMode(28, "SafeNulls") + + /** We are typing the body of the condition of an `inline if` or the scrutinee of an `inline match` + * This mode forces expansion of inline calls in those positions even during typing. + */ + val ForceInline: Mode = newMode(29, "ForceInline") } diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 5c0725e17c4b..4646751192b4 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -198,6 +198,7 @@ object Phases { private var myPostTyperPhase: Phase = _ private var mySbtExtractDependenciesPhase: Phase = _ private var myPicklerPhase: Phase = _ + private var myInliningPhase: Phase = _ private var myPickleQuotesPhase: Phase = _ private var myFirstTransformPhase: Phase = _ private var myCollectNullableFieldsPhase: Phase = _ @@ -218,6 +219,7 @@ object Phases { final def postTyperPhase: Phase = myPostTyperPhase final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase final def picklerPhase: Phase = myPicklerPhase + final def inliningPhase: Phase = myInliningPhase final def pickleQuotesPhase: Phase = myPickleQuotesPhase final def firstTransformPhase: Phase = myFirstTransformPhase final def collectNullableFieldsPhase: Phase = myCollectNullableFieldsPhase @@ -241,6 +243,7 @@ object Phases { myPostTyperPhase = phaseOfClass(classOf[PostTyper]) mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies]) myPicklerPhase = phaseOfClass(classOf[Pickler]) + myInliningPhase = phaseOfClass(classOf[Inlining]) myPickleQuotesPhase = phaseOfClass(classOf[PickleQuotes]) myFirstTransformPhase = phaseOfClass(classOf[FirstTransform]) myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields]) @@ -406,6 +409,7 @@ object Phases { def postTyperPhase(using Context): Phase = ctx.base.postTyperPhase def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase def picklerPhase(using Context): Phase = ctx.base.picklerPhase + def inliningPhase(using Context): Phase = ctx.base.inliningPhase def pickleQuotesPhase(using Context): Phase = ctx.base.pickleQuotesPhase def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 863909066161..cf8b3e01822a 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -77,10 +77,11 @@ class ExtractAPI extends Phase { } finally pw.close() } - if (ctx.sbtCallback != null) { + if ctx.sbtCallback != null && + !ctx.compilationUnit.suspendedAtInliningPhase // already registered before this unit was suspended + then classes.foreach(ctx.sbtCallback.api(sourceFile.file, _)) mainClasses.foreach(ctx.sbtCallback.mainClass(sourceFile.file, _)) - } } } diff --git a/compiler/src/dotty/tools/dotc/transform/InlineVals.scala b/compiler/src/dotty/tools/dotc/transform/InlineVals.scala new file mode 100644 index 000000000000..0a7d6a9e7a07 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/InlineVals.scala @@ -0,0 +1,43 @@ +package dotty.tools +package dotc +package transform + +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.typer.Inliner + +/** Check that `tree.rhs` can be right hand-side of an `inline` value definition. */ +class InlineVals extends MiniPhase: + import ast.tpd._ + + def phaseName: String = "inlineVals" + + override def checkPostCondition(tree: Tree)(using Context): Unit = + if !ctx.erasedTypes then + tree match + case tree: ValDef => checkInlineConformant(tree) + case _ => + + override def transformValDef(tree: ValDef)(using Context): Tree = + checkInlineConformant(tree) + tree + + /** Check that `tree.rhs` can be right hand-side of an `inline` value definition. */ + private def checkInlineConformant(tree: ValDef)(using Context): Unit = { + if tree.symbol.is(Inline, butNot = DeferredOrTermParamOrAccessor) + && !Inliner.inInlineMethod + then + val rhs = tree.rhs + val tpt = tree.tpt + tpt.tpe.widenTermRefExpr.dealias.normalized match + case tp: ConstantType => + if !isPureExpr(rhs) then + val details = if enclosingInlineds.isEmpty then "" else em"but was: $rhs" + report.error(s"inline value must be pure$details", rhs.srcPos) + case _ => + val pos = if tpt.span.isZeroExtent then rhs.srcPos else tpt.srcPos + report.error(em"inline value must have a literal constant type", pos) + } \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index 70895a647e39..5db2afdacd8d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -33,11 +33,22 @@ import scala.annotation.constructorOnly /** Inlines all calls to inline methods that are not in an inline method or a quote */ class Inlining extends MacroTransform { import tpd._ + import Inlining._ override def phaseName: String = Inlining.name override def allowsImplicitSearch: Boolean = true + override def run(using Context): Unit = + if ctx.compilationUnit.needsInlining then + try super.run + catch case _: CompilationUnit.SuspendException => () + + override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = + val newUnits = super.runOn(units).filterNot(_.suspended) + ctx.run.checkSuspendedUnits(newUnits) + newUnits + override def checkPostCondition(tree: Tree)(using Context): Unit = tree match { case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass => @@ -69,13 +80,13 @@ class Inlining extends MacroTransform { else super.transform(tree) case _: Typed | _: Block => super.transform(tree) - case _ if Inliner.isInlineable(tree) && !tree.tpe.widen.isInstanceOf[MethodOrPoly] && StagingContext.level == 0 => + case _ if Inliner.needsInlining(tree) => val tree1 = super.transform(tree) - val inlined = Inliner.inlineCall(tree1) - if tree1 eq inlined then inlined - else transform(inlined) + if tree1.tpe.isError then tree1 + else Inliner.inlineCall(tree1) case _: GenericApply if tree.symbol.isQuote => - ctx.compilationUnit.needsStaging = true + if level == 0 then + ctx.compilationUnit.needsQuotePickling = true super.transform(tree)(using StagingContext.quoteContext) case _: GenericApply if tree.symbol.isExprSplice => super.transform(tree)(using StagingContext.spliceContext) @@ -85,6 +96,5 @@ class Inlining extends MacroTransform { } } -object Inlining { +object Inlining: val name: String = "inlining" -} diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 0f00add41809..e0f0858652cf 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -88,7 +88,7 @@ class PickleQuotes extends MacroTransform { } override def run(using Context): Unit = - if (ctx.compilationUnit.needsStaging) super.run(using freshStagingContext) + if (ctx.compilationUnit.needsQuotePickling) super.run(using freshStagingContext) protected def newTransformer(using Context): Transformer = new Transformer { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 09806b2b1679..bc69b18e541b 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -258,12 +258,16 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase override def transform(tree: Tree)(using Context): Tree = try tree match { case tree: Ident if !tree.isType => + if tree.symbol.is(Inline) && !Inliner.inInlineMethod then + ctx.compilationUnit.needsInlining = true checkNoConstructorProxy(tree) tree.tpe match { case tpe: ThisType => This(tpe.cls).withSpan(tree.span) case _ => tree } case tree @ Select(qual, name) => + if tree.symbol.is(Inline) then + ctx.compilationUnit.needsInlining = true if (name.isTypeName) { Checking.checkRealizable(qual.tpe, qual.srcPos) withMode(Mode.Type)(super.transform(tree)) @@ -302,6 +306,9 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case tree: TypeApply => if tree.symbol.isQuote then ctx.compilationUnit.needsStaging = true + ctx.compilationUnit.needsQuotePickling = true + if tree.symbol.is(Inline) then + ctx.compilationUnit.needsInlining = true val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) args.foreach(checkInferredWellFormed) if (fn.symbol != defn.ChildAnnot.primaryConstructor) diff --git a/compiler/src/dotty/tools/dotc/transform/TupleOptimizations.scala b/compiler/src/dotty/tools/dotc/transform/TupleOptimizations.scala index d208c19c5c52..73e75597c2b3 100644 --- a/compiler/src/dotty/tools/dotc/transform/TupleOptimizations.scala +++ b/compiler/src/dotty/tools/dotc/transform/TupleOptimizations.scala @@ -173,7 +173,7 @@ class TupleOptimizations extends MiniPhase with IdentityDenotTransformer { val size = tpes.size if (size == 0) // Array.emptyObjectArray - ref(defn.ArrayModule).select("emptyObjectArray".toTermName).ensureApplied + ref(defn.ArrayModule).select("emptyObjectArray".toTermName).ensureApplied.withSpan(tree.span) else if (size <= MaxTupleArity) // scala.runtime.Tuples.productToArray(tup.asInstanceOf[Product]) ref(defn.RuntimeTuples_productToArray).appliedTo(tup.asInstance(defn.ProductClass.typeRef)) diff --git a/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala b/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala index 3f5da6bab7e7..44655e3bead3 100644 --- a/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala +++ b/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala @@ -27,11 +27,12 @@ class YCheckPositions extends Phase { // Check current context is correct assert(ctx.source == sources.head) - if (!tree.isEmpty && !tree.isInstanceOf[untpd.TypedSplice] && ctx.typerState.isGlobalCommittable) - if (!tree.isType) { // TODO also check types, currently we do not add Inlined(EmptyTree, _, _) for types. We should. + if (!tree.isEmpty && !tree.isInstanceOf[untpd.TypedSplice] && !tree.isInstanceOf[Inlined] && ctx.typerState.isGlobalCommittable) + if !tree.isType // TODO also check types, currently we do not add Inlined(EmptyTree, _, _) for types. We should. + && !tree.symbol.is(InlineProxy) // TODO check inline proxies (see tests/tun/lst) + then val currentSource = sources.head - assert(tree.source == currentSource, i"wrong source set for $tree # ${tree.uniqueId} of ${tree.getClass}, set to ${tree.source} but context had $currentSource") - } + assert(tree.source == currentSource, i"wrong source set for $tree # ${tree.uniqueId} of ${tree.getClass}, set to ${tree.source} but context had $currentSource\n ${tree.symbol.flagsString}") // Recursivlely check children while keeping track of current source tree match { @@ -42,7 +43,7 @@ class YCheckPositions extends Phase { traverse(expansion)(using inlineContext(EmptyTree).withSource(sources.head)) sources = old case Inlined(call, bindings, expansion) => - bindings.foreach(traverse(_)) + // bindings.foreach(traverse(_)) // TODO check inline proxies (see tests/tun/lst) sources = call.symbol.topLevelClass.source :: sources if (!isMacro(call)) // FIXME macro implementations can drop Inlined nodes. We should reinsert them after macro expansion based on the positions of the trees traverse(expansion)(using inlineContext(call).withSource(sources.head)) @@ -55,8 +56,10 @@ class YCheckPositions extends Phase { } private def isMacro(call: Tree)(using Context) = - if (ctx.phase <= postTyperPhase) call.symbol.is(Macro) - else call.isInstanceOf[Select] // The call of a macro after typer is encoded as a Select while other inlines are Ident - // TODO remove this distinction once Inline nodes of expanded macros can be trusted (also in Inliner.inlineCallTrace) + call.symbol.is(Macro) || + // The call of a macro after typer is encoded as a Select while other inlines are Ident + // TODO remove this distinction once Inline nodes of expanded macros can be trusted (also in Inliner.inlineCallTrace) + (!(ctx.phase <= postTyperPhase) && call.isInstanceOf[Select]) + } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 32bcbc0aab4b..dfe81c3b85b3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -899,18 +899,6 @@ trait Checking { } } - /** Check that `tree` can be right hand-side or argument to `inline` value or parameter. */ - def checkInlineConformant(tpt: Tree, tree: Tree, sym: Symbol)(using Context): Unit = { - if sym.is(Inline, butNot = DeferredOrTermParamOrAccessor) && !ctx.erasedTypes && !Inliner.inInlineMethod then - tpt.tpe.widenTermRefExpr.dealias.normalized match - case tp: ConstantType => - if !(exprPurity(tree) >= Pure) then - report.error(em"inline value must be pure", tree.srcPos) - case _ => - val pos = if tpt.span.isZeroExtent then tree.srcPos else tpt.srcPos - report.error(em"inline value must have a literal constant type", pos) - } - /** A hook to exclude selected symbols from double declaration check */ def excludeFromDoubleDeclCheck(sym: Symbol)(using Context): Boolean = false @@ -1306,7 +1294,6 @@ trait NoChecking extends ReChecking { override def checkImplicitConversionUseOK(tree: Tree)(using Context): Unit = () override def checkFeasibleParent(tp: Type, pos: SrcPos, where: => String = "")(using Context): Type = tp override def checkAnnotArgs(tree: Tree)(using Context): tree.type = tree - override def checkInlineConformant(tpt: Tree, tree: Tree, sym: Symbol)(using Context): Unit = () override def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(using Context): Unit = () override def checkSimpleKinded(tpt: Tree)(using Context): Tree = tpt diff --git a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala index 87307115bcda..0f51b19ddf91 100644 --- a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala +++ b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala @@ -122,21 +122,7 @@ class FrontEnd extends Phase { unitContexts.foreach(javaCheck(using _)) // after typechecking to avoid cycles val newUnits = unitContexts.map(_.compilationUnit).filterNot(discardAfterTyper) - val suspendedUnits = ctx.run.suspendedUnits - if newUnits.isEmpty && suspendedUnits.nonEmpty && !ctx.reporter.errorsReported then - val where = - if suspendedUnits.size == 1 then i"in ${suspendedUnits.head}." - else i"""among - | - | ${suspendedUnits.toList}%, % - |""" - val enableXprintSuspensionHint = - if ctx.settings.XprintSuspension.value then "" - else "\n\nCompiling with -Xprint-suspension gives more information." - report.error(em"""Cyclic macro dependencies $where - |Compilation stopped since no further progress can be made. - | - |To fix this, place macros in one set of files and their callers in another.$enableXprintSuspensionHint""") + ctx.run.checkSuspendedUnits(newUnits) newUnits def run(using Context): Unit = unsupported("run") diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 79e1b739b542..875439675b0d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -43,7 +43,7 @@ object Inliner { * @pre hasBodyToInline(sym) */ def bodyToInline(sym: SymDenotation)(using Context): Tree = - if (sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot)) + if hasBodyToInline(sym) then sym.getAnnotation(defn.BodyAnnot).get.tree else EmptyTree @@ -52,16 +52,29 @@ object Inliner { def inInlineMethod(using Context): Boolean = ctx.owner.ownersIterator.exists(_.isInlineMethod) - /** Should call to method `meth` be inlined in this context? */ + /** Can a call to method `meth` be inlined? */ def isInlineable(meth: Symbol)(using Context): Boolean = meth.is(Inline) && meth.hasAnnotation(defn.BodyAnnot) && !inInlineMethod /** Should call be inlined in this context? */ - def isInlineable(tree: Tree)(using Context): Boolean = tree match { - case Block(_, expr) => isInlineable(expr) - case _ => isInlineable(tree.symbol) && !tree.tpe.isInstanceOf[MethodOrPoly] + def needsInlining(tree: Tree)(using Context): Boolean = tree match { + case Block(_, expr) => needsInlining(expr) + case _ => + isInlineable(tree.symbol) + && !tree.tpe.widenTermRefExpr.isInstanceOf[MethodOrPoly] + && StagingContext.level == 0 + && ( + ctx.phase == Phases.inliningPhase + || (ctx.phase == Phases.typerPhase && needsTransparentInlining(tree)) + ) + && !ctx.typer.hasInliningErrors } + private def needsTransparentInlining(tree: Tree)(using Context): Boolean = + tree.symbol.is(Transparent) + || ctx.mode.is(Mode.ForceInline) + || ctx.settings.YforceInlineWhileTyping.value + /** Try to inline a call to an inline method. Fail with error if the maximal * inline depth is exceeded. * @@ -283,11 +296,17 @@ object Inliner { assert(tree.symbol == defn.CompiletimeTesting_typeChecks || tree.symbol == defn.CompiletimeTesting_typeCheckErrors) def stripTyped(t: Tree): Tree = t match { case Typed(t2, _) => stripTyped(t2) + case Block(Nil, t2) => stripTyped(t2) + case Inlined(_, Nil, t2) => stripTyped(t2) case _ => t } val Apply(_, codeArg :: Nil) = tree - val underlyingCodeArg = stripTyped(codeArg.underlying) + val codeArg1 = stripTyped(codeArg.underlying) + val underlyingCodeArg = + if Inliner.isInlineable(codeArg1.symbol) then stripTyped(Inliner.inlineCall(codeArg1)) + else codeArg1 + ConstFold(underlyingCodeArg).tpe.widenTermRefExpr match { case ConstantType(Constant(code: String)) => val source2 = SourceFile.virtual("tasty-reflect", code) @@ -303,7 +322,7 @@ object Inliner { res ++= typerErrors.map(e => ErrorKind.Typer -> e) res.toList case t => - report.error("argument to compileError must be a statically known String", underlyingCodeArg.srcPos) + report.error(em"argument to compileError must be a statically known String but was: $codeArg", codeArg1.srcPos) Nil } @@ -327,7 +346,7 @@ object Inliner { /** Expand call to scala.compiletime.testing.typeChecks */ def typeChecks(tree: Tree)(using Context): Tree = val errors = compileForErrors(tree, true) - Literal(Constant(errors.isEmpty)) + Literal(Constant(errors.isEmpty)).withSpan(tree.span) /** Expand call to scala.compiletime.testing.typeCheckErrors */ def typeCheckErrors(tree: Tree)(using Context): Tree = @@ -941,7 +960,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { } } - def tryInline(tree: Tree)(using Context): Tree = tree match { + def tryInlineArg(tree: Tree)(using Context): Tree = tree match { case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs") rhs @@ -1236,7 +1255,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { } override def typedIdent(tree: untpd.Ident, pt: Type)(using Context): Tree = - tryInline(tree.asInstanceOf[tpd.Tree]) `orElse` super.typedIdent(tree, pt) + inlineIfNeeded(tryInlineArg(tree.asInstanceOf[tpd.Tree]) `orElse` super.typedIdent(tree, pt)) override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { assert(tree.hasType, tree) @@ -1248,11 +1267,12 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { else val res = resMaybeReduced ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.srcPos) - res + inlineIfNeeded(res) } override def typedIf(tree: untpd.If, pt: Type)(using Context): Tree = - typed(tree.cond, defn.BooleanType) match { + val condCtx = if tree.isInline then ctx.addMode(Mode.ForceInline) else ctx + typed(tree.cond, defn.BooleanType)(using condCtx) match { case cond1 @ ConstantValue(b: Boolean) => val selected0 = if (b) tree.thenp else tree.elsep val selected = if (selected0.isEmpty) tpd.Literal(Constant(())) else typed(selected0, pt) @@ -1278,14 +1298,30 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { super.typedValDef(vdef1, sym) override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = - constToLiteral(betaReduce(super.typedApply(tree, pt))) match { + val res = constToLiteral(betaReduce(super.typedApply(tree, pt))) match { case res: Apply if res.symbol == defn.QuotedRuntime_exprSplice && level == 0 - && !suppressInline => + && !hasInliningErrors => val expanded = expandMacro(res.args.head, tree.span) typedExpr(expanded) // Inline calls and constant fold code generated by the macro - case res => res + case res => + inlineIfNeeded(res) } + if res.symbol == defn.QuotedRuntime_exprQuote then + ctx.compilationUnit.needsQuotePickling = true + res + + override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = + inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt)))) + + override def typedMatch(tree: untpd.Match, pt: Type)(using Context): Tree = + val tree1 = + if tree.isInline then + // TODO this might not be useful if we do not support #11291 + val sel1 = typedExpr(tree.selector)(using ctx.addMode(Mode.ForceInline)) + untpd.cpy.Match(tree)(sel1, tree.cases) + else tree + super.typedMatch(tree1, pt) override def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(using Context) = if (!tree.isInline || ctx.owner.isInlineMethod) // don't reduce match of nested inline method yet @@ -1332,9 +1368,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { override def newLikeThis: Typer = new InlineTyper(initialErrorCount) - /** Suppress further inlining if this inline typer has already issued errors */ - override def suppressInline(using Context) = - ctx.reporter.errorCount > initialErrorCount || super.suppressInline + /** True if this inline typer has already issued errors */ + override def hasInliningErrors(using Context) = ctx.reporter.errorCount > initialErrorCount + + private def inlineIfNeeded(tree: Tree)(using Context): Tree = + if Inliner.needsInlining(tree) then Inliner.inlineCall(tree) + else tree + } /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 0d34ffe1320e..8ff0dfc8c61b 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -118,7 +118,7 @@ class ReTyper extends Typer with ReChecking { try super.typedUnadapted(tree, pt, locked) catch { case NonFatal(ex) => - if (ctx.isAfterTyper) + if ctx.phase != Phases.typerPhase && ctx.phase != Phases.inliningPhase then println(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}") throw ex } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 7bd34bf8a414..3063d6ea17af 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -865,9 +865,9 @@ class Typer extends Namer */ val arg1 = pt match { case AppliedType(a, typ :: Nil) if ctx.isJava && a.isRef(defn.ArrayClass) => - tryAlternatively { typed(tree.arg, pt) } { + tryAlternatively { typed(tree.arg, pt) } { val elemTp = untpd.TypedSplice(TypeTree(typ)) - typed(untpd.JavaSeqLiteral(tree.arg :: Nil, elemTp), pt) + typed(untpd.JavaSeqLiteral(tree.arg :: Nil, elemTp), pt) } case _ => typed(tree.arg, pt) } @@ -2024,7 +2024,6 @@ class Typer extends Namer } val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) checkSignatureRepeatedParam(sym) - checkInlineConformant(tpt1, rhs1, sym) vdef1.setDefTree } @@ -3407,13 +3406,12 @@ class Typer extends Namer val meth = methPart(tree).symbol if meth.isAllOf(DeferredInline) && !Inliner.inInlineMethod then errorTree(tree, i"Deferred inline ${meth.showLocated} cannot be invoked") - else if (Inliner.isInlineable(tree) && !suppressInline && StagingContext.level == 0) { + else if Inliner.needsInlining(tree) then tree.tpe <:< wildApprox(pt) val errorCount = ctx.reporter.errorCount val inlined = Inliner.inlineCall(tree) if ((inlined ne tree) && errorCount == ctx.reporter.errorCount) readaptSimplified(inlined) else inlined - } else if (tree.symbol.isScala2Macro && // `raw`, `f` and `s` are eliminated by the StringInterpolatorOpt phase tree.symbol != defn.StringContext_raw && @@ -3731,8 +3729,8 @@ class Typer extends Namer } } - // Overridden in InlineTyper - def suppressInline(using Context): Boolean = ctx.isAfterTyper + /** True if this inline typer has already issued errors */ + def hasInliningErrors(using Context): Boolean = false /** Does the "contextuality" of the method type `methType` match the one of the prototype `pt`? * This is the case if diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 263fc51f8ba7..134f7e8507b8 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -76,6 +76,10 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object reflect extends reflectModule: + object CompilationInfo extends CompilationInfoModule: + def isWhileTyping: Boolean = !ctx.isAfterTyper + end CompilationInfo + extension (expr: Expr[Any]) def asTerm: Term = val exprImpl = expr.asInstanceOf[ExprImpl] diff --git a/compiler/test-resources/repl/i5218 b/compiler/test-resources/repl/i5218 index abe63009ef74..e99590d0315d 100644 --- a/compiler/test-resources/repl/i5218 +++ b/compiler/test-resources/repl/i5218 @@ -3,4 +3,4 @@ val tuple: (Int, String, Long) = (1,2,3) scala> 0.0 *: tuple val res0: (Double, Int, String, Long) = (0.0,1,2,3) scala> tuple ++ tuple -val res1: Int *: String *: Long *: tuple.type = (1,2,3,1,2,3) +val res1: Int *: scala.Tuple.Concat[(String, Long), tuple.type] = (1,2,3,1,2,3) diff --git a/compiler/test/dotc/run-from-tasty.blacklist b/compiler/test/dotc/run-from-tasty.blacklist index e69de29bb2d1..2c483e9e34b6 100644 --- a/compiler/test/dotc/run-from-tasty.blacklist +++ b/compiler/test/dotc/run-from-tasty.blacklist @@ -0,0 +1,2 @@ +# CI only: cannot reduce summonFrom with +sip23-valueof.scala diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index aefe3b14b017..d36314e896d8 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -1,30 +1,35 @@ +derive-generic.scala eff-dependent.scala +enum-java +i5257.scala +i7212 +i7868.scala +i9011.scala +i9473.scala +macros-in-same-project1 +mixin-forwarder-overload +t10889 t3452d t3452e t3452g t7374 -tuples1.scala -tuples1a.scala -tuples1b.scala +t8905 +tuple-drop.scala +tuple-ops.scala tuple-ops.scala tuple-take.scala -tuple-drop.scala tuple-zip.scala +tuples1.scala +tuples1a.scala +tuples1b.scala +typeCheckErrors.scala +typeclass-derivation-doc-example.scala typeclass-derivation1.scala typeclass-derivation2.scala typeclass-derivation2a.scala +typeclass-derivation2b.scala typeclass-derivation2c.scala typeclass-derivation2d.scala typeclass-derivation3.scala -derive-generic.scala -mixin-forwarder-overload -t8905 -t10889 -macros-in-same-project1 -i5257.scala -i7868.scala -enum-java -zero-arity-case-class.scala -tuple-ops.scala -i7212 varargs-abstract +zero-arity-case-class.scala diff --git a/docs/docs/reference/metaprogramming/inline.md b/docs/docs/reference/metaprogramming/inline.md index ba18f55d76d8..d7275df6f257 100644 --- a/docs/docs/reference/metaprogramming/inline.md +++ b/docs/docs/reference/metaprogramming/inline.md @@ -284,6 +284,23 @@ transparent inline def zero: Int = 0 val one: 1 = zero + 1 ``` +### Transparent vs. non-transparent inline +As we already discussed, transparent inline methods may influence type checking at call site. +Technically this implies that transparent inline methods must be expanded during type checking of the program. +Other inline methods are inlined later after the program is fully typed. + +For example, the following two functions will be typed the same way but will be inlined at different times. +```scala +inline def f1: T = ... +transparent inline def f2: T = (...): T +``` + +A noteworthy difference is the behavior of `transparent inline given`. +If there is an error reported when inlining that definition, it will be considered as an implicit search mismatch and the search will continue. +A `transparent inline given` can add a type ascription in its RHS (as in `f2` from the previous example) to avoid the precise type but keep the search behavior. +On the other hand, `inline given` be taken as the implicit and then inlined after typing. +Any error will be emitted as usual. + ## Inline Conditionals An if-then-else expression whose condition is a constant expression can be simplified to @@ -313,6 +330,8 @@ below: | This location is in code that was inlined at ... ``` +In a transparent inline, an `inline if` will force the inlining of any inline definition in its condition during type checking. + ## Inline Matches A `match` expression in the body of an `inline` method definition may be diff --git a/docs/docs/reference/metaprogramming/macros.md b/docs/docs/reference/metaprogramming/macros.md index 997252176fc3..fc76f1607151 100644 --- a/docs/docs/reference/metaprogramming/macros.md +++ b/docs/docs/reference/metaprogramming/macros.md @@ -618,9 +618,9 @@ def setForExpr[T: Type](using Quotes): Expr[Set[T]] = case _ => '{ new HashSet[T] } ``` -## Relationship with Whitebox Inline +## Relationship with Transparent Inline -[Inline](./inline.md) documents inlining. The code below introduces a whitebox +[Inline](./inline.md) documents inlining. The code below introduces a transparent inline method that can calculate either a value of type `Int` or a value of type `String`. diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index ac0f03431b6e..248b54663353 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -74,14 +74,14 @@ package object compiletime { * cannot be constructed from the provided type. Otherwise returns * that value wrapped in `Some`. */ - inline def constValueOpt[T]: Option[T] = + transparent inline def constValueOpt[T]: Option[T] = // implemented in dotty.tools.dotc.typer.Inliner error("Compiler bug: `constValueOpt` was not evaluated by the compiler") /** Given a constant, singleton type `T`, convert it to a value * of the same singleton type. For example: `assert(constValue[1] == 1)`. */ - inline def constValue[T]: T = + transparent inline def constValue[T]: T = // implemented in dotty.tools.dotc.typer.Inliner error("Compiler bug: `constValue` was not evaluated by the compiler") diff --git a/library/src/scala/compiletime/testing/package.scala b/library/src/scala/compiletime/testing/package.scala index 780debc87b20..ebe629244fb3 100644 --- a/library/src/scala/compiletime/testing/package.scala +++ b/library/src/scala/compiletime/testing/package.scala @@ -2,6 +2,8 @@ package scala.compiletime package testing /** Whether the code type checks in the current context? + * + * An inline definition with a call to `typeChecks` should be transparent. * * @param code The code to be type checked * @@ -9,7 +11,7 @@ package testing * * The code should be a sequence of expressions or statements that may appear in a block. */ -inline def typeChecks(inline code: String): Boolean = +transparent inline def typeChecks(inline code: String): Boolean = // implemented in package dotty.tools.dotc.typer.Inliner.Intrinsics error("Compiler bug: `typeChecks` was not checked by the compiler") @@ -20,12 +22,14 @@ inline def typeChecks(inline code: String): Boolean = * version to version. This API is to be used for testing purposes * only. * + * An inline definition with a call to `typeCheckErrors` should be transparent. + * * @param code The code to be type checked * * @return a list of errors encountered during parsing and typechecking. * * The code should be a sequence of expressions or statements that may appear in a block. */ -inline def typeCheckErrors(inline code: String): List[Error] = +transparent inline def typeCheckErrors(inline code: String): List[Error] = // implemented in package dotty.tools.dotc.typer.Inliner.Intrinsics error("Compiler bug: `typeCheckErrors` was not checked by the compiler") diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 09a7d11bfd6a..31c840e49578 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -217,6 +217,19 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => */ trait reflectModule { self: reflect.type => + /** Module object of `type CompilationInfo` */ + val CompilationInfo: CompilationInfoModule + + /** Methods of the module object `val CompilationInfo` */ + trait CompilationInfoModule { this: CompilationInfo.type => + /** Are we expanding a `inline` macro while typing the program? + * + * This will be true when the macro is used in a transparent inline. + */ + def isWhileTyping: Boolean + } + + /** Returns the `Term` representation this expression */ extension (expr: Expr[Any]) def asTerm: Term diff --git a/library/src/scala/quoted/ToExpr.scala b/library/src/scala/quoted/ToExpr.scala index 5d070f03eb18..b13a88caa4fe 100644 --- a/library/src/scala/quoted/ToExpr.scala +++ b/library/src/scala/quoted/ToExpr.scala @@ -408,8 +408,9 @@ object ToExpr { /** Default implemetation of `ToExpr[H *: T]` */ given TupleConsToExpr [H: Type: ToExpr, T <: Tuple: Type: ToExpr]: ToExpr[H *: T] with { def apply(tup: H *: T)(using Quotes): Expr[H *: T] = - '{ ${summon[ToExpr[H]].apply(tup.head)} *: ${summon[ToExpr[T]].apply(tup.tail)} } - // '{ ${Expr(tup.head)} *: ${Expr(tup.tail)} } // TODO figure out why this fails during CI documentation + val head = Expr[H](tup.head) + val tail = Expr[T](tup.tail) + '{ $head *: $tail } } /** Default implemetation of `ToExpr[BigInt]` */ diff --git a/library/src/scala/runtime/stdLibPatches/Predef.scala b/library/src/scala/runtime/stdLibPatches/Predef.scala index d811d7d2e3fb..e27e77e0d9f9 100644 --- a/library/src/scala/runtime/stdLibPatches/Predef.scala +++ b/library/src/scala/runtime/stdLibPatches/Predef.scala @@ -3,10 +3,10 @@ package scala.runtime.stdLibPatches object Predef: import compiletime.summonFrom - inline def assert(inline assertion: Boolean, inline message: => Any): Unit = + transparent inline def assert(inline assertion: Boolean, inline message: => Any): Unit = if !assertion then scala.runtime.Scala3RunTime.assertFailed(message) - inline def assert(inline assertion: Boolean): Unit = + transparent inline def assert(inline assertion: Boolean): Unit = if !assertion then scala.runtime.Scala3RunTime.assertFailed() /** diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/test b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/test index 092c454ec03f..4b6cf56e68be 100644 --- a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/test +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/test @@ -11,7 +11,7 @@ $ copy-file changes/Macro.scala Macro.scala > clean > run -# use an implemntation of the macro that emits a compile time error +# use an implemntation of the macro that emits a compile-time error $ copy-file changes/MacroCompileError.scala Macro.scala -> compile diff --git a/tests/neg-macros/i7618.scala b/tests/neg-macros/i7618.scala index 3c28caf68564..b97caeb24d7d 100644 --- a/tests/neg-macros/i7618.scala +++ b/tests/neg-macros/i7618.scala @@ -31,6 +31,6 @@ object Example { val exp = Plus(Plus(Num(2), Var("x")), Num(4)) val letExp = Let("x", Num(3), exp) - Compiler.compile(letExp, Map.empty)(using Quotes.macroContext) // error // error + Compiler.compile(letExp, Map.empty)(using Quotes.macroContext) // error } } diff --git a/tests/neg-macros/i9014.check b/tests/neg-macros/i9014.check index be338d620c60..509eac067fc8 100644 --- a/tests/neg-macros/i9014.check +++ b/tests/neg-macros/i9014.check @@ -2,9 +2,5 @@ -- Error: tests/neg-macros/i9014/Test_2.scala:1:23 --------------------------------------------------------------------- 1 |val tests = summon[Bar] // error | ^ - | no implicit argument of type Bar was found for parameter x of method summon in object Predef. - | I found: - | - | given_Bar - | - | But given instance given_Bar does not match type Bar. + | Failed to expand! + | This location contains code that was inlined from Test_2.scala:1 diff --git a/tests/neg-macros/i9014b.check b/tests/neg-macros/i9014b.check new file mode 100644 index 000000000000..a7250b66fd95 --- /dev/null +++ b/tests/neg-macros/i9014b.check @@ -0,0 +1,10 @@ + +-- Error: tests/neg-macros/i9014b/Test_2.scala:1:23 -------------------------------------------------------------------- +1 |val tests = summon[Bar] // error + | ^ + | no implicit argument of type Bar was found for parameter x of method summon in object Predef. + | I found: + | + | given_Bar + | + | But given instance given_Bar does not match type Bar. diff --git a/tests/neg-macros/i9014b/Macros_1.scala b/tests/neg-macros/i9014b/Macros_1.scala new file mode 100644 index 000000000000..0dcb45d513cb --- /dev/null +++ b/tests/neg-macros/i9014b/Macros_1.scala @@ -0,0 +1,4 @@ +import scala.quoted._ +trait Bar +transparent inline given Bar = ${ impl } +def impl(using Quotes): Expr[Bar] = quotes.reflect.report.throwError("Failed to expand!") diff --git a/tests/neg-macros/i9014b/Test_2.scala b/tests/neg-macros/i9014b/Test_2.scala new file mode 100644 index 000000000000..b23781947922 --- /dev/null +++ b/tests/neg-macros/i9014b/Test_2.scala @@ -0,0 +1 @@ +val tests = summon[Bar] // error diff --git a/tests/neg/i11101.check b/tests/neg/i11101.check new file mode 100644 index 000000000000..726a1ad2a44b --- /dev/null +++ b/tests/neg/i11101.check @@ -0,0 +1,8 @@ +-- Error: tests/neg/i11101.scala:16:22 --------------------------------------------------------------------------------- +16 | summon[TC[FailHere]] // error: blow up here + | ^ + | blow up here +-- Error: tests/neg/i11101.scala:17:16 --------------------------------------------------------------------------------- +17 | TC.mkDefaultTC[FailHere] // error: blow up here + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | blow up here diff --git a/tests/neg/i11101.scala b/tests/neg/i11101.scala new file mode 100644 index 000000000000..b5e00f6a28f0 --- /dev/null +++ b/tests/neg/i11101.scala @@ -0,0 +1,17 @@ +class FailHere + +trait TC[T] +object TC { + + inline def mkDefaultTC[A]: TC[A] = inline compiletime.erasedValue[A] match { + case _: FailHere => compiletime.error("blow up here") + case _ => ??? + } + + inline given [T]: TC[T] = mkDefaultTC[T] +} + +def test = + summon[TC[Int]] // ok + summon[TC[FailHere]] // error: blow up here + TC.mkDefaultTC[FailHere] // error: blow up here diff --git a/tests/neg/i9014.check b/tests/neg/i9014.check index 629e81f6b6af..a8e21d94e501 100644 --- a/tests/neg/i9014.check +++ b/tests/neg/i9014.check @@ -1,9 +1,4 @@ -- Error: tests/neg/i9014.scala:4:25 ----------------------------------------------------------------------------------- -4 | val tests = summon[Bar] // error +4 | val tests = summon[Bar] // error: Failed to expand! | ^ - | no implicit argument of type Bar was found for parameter x of method summon in object Predef. - | I found: - | - | Bar.given_Bar - | - | But given instance given_Bar in object Bar does not match type Bar. + | Failed to expand! diff --git a/tests/neg/i9014.scala b/tests/neg/i9014.scala index 2a82d54be9d9..a31f77bba048 100644 --- a/tests/neg/i9014.scala +++ b/tests/neg/i9014.scala @@ -1,4 +1,4 @@ trait Bar object Bar: inline given Bar = compiletime.error("Failed to expand!") - val tests = summon[Bar] // error + val tests = summon[Bar] // error: Failed to expand! diff --git a/tests/neg/inlinevals-2.scala b/tests/neg/inlinevals-2.scala new file mode 100644 index 000000000000..9a3c6829c456 --- /dev/null +++ b/tests/neg/inlinevals-2.scala @@ -0,0 +1,12 @@ +object Test { + + inline def power(x: Double, inline n: Int): Double = // ok + inline if n == 0 then ??? else ??? + + inline val N = 10 + def X = 20 + + power(2.0, N) // ok, since it's a by-name parameter + power(2.0, X) // error: cannot reduce inline if + +} diff --git a/tests/neg/inlinevals-3.scala b/tests/neg/inlinevals-3.scala new file mode 100644 index 000000000000..8fa7a4270e4a --- /dev/null +++ b/tests/neg/inlinevals-3.scala @@ -0,0 +1,12 @@ +object Test { + + inline def power(x: Double, inline n: Int): Double = // ok + inline if n == 0 then ??? else ??? + + inline val N = 10 + def X = 20 + + power(2.0, N) // ok, since it's an inline parameter + power(2.0, X) // error: cannot reduce inline if + +} diff --git a/tests/neg/inlinevals-4.scala b/tests/neg/inlinevals-4.scala new file mode 100644 index 000000000000..b4d4bfdfb579 --- /dev/null +++ b/tests/neg/inlinevals-4.scala @@ -0,0 +1,13 @@ +object Test { + + inline def power(x: Double, inline n: Int): Double = // ok + inline if n == 0 then ??? else ??? + + inline val N = 10 + def X = 20 + + inline val M = X // error: rhs must be constant expression + + inline val xs = List(1, 2, 3) // error: must be a constant expression + +} diff --git a/tests/neg/inlinevals.scala b/tests/neg/inlinevals.scala index d3bd16df8ac1..02732975be80 100644 --- a/tests/neg/inlinevals.scala +++ b/tests/neg/inlinevals.scala @@ -2,9 +2,6 @@ object Test { def power0(x: Double, inline n: Int): Double = ??? // error: inline modifier can only be used for parameters of inline methods - inline def power(x: Double, inline n: Int): Double = // ok - inline if n == 0 then ??? else ??? - inline val N = 10 def X = 20 @@ -15,13 +12,6 @@ object Test { inline def bar: Int } - power(2.0, N) // ok, since it's a by-name parameter - power(2.0, X) // error: cannot reduce inline if - - inline val M = X // error: rhs must be constant expression - - inline val xs = List(1, 2, 3) // error: must be a constant expression - inline def foo(x: Int) = { def f(inline xs: List[Int]) = xs // error: inline modifier can only be used for parameters of inline methods diff --git a/tests/neg/power2.scala b/tests/neg/power2.scala new file mode 100644 index 000000000000..cd7019b47b0d --- /dev/null +++ b/tests/neg/power2.scala @@ -0,0 +1,14 @@ +object Test { + + transparent inline def power(x: Double, n: Int): Double = + if (n == 0) 1.0 + else if (n == 1) x + else { + val y = power(x, n / 2) + if (n % 2 == 0) y * y else y * y * x + } + + def main(args: Array[String]): Unit = { + println(power(2.0, args.length)) // error: maximal number of inlines exceeded + } +} diff --git a/tests/pos-custom-args/erased/i7878.scala b/tests/pos-custom-args/erased/i7878.scala index d47bb32f1a65..7a27cdad4c14 100644 --- a/tests/pos-custom-args/erased/i7878.scala +++ b/tests/pos-custom-args/erased/i7878.scala @@ -2,7 +2,7 @@ object Boom { import scala.compiletime._ trait Fail[A <: Int, B <: Int] - erased inline given fail[X <: Int, Y <: Int]: Fail[X, Y] = { + erased transparent inline given fail[X <: Int, Y <: Int]: Fail[X, Y] = { scala.compiletime.summonFrom { case t: Fail[X, y] if constValue[y] < constValue[Y] => ??? } diff --git a/tests/pos/i7580.scala b/tests/pos-deep-subtype/i7580.scala similarity index 100% rename from tests/pos/i7580.scala rename to tests/pos-deep-subtype/i7580.scala diff --git a/tests/pos-macros/classesInInlinedParam.scala b/tests/pos-macros/classesInInlinedParam.scala new file mode 100644 index 000000000000..355d29cc8e29 --- /dev/null +++ b/tests/pos-macros/classesInInlinedParam.scala @@ -0,0 +1,27 @@ +inline def f(inline thunk: Any): Unit = thunk +transparent inline def g(inline thunk: Any): Unit = thunk + +def test: Unit = + f { + class C1(val i: Int) + val c = C1(1) + c.i + } + + f { + case class C2(i: Int) + val c = C2(2) + c.i + } + + g { + class C3(val i: Int) + val c = C3(3) + c.i + } + + g { + case class C4(i: Int) + val c = C4(4) + c.i + } \ No newline at end of file diff --git a/tests/pos-macros/forced-inline-typeChecks.scala b/tests/pos-macros/forced-inline-typeChecks.scala new file mode 100644 index 000000000000..5f5f53347728 --- /dev/null +++ b/tests/pos-macros/forced-inline-typeChecks.scala @@ -0,0 +1,8 @@ +class Test: + def test: Unit = + assert2(scala.compiletime.testing.typeChecks(stripMargin("|1 + 1"))) + + inline def stripMargin(inline x: String): String = x + + transparent inline def assert2(inline assertion: Boolean): Unit = + if !assertion then scala.runtime.Scala3RunTime.assertFailed() diff --git a/tests/pos-macros/i6588.scala b/tests/pos-macros/i6588.scala index 214dd999ff63..5497c3b287f4 100644 --- a/tests/pos-macros/i6588.scala +++ b/tests/pos-macros/i6588.scala @@ -1,6 +1,6 @@ import scala.quoted._ -inline def foo[T:Type]: Int = 10 +inline def foo[T<:AnyKind:Type]: Int = 10 def main(using Quotes) = { type S = Int diff --git a/tests/pos-macros/i7558/Macro_1.scala b/tests/pos-macros/i7558/Macro_1.scala new file mode 100644 index 000000000000..312017bbdc66 --- /dev/null +++ b/tests/pos-macros/i7558/Macro_1.scala @@ -0,0 +1,8 @@ +import scala.quoted._ + +inline def mcr(expr: => Any): Any = ${mcrImpl('expr)} + +def mcrImpl(expr: Expr[Any])(using Quotes): Expr[Any] = + import quotes.reflect._ + expr.asTerm.tpe.widen.show + expr diff --git a/tests/pos-macros/i7558/Test_2.scala b/tests/pos-macros/i7558/Test_2.scala new file mode 100644 index 000000000000..b7113215dfd2 --- /dev/null +++ b/tests/pos-macros/i7558/Test_2.scala @@ -0,0 +1 @@ +def eq[T](p: T) = mcr(p) diff --git a/tests/pos-macros/i8858/Macro_1.scala b/tests/pos-macros/i8858/Macro_1.scala new file mode 100644 index 000000000000..8eb0182c2779 --- /dev/null +++ b/tests/pos-macros/i8858/Macro_1.scala @@ -0,0 +1,9 @@ +import scala.quoted._ + +inline def mcr(inline x: Any): Any = ${ mcrImpl('x) } +def mcrImpl(expr: Expr[Any])(using Quotes): Expr[Any] = + import quotes.reflect._ + expr.asTerm match + case Inlined(_, _, id1) => + println(id1.tpe.widen.show) + '{()} diff --git a/tests/pos-macros/i8858/Test_2.scala b/tests/pos-macros/i8858/Test_2.scala new file mode 100644 index 000000000000..21acefdd2ccc --- /dev/null +++ b/tests/pos-macros/i8858/Test_2.scala @@ -0,0 +1,3 @@ +trait Foo[A] + +def check[T](x: Foo[T]) = mcr(x) diff --git a/tests/pos-macros/i9296/Test_2.scala b/tests/pos-macros/i9296/Test_2.scala index 8e22c5ae841b..7ee26cc869cf 100644 --- a/tests/pos-macros/i9296/Test_2.scala +++ b/tests/pos-macros/i9296/Test_2.scala @@ -5,10 +5,11 @@ import scala.concurrent._ trait CB[T] -given myConversion[T]: Conversion[Future[T],CB[T]] = (ft => ???) object O { + given myConversion[T]: Conversion[Future[T],CB[T]] = (ft => ???) + def main(argvs: Array[String]): Unit = { val p = Promise[Int]() //val cbp = summon[Conversion[Future[Int],CB[Int]]] //works diff --git a/tests/pos/i10107c.scala b/tests/pos/i10107c.scala new file mode 100644 index 000000000000..b42e62ddb7dc --- /dev/null +++ b/tests/pos/i10107c.scala @@ -0,0 +1,12 @@ +import scala.quoted._ + +inline def isTrue: Boolean = true + +inline def oneOf(): String = { + inline if isTrue then + "foo" + else + "bar" +} + +def test1 = oneOf() diff --git a/tests/pos/i10295.scala b/tests/pos/i10295.scala index 8fc237e2e853..d33a67ad799e 100644 --- a/tests/pos/i10295.scala +++ b/tests/pos/i10295.scala @@ -3,7 +3,7 @@ trait M: object X: def foo(): X = ??? -inline def m(using m: M): m.type = m +transparent inline def m(using m: M): m.type = m def doSomething(body: M ?=> Unit) = body(using new M{}) diff --git a/tests/pos/i11184a.scala b/tests/pos/i11184a.scala new file mode 100644 index 000000000000..b4b0beca45ee --- /dev/null +++ b/tests/pos/i11184a.scala @@ -0,0 +1,5 @@ +import scala.quoted._ + +inline def isTrue: Boolean = true +inline def oneOf: String = inline if isTrue then "foo" else "bar" +def test1 = oneOf diff --git a/tests/pos/i11184b.scala b/tests/pos/i11184b.scala new file mode 100644 index 000000000000..16a12b92d15b --- /dev/null +++ b/tests/pos/i11184b.scala @@ -0,0 +1,5 @@ +import scala.quoted._ + +inline def isTrue(): Boolean = true +inline def oneOf: String = inline if isTrue() then "foo" else "bar" +def test1 = oneOf diff --git a/tests/pos/i11184c.scala b/tests/pos/i11184c.scala new file mode 100644 index 000000000000..bcd83fb056a2 --- /dev/null +++ b/tests/pos/i11184c.scala @@ -0,0 +1,6 @@ +import scala.quoted._ + +object Foo: + inline def isTrue: Boolean = true +inline def oneOf: String = inline if Foo.isTrue then "foo" else "bar" +def test1 = oneOf diff --git a/tests/pos/i11184d.scala b/tests/pos/i11184d.scala new file mode 100644 index 000000000000..3807159a9c5d --- /dev/null +++ b/tests/pos/i11184d.scala @@ -0,0 +1,5 @@ +import scala.quoted._ + +inline def isTrue: Boolean = true +transparent inline def oneOf: Any = inline if isTrue then isTrue else "bar" +def test1 = oneOf diff --git a/tests/pos/i8892.scala b/tests/pos/i8892.scala index f08b12341d3b..0a7e7223fe06 100644 --- a/tests/pos/i8892.scala +++ b/tests/pos/i8892.scala @@ -6,7 +6,7 @@ class Dummy extends Reporter: object ABug { sealed trait Nat { - inline def ++ : Succ[this.type] = Succ(this) + transparent inline def ++ : Succ[this.type] = Succ(this) transparent inline def +(inline that: Nat): Nat = inline this match { diff --git a/tests/pos/inline-separate/A_1.scala b/tests/pos/inline-separate/A_1.scala index 72783578df80..e5055f1761e4 100644 --- a/tests/pos/inline-separate/A_1.scala +++ b/tests/pos/inline-separate/A_1.scala @@ -1,5 +1,5 @@ object A { - inline def summon[T] = compiletime.summonFrom { + transparent inline def summon[T] = compiletime.summonFrom { case t: T => t } } diff --git a/tests/pos/inline-separate/Test_2.scala b/tests/pos/inline-separate/Test_2.scala index 2a7c012b3771..ead2408dd407 100644 --- a/tests/pos/inline-separate/Test_2.scala +++ b/tests/pos/inline-separate/Test_2.scala @@ -1,7 +1,7 @@ import A._ object Test extends App { class Foo(f: => Foo) - inline implicit def foo(implicit f: => Foo): Foo = new Foo(summon[Foo]) + transparent inline implicit def foo(implicit f: => Foo): Foo = new Foo(summon[Foo]) def summonFoo(implicit ev: Foo): Foo = ev summonFoo } diff --git a/tests/pos/summonInline.scala b/tests/pos/summonInline.scala new file mode 100644 index 000000000000..e007fd214981 --- /dev/null +++ b/tests/pos/summonInline.scala @@ -0,0 +1,10 @@ +import scala.compiletime.summonInline + +type A +given A = ??? + +inline def summon1: A = summonInline[A] +transparent inline def summon2: A = summonInline[A] + +def test1 = summon1 +def test2 = summon2 diff --git a/tests/run-custom-args/Yretain-trees/tasty-definitions-3.check b/tests/run-custom-args/Yretain-trees/tasty-definitions-3.check index 3cdc1b6b29f7..3c2b67edfde0 100644 --- a/tests/run-custom-args/Yretain-trees/tasty-definitions-3.check +++ b/tests/run-custom-args/Yretain-trees/tasty-definitions-3.check @@ -1,3 +1,3 @@ -DefDef("foo", Nil, Inferred(), None) -ValDef("bar", Inferred(), None) +DefDef("foo", Nil, TypeIdent("Int"), Some(Apply(Select(Literal(IntConstant(1)), "+"), List(Literal(IntConstant(2)))))) +ValDef("bar", TypeIdent("Int"), Some(Apply(Select(Literal(IntConstant(2)), "+"), List(Literal(IntConstant(3)))))) Bind("x", Ident("_")) diff --git a/tests/run-custom-args/Yretain-trees/tasty-load-tree-2.check b/tests/run-custom-args/Yretain-trees/tasty-load-tree-2.check index 70f474a9e253..e66a336fb836 100644 --- a/tests/run-custom-args/Yretain-trees/tasty-load-tree-2.check +++ b/tests/run-custom-args/Yretain-trees/tasty-load-tree-2.check @@ -1,2 +1,2 @@ -DefDef("foo", Nil, Inferred(), None) -ValDef("bar", Inferred(), None) +DefDef("foo", Nil, TypeIdent("Int"), Some(Apply(Select(Literal(IntConstant(1)), "+"), List(Literal(IntConstant(2)))))) +ValDef("bar", TypeIdent("Int"), Some(Apply(Select(Literal(IntConstant(2)), "+"), List(Literal(IntConstant(3)))))) diff --git a/tests/run-macros/beta-reduce-inline-result.check b/tests/run-macros/beta-reduce-inline-result.check index f1c651a532ba..ed716e4990d8 100644 --- a/tests/run-macros/beta-reduce-inline-result.check +++ b/tests/run-macros/beta-reduce-inline-result.check @@ -1,6 +1,6 @@ -compile-time: 4:Int +compile-time: Macros.betaReduce[Int, Int](Test.dummy1)(3) run-time: 4 -compile-time: 1:Int +compile-time: Macros.betaReduce[Int, Int](Test.dummy2)(1) run-time: 1 run-time: 5 run-time: 7 diff --git a/tests/run-macros/i10043/Macro_1.scala b/tests/run-macros/i10043/Macro_1.scala new file mode 100644 index 000000000000..0a8f6b17e714 --- /dev/null +++ b/tests/run-macros/i10043/Macro_1.scala @@ -0,0 +1,13 @@ +package example + +import scala.quoted._ + +trait Trait { + final val foo = 23 +} + +object Trait { + inline def get: Trait = ${ getImpl } + + def getImpl(using Quotes): Expr[Trait] = '{ new Trait {} } +} diff --git a/tests/run-macros/i10043/Test_2.scala b/tests/run-macros/i10043/Test_2.scala new file mode 100644 index 000000000000..c4634cc44dc3 --- /dev/null +++ b/tests/run-macros/i10043/Test_2.scala @@ -0,0 +1 @@ +@main def Test = println(example.Trait.get) diff --git a/tests/run-macros/i7025b/Macros_1.scala b/tests/run-macros/i7025b/Macros_1.scala new file mode 100644 index 000000000000..e2207a3ee893 --- /dev/null +++ b/tests/run-macros/i7025b/Macros_1.scala @@ -0,0 +1,21 @@ +inline def debug: Unit = ${Macros.debugImpl} + +object Macros { + import scala.quoted._ + + def debugImpl(using Quotes): Expr[Unit] = { + import quotes.reflect._ + + def nearestEnclosingDef(owner: Symbol): Symbol = + if owner.isDefDef then owner + else if owner.isClassDef then owner + else nearestEnclosingDef(owner.owner) + + val sym = nearestEnclosingDef(Symbol.spliceOwner) + if sym.isDefDef then + val code = sym.signature.toString + '{ println(${Expr(code)}) } + else + '{()} + } +} diff --git a/tests/run-macros/i7025b/Test_2.scala b/tests/run-macros/i7025b/Test_2.scala new file mode 100644 index 000000000000..d59038c17823 --- /dev/null +++ b/tests/run-macros/i7025b/Test_2.scala @@ -0,0 +1,9 @@ +object Test { + def main(args: Array[String]): Unit = { + bar("world", 100, true) + } + + def bar(a1: String, a2: Long, a3: Boolean) = { + debug + } +} diff --git a/tests/run-macros/i8877/Macros_1.scala b/tests/run-macros/i8877/Macros_1.scala new file mode 100644 index 000000000000..92eb01938455 --- /dev/null +++ b/tests/run-macros/i8877/Macros_1.scala @@ -0,0 +1,20 @@ +//macros +import scala.quoted._ + +object Macros { + //a specialization of the `findOwner` function from `sourcecode` for our purposes + private def firstNonSyntheticOwner(using Quotes)(s: quotes.reflect.Symbol): quotes.reflect.Symbol = { + import quotes.reflect._ + if (s.flags.is(Flags.Synthetic)) firstNonSyntheticOwner(s.owner) + else s + } + + def genOwnerImpl()(using Quotes): Expr[String] = { + import quotes.reflect._ + Expr(firstNonSyntheticOwner(Symbol.spliceOwner).name) + } +} + +object Foo { + inline def genOwner: String = ${ Macros.genOwnerImpl() } +} diff --git a/tests/run-macros/i8877/Test_2.scala b/tests/run-macros/i8877/Test_2.scala new file mode 100644 index 000000000000..032efb5f0e6c --- /dev/null +++ b/tests/run-macros/i8877/Test_2.scala @@ -0,0 +1,5 @@ + +// main +@main def Test = { + println(Foo.genOwner) +} \ No newline at end of file diff --git a/tests/run-macros/is-in-typer/Macro_1.scala b/tests/run-macros/is-in-typer/Macro_1.scala new file mode 100644 index 000000000000..80157dec0845 --- /dev/null +++ b/tests/run-macros/is-in-typer/Macro_1.scala @@ -0,0 +1,9 @@ +import scala.quoted._ + +inline def isWhileTyping: Boolean = ${ whileTypeing } + +transparent inline def isWhileTypingTransparent: Boolean = ${ whileTypeing } + +private def whileTypeing(using Quotes): Expr[Boolean] = + import quotes.reflect._ + Expr(CompilationInfo.isWhileTyping) diff --git a/tests/run-macros/is-in-typer/Test_2.scala b/tests/run-macros/is-in-typer/Test_2.scala new file mode 100644 index 000000000000..5bd283c4aca4 --- /dev/null +++ b/tests/run-macros/is-in-typer/Test_2.scala @@ -0,0 +1,20 @@ + +@main def Test = + assert(!isWhileTyping) + assert(isWhileTypingTransparent) + assert(f1 == "afterTyper") + assert(f2 == "afterTyper") + assert(f3 == "inTyper") + assert(f4 == "inTyper") + +inline def f1 = + inline if isWhileTyping then "inTyper" else "afterTyper" + +inline def f2 = + inline if isWhileTypingTransparent /*delayed*/ then "inTyper" else "afterTyper" + +transparent inline def f3 = + inline if isWhileTyping /*forced*/ then "inTyper" else "afterTyper" + +transparent inline def f4 = + inline if isWhileTypingTransparent then "inTyper" else "afterTyper" diff --git a/tests/run-macros/no-symbol/2.scala b/tests/run-macros/no-symbol/2.scala index 0e9357d2cc20..cb85878e7bd9 100644 --- a/tests/run-macros/no-symbol/2.scala +++ b/tests/run-macros/no-symbol/2.scala @@ -1,6 +1,6 @@ object Test { def main(args: Array[String]): Unit = { assert(Macro.foo[Foo] == "symbol") - assert(Macro.foo[Box] == "no symbol") + assert(Macro.foo[Box[_]] == "no symbol") } } diff --git a/tests/run-macros/reflect-inline/test_2.scala b/tests/run-macros/reflect-inline/test_2.scala index 2f36d7b52214..97b43666abb7 100644 --- a/tests/run-macros/reflect-inline/test_2.scala +++ b/tests/run-macros/reflect-inline/test_2.scala @@ -2,7 +2,7 @@ import api._ object Test { def main(args: Array[String]): Unit = { - val a: String = "5" + inline val a = "5" assert(typeChecks("|1 + 1".stripMargin)) assert(scala.compiletime.testing.typeChecks("|1 + 1".stripMargin)) assert(("|3 + " + a).stripMargin == "3 + 5") diff --git a/tests/run-macros/reflect-sourceCode.check b/tests/run-macros/reflect-sourceCode.check new file mode 100644 index 000000000000..e4e8e81b1acb --- /dev/null +++ b/tests/run-macros/reflect-sourceCode.check @@ -0,0 +1,7 @@ +args(0) +args( 0 ) +args( 0 /* ignore */) +f +f +{ f } +{ f; f } diff --git a/tests/run-macros/reflect-sourceCode/Test_2.scala b/tests/run-macros/reflect-sourceCode/Test_2.scala index a1a6d6562473..8bd0b6317066 100644 --- a/tests/run-macros/reflect-sourceCode/Test_2.scala +++ b/tests/run-macros/reflect-sourceCode/Test_2.scala @@ -4,12 +4,12 @@ object Test { def f(implicit x: Int): Int = x * x def main(args: Array[String]): Unit = { implicit val x: Int = 10 - assert(args(0).reflect == "args(0)") - assert(args( 0 ).reflect == "args( 0 )") - assert(args( 0 /* ignore */).reflect == "args( 0 /* ignore */)") - assert(f.reflect == "f") - assert((f).reflect == "f") - assert( { f }.reflect == "{ f }") - assert( { f; f }.reflect == "{ f; f }") + println(args(0).reflect) + println(args( 0 ).reflect) + println(args( 0 /* ignore */).reflect) + println(f.reflect) + println((f).reflect) + println( { f }.reflect) + println( { f; f }.reflect) } } diff --git a/tests/run-macros/reflect-typeChecks/assert_1.scala b/tests/run-macros/reflect-typeChecks/assert_1.scala index db8d4fe3dd31..b7456ba6c6e8 100644 --- a/tests/run-macros/reflect-typeChecks/assert_1.scala +++ b/tests/run-macros/reflect-typeChecks/assert_1.scala @@ -2,8 +2,8 @@ import scala.quoted._ object scalatest { - inline def assertCompile(inline code: String): Unit = ${ assertImpl('code, '{compiletime.testing.typeChecks(code)}, true) } - inline def assertNotCompile(inline code: String): Unit = ${ assertImpl('code, '{compiletime.testing.typeChecks(code)}, false) } + transparent inline def assertCompile(inline code: String): Unit = ${ assertImpl('code, '{compiletime.testing.typeChecks(code)}, true) } + transparent inline def assertNotCompile(inline code: String): Unit = ${ assertImpl('code, '{compiletime.testing.typeChecks(code)}, false) } def assertImpl(code: Expr[String], actual: Expr[Boolean], expect: Boolean)(using Quotes) : Expr[Unit] = { '{ assert(${Expr(expect)} == $actual) } diff --git a/tests/run-macros/string-context-implicits/Macro_1.scala b/tests/run-macros/string-context-implicits/Macro_1.scala index b0049f9049d3..0e1b3baa7956 100644 --- a/tests/run-macros/string-context-implicits/Macro_1.scala +++ b/tests/run-macros/string-context-implicits/Macro_1.scala @@ -26,5 +26,6 @@ trait Show[-T] { def show(x: T): String } -given Show[Int] = x => s"Int($x)" -given Show[String] = x => s"Str($x)" +object Show: + given Show[Int] = x => s"Int($x)" + given Show[String] = x => s"Str($x)" diff --git a/tests/run-macros/tasty-extractors-2.check b/tests/run-macros/tasty-extractors-2.check index a5a3c806c923..3fa60370e850 100644 --- a/tests/run-macros/tasty-extractors-2.check +++ b/tests/run-macros/tasty-extractors-2.check @@ -49,7 +49,7 @@ TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), Nil, None, List(DefDef("a", Nil, Inferred(), Some(Literal(IntConstant(0))))))), Literal(UnitConstant()))) TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") -Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil), TypeSelect(Select(Ident("_root_"), "scala"), "Product"), TypeSelect(Select(Ident("_root_"), "scala"), "Serializable")), Nil, None, List(DefDef("copy", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil))))), ValDef("Foo", TypeIdent("Foo$"), Some(Apply(Select(New(TypeIdent("Foo$")), ""), Nil))), ClassDef("Foo$", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), Nil, Some(ValDef("_", Singleton(Ident("Foo")), None)), List(DefDef("apply", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil))), DefDef("unapply", List(TermParamClause(List(ValDef("x$1", Inferred(), None)))), Singleton(Literal(BooleanConstant(true))), Some(Literal(BooleanConstant(true)))), DefDef("toString", Nil, Inferred(), Some(Literal(StringConstant("Foo"))))))), Literal(UnitConstant()))) +Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil), TypeSelect(Select(Ident("_root_"), "scala"), "Product"), TypeSelect(Select(Ident("_root_"), "scala"), "Serializable")), Nil, None, List(DefDef("hashCode", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_hashCode"), List(This(Some("Foo")))))), DefDef("equals", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Apply(Select(Apply(Select(This(Some("Foo")), "eq"), List(TypeApply(Select(Ident("x$0"), "$asInstanceOf$"), List(Inferred())))), "||"), List(Match(Ident("x$0"), List(CaseDef(Bind("x$0", Typed(Ident("_"), Inferred())), None, Apply(Select(Literal(BooleanConstant(true)), "&&"), List(Apply(Select(Ident("x$0"), "canEqual"), List(This(Some("Foo"))))))), CaseDef(Ident("_"), None, Literal(BooleanConstant(false))))))))), DefDef("toString", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_toString"), List(This(Some("Foo")))))), DefDef("canEqual", List(TermParamClause(List(ValDef("that", Inferred(), None)))), Inferred(), Some(TypeApply(Select(Ident("that"), "isInstanceOf"), List(Inferred())))), DefDef("productArity", Nil, Inferred(), Some(Literal(IntConstant(0)))), DefDef("productPrefix", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), DefDef("productElement", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Ident("_"), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), ""), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("productElementName", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Ident("_"), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), ""), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("copy", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil))))), ValDef("Foo", TypeIdent("Foo$"), Some(Apply(Select(New(TypeIdent("Foo$")), ""), Nil))), ClassDef("Foo$", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil), Inferred()), Nil, Some(ValDef("_", Singleton(Ident("Foo")), None)), List(DefDef("apply", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil))), DefDef("unapply", List(TermParamClause(List(ValDef("x$1", Inferred(), None)))), Singleton(Literal(BooleanConstant(true))), Some(Literal(BooleanConstant(true)))), DefDef("toString", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), TypeDef("MirroredMonoType", TypeBoundsTree(Inferred(), Inferred())), DefDef("fromProduct", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil)))))), Literal(UnitConstant()))) TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") Inlined(None, Nil, Block(List(ClassDef("Foo1", DefDef("", List(TermParamClause(List(ValDef("a", TypeIdent("Int"), None)))), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), Nil, None, List(ValDef("a", Inferred(), None)))), Literal(UnitConstant()))) diff --git a/tests/run-macros/whitebox-inline-macro/Macro_1.scala b/tests/run-macros/whitebox-inline-macro/Macro_1.scala new file mode 100644 index 000000000000..bac54fe3301c --- /dev/null +++ b/tests/run-macros/whitebox-inline-macro/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +object Macros { + + inline def blackbox: Int = ${one} + + transparent inline def whitebox: Int = ${one} + + private def one(using Quotes): Expr[Int] = Expr(1) + +} diff --git a/tests/run-macros/whitebox-inline-macro/Test_2.scala b/tests/run-macros/whitebox-inline-macro/Test_2.scala new file mode 100644 index 000000000000..07f6bea5319b --- /dev/null +++ b/tests/run-macros/whitebox-inline-macro/Test_2.scala @@ -0,0 +1,12 @@ +import scala.quoted._ +import Macros._ + +object Test { + def main(args: Array[String]): Unit = { + val a: Int = blackbox + val b: 1 = whitebox + + assert(a == 1) + assert(b == 1) + } +} diff --git a/tests/run-staging/i11184.scala b/tests/run-staging/i11184.scala new file mode 100644 index 000000000000..5ef3e2a037a7 --- /dev/null +++ b/tests/run-staging/i11184.scala @@ -0,0 +1,22 @@ +import scala.quoted._ + +object Test { + def main(args: Array[String]): Unit = { + given staging.Compiler = staging.Compiler.make(getClass.getClassLoader) + staging.run(genCode) + } +} + +inline def isTrue1: Boolean = true + +object Foo: + inline def isTrue2: Boolean = true + +inline def isTrue3(): Boolean = true + +inline def oneOf: String = + inline if isTrue1 then "foo" else "bar" + inline if Foo.isTrue2 then "foo" else "bar" + inline if isTrue3() then "foo" else "bar" + +def genCode(using Quotes) = '{ oneOf } diff --git a/tests/run/tuples1a.scala b/tests/run/tuples1a.scala index d1764caebf74..0911ef6871db 100644 --- a/tests/run/tuples1a.scala +++ b/tests/run/tuples1a.scala @@ -6,4 +6,5 @@ object Test extends App { val t7c: Unit = (t7.tail: (Int, String)).tail val t7d: Unit = (t7.tail: Int *: String *: EmptyTuple).tail val t7e: Unit = t7.tail.tail + val t7f: Unit = t7.drop(1).drop(1) } diff --git a/tests/run/typeCheckErrors.check b/tests/run/typeCheckErrors.check new file mode 100644 index 000000000000..c74d3695926d --- /dev/null +++ b/tests/run/typeCheckErrors.check @@ -0,0 +1 @@ +List(Error(value check is not a member of Unit,compileError("1" * 2).check(""),22,Typer), Error(argument to compileError must be a statically known String but was: augmentString("1").*(2),compileError("1" * 2).check(""),13,Typer)) diff --git a/tests/run/typeCheckErrors.scala b/tests/run/typeCheckErrors.scala new file mode 100644 index 000000000000..9fd357e5b3d2 --- /dev/null +++ b/tests/run/typeCheckErrors.scala @@ -0,0 +1,8 @@ +import scala.compiletime.testing._ + +transparent inline def compileError(inline expr: String): Unit = + println(typeCheckErrors(expr)) + +@main def Test = compileError( + """compileError("1" * 2).check("")""" +) diff --git a/tests/run/typeclass-derivation3.scala b/tests/run/typeclass-derivation3.scala index 675faab8da5d..a768cb014721 100644 --- a/tests/run/typeclass-derivation3.scala +++ b/tests/run/typeclass-derivation3.scala @@ -48,7 +48,7 @@ object typeclasses { true } - inline def eqlCases[Alts](n: Int)(x: Any, y: Any, ord: Int): Boolean = + transparent inline def eqlCases[Alts](n: Int)(x: Any, y: Any, ord: Int): Boolean = inline erasedValue[Alts] match { case _: (alt *: alts1) => if (ord == n) @@ -61,7 +61,7 @@ object typeclasses { false } - inline def derived[T](implicit ev: Mirror.Of[T]): Eq[T] = new Eq[T] { + transparent inline def derived[T](implicit ev: Mirror.Of[T]): Eq[T] = new Eq[T] { def eql(x: T, y: T): Boolean = inline ev match { case m: Mirror.SumOf[T] => @@ -102,7 +102,7 @@ object typeclasses { case _: EmptyTuple => } - inline def pickleCases[Alts <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Any, ord: Int): Unit = + transparent inline def pickleCases[Alts <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Any, ord: Int): Unit = inline erasedValue[Alts] match { case _: (alt *: alts1) => if (ord == n) @@ -125,7 +125,7 @@ object typeclasses { case _: EmptyTuple => } - inline def unpickleCase[T, Elems <: Tuple](buf: mutable.ListBuffer[Int], m: Mirror.ProductOf[T]): T = { + transparent inline def unpickleCase[T, Elems <: Tuple](buf: mutable.ListBuffer[Int], m: Mirror.ProductOf[T]): T = { inline val size = constValue[Tuple.Size[Elems]] inline if (size == 0) m.fromProduct(EmptyTuple) @@ -140,7 +140,7 @@ object typeclasses { } } - inline def unpickleCases[T, Alts <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], ord: Int): T = + transparent inline def unpickleCases[T, Alts <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], ord: Int): T = inline erasedValue[Alts] match { case _: (alt *: alts1) => if (ord == n) @@ -153,7 +153,7 @@ object typeclasses { throw new IndexOutOfBoundsException(s"unexpected ordinal number: $ord") } - inline def derived[T](implicit ev: Mirror.Of[T]): Pickler[T] = new { + transparent inline def derived[T](implicit ev: Mirror.Of[T]): Pickler[T] = new { def pickle(buf: mutable.ListBuffer[Int], x: T): Unit = inline ev match { case m: Mirror.SumOf[T] => @@ -203,7 +203,7 @@ object typeclasses { Nil } - inline def showCase[T](x: Any, m: Mirror.ProductOf[T]): String = { + transparent inline def showCase[T](x: Any, m: Mirror.ProductOf[T]): String = { val label = constValue[m.MirroredLabel] inline m match { case m: Mirror.Singleton => label @@ -211,7 +211,7 @@ object typeclasses { } } - inline def showCases[Alts <: Tuple](n: Int)(x: Any, ord: Int): String = + transparent inline def showCases[Alts <: Tuple](n: Int)(x: Any, ord: Int): String = inline erasedValue[Alts] match { case _: (alt *: alts1) => if (ord == n) showCase(x, summonInline[Mirror.ProductOf[`alt`]]) @@ -220,7 +220,7 @@ object typeclasses { throw new MatchError(x) } - inline def derived[T](implicit ev: Mirror.Of[T]): Show[T] = new { + transparent inline def derived[T](implicit ev: Mirror.Of[T]): Show[T] = new { def show(x: T): String = inline ev match { case m: Mirror.SumOf[T] => diff --git a/tests/run/whitebox-inline.scala b/tests/run/whitebox-inline.scala new file mode 100644 index 000000000000..09e7911caaa1 --- /dev/null +++ b/tests/run/whitebox-inline.scala @@ -0,0 +1,16 @@ +import scala.quoted._ + +object Test { + def main(args: Array[String]): Unit = { + val a: Int = blackbox + val b: 1 = whitebox + + assert(a == 1) + assert(b == 1) + } + + inline def blackbox: Int = 1 + + transparent inline def whitebox: Int = 1 + +} diff --git a/tests/semanticdb/expect/recursion.expect.scala b/tests/semanticdb/expect/recursion.expect.scala index a576eca7a443..aba74b1b032e 100644 --- a/tests/semanticdb/expect/recursion.expect.scala +++ b/tests/semanticdb/expect/recursion.expect.scala @@ -3,7 +3,7 @@ package recursion object Nats/*<-recursion::Nats.*/ { sealed trait Nat/*<-recursion::Nats.Nat#*/ { - inline def ++/*<-recursion::Nats.Nat#`++`().*/ : Succ/*->recursion::Nats.Succ#*/[this.type] = Succ/*->recursion::Nats.Succ.*//*->recursion::Nats.Succ.apply().*/(this) + transparent inline def ++/*<-recursion::Nats.Nat#`++`().*/ : Succ/*->recursion::Nats.Succ#*/[this.type] = Succ/*->recursion::Nats.Succ.*//*->recursion::Nats.Succ.apply().*/(this) transparent inline def +/*<-recursion::Nats.Nat#`+`().*/(inline that/*<-recursion::Nats.Nat#`+`().(that)*/: Nat/*->recursion::Nats.Nat#*/): Nat/*->recursion::Nats.Nat#*/ = inline this match { diff --git a/tests/semanticdb/expect/recursion.scala b/tests/semanticdb/expect/recursion.scala index 4c0b54dd5083..3c06e5b202f4 100644 --- a/tests/semanticdb/expect/recursion.scala +++ b/tests/semanticdb/expect/recursion.scala @@ -3,7 +3,7 @@ package recursion object Nats { sealed trait Nat { - inline def ++ : Succ[this.type] = Succ(this) + transparent inline def ++ : Succ[this.type] = Succ(this) transparent inline def +(inline that: Nat): Nat = inline this match { diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index c13392fb7ebc..191e2f3d0379 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -3309,10 +3309,10 @@ Occurrences: [3:7..3:11): Nats <- recursion/Nats. [4:15..4:18): Nat <- recursion/Nats.Nat# [5:4..5:4): <- recursion/Nats.Nat#``(). -[5:15..5:17): ++ <- recursion/Nats.Nat#`++`(). -[5:20..5:24): Succ -> recursion/Nats.Succ# -[5:38..5:42): Succ -> recursion/Nats.Succ. -[5:42..5:42): -> recursion/Nats.Succ.apply(). +[5:27..5:29): ++ <- recursion/Nats.Nat#`++`(). +[5:32..5:36): Succ -> recursion/Nats.Succ# +[5:50..5:54): Succ -> recursion/Nats.Succ. +[5:54..5:54): -> recursion/Nats.Succ.apply(). [7:27..7:28): + <- recursion/Nats.Nat#`+`(). [7:36..7:40): that <- recursion/Nats.Nat#`+`().(that) [7:42..7:45): Nat -> recursion/Nats.Nat#