diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 19f7ee238d99..9644313e925d 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -592,7 +592,7 @@ object SymDenotations { def seesOpaques(implicit ctx: Context): Boolean = containsOpaques || - is(Module, butNot = Package) && owner.containsOpaques + is(Module, butNot = Package) && owner.seesOpaques /** Is this the denotation of a self symbol of some class? * This is the case if one of two conditions holds: diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index fd000257c75c..7c194b3a7283 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2463,9 +2463,9 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { if (skipped) op else { indent += 2 - b append "\n" append (" " * indent) append "==> " append str + b.append("\n").append(" " * indent).append("==> ").append(str) val res = op - b append "\n" append (" " * indent) append "<== " append str append " = " append show(res) + b.append("\n").append(" " * indent).append("<== ").append(str).append(" = ").append(show(res)) indent -= 2 res } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 2452081bf98c..ae00b9e4a5d6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -201,7 +201,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private val (methPart, callTypeArgs, callValueArgss) = decomposeCall(call) private val inlinedMethod = methPart.symbol - private val inlineCallPrefix = qualifier(methPart) + private val inlineCallPrefix = + qualifier(methPart).orElse(This(inlinedMethod.enclosingClass.asClass)) // Make sure all type arguments to the call are fully determined for (targ <- callTypeArgs) fullyDefinedType(targ.tpe, "inlined type argument", targ.span) @@ -243,6 +244,75 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private def newSym(name: Name, flags: FlagSet, info: Type)(implicit ctx: Context): Symbol = ctx.newSymbol(ctx.owner, name, flags, info, coord = call.span) + /** A this-proxy for `tpe` is not needed if + * - `tpe` is the type of the inline call receiver and the inline goes to the same class. + * Example: + * + * class C { + * inline def f() + * this.f() + * } + * + * - The class referenced by `tpe` is inside the inline method + * - The class referenced by `tpe` is a static object that does not have opaque types + * in its scope or environment + */ + private def canElideThis(tpe: ThisType): Boolean = { + val cls = tpe.cls + inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(cls) || + cls.isContainedIn(inlinedMethod) || + cls.isStaticOwner && !cls.seesOpaques + } + + /** Populate `thisProxy` and some parts of `paramProxy` as follows: + * + * 1. If given type refers to a this type of a class that cannot be elided, + * create a proxy symbol and bind the thistype to refer to the proxy. + * The proxy is not yet entered in `bindingsBuf`; that will come later. + * 2. If this type goes to a class or non-static module, also create a this proxy + * for the this type of the owner of the inline method, so that we have a root + * to compute the initializers of this-proxies. + * 3. If the class referenced by a this type has type parameters, create parameter + * proxies for them. + */ + private def registerThisProxies(tpe: Type): Unit = tpe match { + case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) => + def adaptToPrefix(tp: Type) = tp.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner) + val proxyName = s"${tpe.cls.name}_this".toTermName + val proxyType = inlineCallPrefix.tpe.dealias.tryNormalize match { + case typeMatchResult if typeMatchResult.exists => typeMatchResult + case _ => adaptToPrefix(tpe).widenIfUnstable + } + thisProxy(tpe.cls) = newSym(proxyName, InlineProxy, proxyType).termRef + if (!tpe.cls.isStaticOwner) + registerThisProxies(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select + for (param <- tpe.cls.typeParams) + paramProxy(param.typeRef) = adaptToPrefix(param.typeRef) + case _ => + } + + /** Populate the rest of `paramProxy` as follows: + * + * If the given type refers to a parameter, make `paramProxy` refer to the entry stored + * in `paramNames` under the parameter's name. This roundabout way to bind parameter + * references to proxies is done because we don't know a priori what the parameter + * references of a method are (we only know the method's type, but that contains TypeParamRefs + * and MethodParams, not TypeRefs or TermRefs. + */ + private def registerParamProxies(tpe: Type): Unit = tpe match { + case tpe: NamedType + if tpe.symbol.is(Param) && tpe.symbol.owner == inlinedMethod && !paramProxy.contains(tpe) => + paramProxy(tpe) = paramBinding(tpe.name) + case _ => + } + + /** Perform `op` for each leaf tree (of type This, Ident, or TypeTree) of rhsToInline` */ + private def foreachRHSLeaf(op: Type => Unit): Unit = + rhsToInline.foreachSubTree { + case tree: (This | Ident | TypeTree) => tree.tpe.foreachPart(op) + case _ => + } + /** A binding for the parameter of an inline method. This is a `val` def for * by-value parameters and a `def` def for by-name parameters. `val` defs inherit * inline annotations from their parameters. The generated `def` is appended @@ -312,15 +382,19 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { var lastSelf: Symbol = NoSymbol var lastLevel: Int = 0 for ((level, selfSym) <- sortedProxies) { - lazy val rhsClsSym = selfSym.info.widenDealias.classSymbol + def widenSelfInfo(tp: Type): Type = tp.widenDealias match { + case RefinedType(parent, _, _) => widenSelfInfo(parent) + case tpw => tpw + } + val rhsClsSym = widenSelfInfo(selfSym.info).classSymbol val rhs = - if (lastSelf.exists) - ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) - else if (rhsClsSym.is(Module) && rhsClsSym.isStatic) + if (rhsClsSym.is(Module) && rhsClsSym.isStatic) ref(rhsClsSym.sourceModule) + else if (lastSelf.exists) + ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) else inlineCallPrefix - val binding = ValDef(selfSym.asTerm, rhs).withSpan(selfSym.span).setDefTree + val binding = ValDef(selfSym.asTerm, rhs.ensureConforms(selfSym.info)).withSpan(selfSym.span).setDefTree bindingsBuf += binding inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}") lastSelf = selfSym @@ -328,49 +402,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } - private def canElideThis(tpe: ThisType): Boolean = - inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || - tpe.cls.isContainedIn(inlinedMethod) || - tpe.cls.is(Package) - - /** Populate `thisProxy` and `paramProxy` as follows: - * - * 1a. If given type refers to a static this, thisProxy binds it to corresponding global reference, - * 1b. If given type refers to an instance this to a class that is not contained in the - * inline method, create a proxy symbol and bind the thistype to refer to the proxy. - * The proxy is not yet entered in `bindingsBuf`; that will come later. - * 2. If given type refers to a parameter, make `paramProxy` refer to the entry stored - * in `paramNames` under the parameter's name. This roundabout way to bind parameter - * references to proxies is done because we don't know a priori what the parameter - * references of a method are (we only know the method's type, but that contains TypeParamRefs - * and MethodParams, not TypeRefs or TermRefs. - */ - private def registerType(tpe: Type): Unit = tpe match { - case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) => - val proxyName = s"${tpe.cls.name}_this".toTermName - def adaptToPrefix(tp: Type) = tp.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner) - val proxyType = inlineCallPrefix.tpe.dealias.tryNormalize match { - case typeMatchResult if typeMatchResult.exists => typeMatchResult - case _ => adaptToPrefix(tpe).widenIfUnstable - } - thisProxy(tpe.cls) = newSym(proxyName, InlineProxy, proxyType).termRef - if (!tpe.cls.isStaticOwner) - registerType(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select - for (param <- tpe.cls.typeParams) - paramProxy(param.typeRef) = adaptToPrefix(param.typeRef) - case tpe: NamedType - if tpe.symbol.is(Param) && tpe.symbol.owner == inlinedMethod && !paramProxy.contains(tpe) => - paramProxy(tpe) = paramBinding(tpe.name) - case _ => - } - - /** Register type of leaf node */ - private def registerLeaf(tree: Tree): Unit = tree match { - case _: This | _: Ident | _: TypeTree => - tree.tpe.foreachPart(registerType, stopAtStatic = true) - case _ => - } - /** Make `tree` part of inlined expansion. This means its owner has to be changed * from its `originalOwner`, and, if it comes from outside the inlined method * itself, it has to be marked as an inlined argument. @@ -401,14 +432,18 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { ) } + // make sure prefix is executed if it is impure + if (!isIdempotentExpr(inlineCallPrefix)) + registerThisProxies(inlinedMethod.owner.thisType) + + // Compute this proxies for all leaves of the inlined body + foreachRHSLeaf(registerThisProxies) + // Compute bindings for all parameters, appending them to bindingsBuf computeParamBindings(inlinedMethod.info, callTypeArgs, callValueArgss) - // make sure prefix is executed if it is impure - if (!isIdempotentExpr(inlineCallPrefix)) registerType(inlinedMethod.owner.thisType) - // Register types of all leaves of inlined body so that the `paramProxy` and `thisProxy` maps are defined. - rhsToInline.foreachSubTree(registerLeaf) + foreachRHSLeaf(registerParamProxies) // Compute bindings for all this-proxies, appending them to bindingsBuf computeThisBindings() @@ -427,9 +462,11 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val inliner = new TreeTypeMap( typeMap = new TypeMap { + override val stopAtStatic = false def apply(t: Type) = t match { case t: ThisType => thisProxy.getOrElse(t.cls, t) case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) + case t: TermRef if t.symbol.is(Package) => t case t: SingletonType => paramProxy.getOrElse(t, mapOver(t)) case t => mapOver(t) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6d6404fb7316..f3edbcbe82bf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -49,7 +49,7 @@ object Typer { */ enum BindingPrec { case NothingBound, PackageClause, WildImport, NamedImport, Definition - + def isImportPrec = this == NamedImport || this == WildImport } diff --git a/compiler/test/dotc/pos-from-tasty.blacklist b/compiler/test/dotc/pos-from-tasty.blacklist index 1c1b2e32e86c..472a26da7ca1 100644 --- a/compiler/test/dotc/pos-from-tasty.blacklist +++ b/compiler/test/dotc/pos-from-tasty.blacklist @@ -6,3 +6,6 @@ t3612.scala # Other failure t802.scala + +# Recursion limit exceeded: find-member K0.ToUnion +shapeless.scala diff --git a/tests/pos/shapeless.scala b/tests/pos/shapeless.scala new file mode 100644 index 000000000000..0f40265eb9e9 --- /dev/null +++ b/tests/pos/shapeless.scala @@ -0,0 +1,265 @@ +package test.shapeless { + +import scala.collection.mutable.WrappedArray +import scala.compiletime._ +import scala.deriving._ +import annotation.tailrec + +object K0 { + type Generic[O] = Mirror { type MirroredType = O ; type MirroredElemTypes } + type ProductGeneric[O] = Mirror.Product { type MirroredType = O ; type MirroredElemTypes } + + def Generic[O] given (gen: Generic[O]): Generic[O] { type MirroredElemTypes = gen.MirroredElemTypes ; type MirroredLabel = gen.MirroredLabel ; type MirroredElemLabels = gen.MirroredElemLabels } = gen + def ProductGeneric[O] given (gen: ProductGeneric[O]): ProductGeneric[O] { type MirroredElemTypes = gen.MirroredElemTypes ; type MirroredLabel = gen.MirroredLabel ; type MirroredElemLabels = gen.MirroredElemLabels } = gen + + opaque type Instances[F[_], T] = ErasedInstances[F[T]] + opaque type ProductInstances[F[_], T] = ErasedProductInstances[F[T]] + + def Instances[F[_], T] given (inst: Instances[F, T]): inst.type = inst + def ProductInstances[F[_], T] given (inst: ProductInstances[F, T]): inst.type = inst + + type ToUnion[T] = T match { + case Unit => Nothing + case a *: b => a | ToUnion[b] + } + + type IndexOf[E, X] = IndexOf0[E, X, 0] + + type IndexOf0[E, X, I <: Int] <: Int = X match { + case Unit => -1 + case x *: xs => x match { + case E => I + case _ => IndexOf0[E, xs, S[I]] + } + } + + inline def summonAsArray[F[_], T]: Array[Any] = inline erasedValue[T] match { + case _: Unit => Array() + case _: Tuple1[a] => Array(summon[F[a]]) + case _: (a, b) => Array(summon[F[a]], summon[F[b]]) + case _: (a, b, c) => Array(summon[F[a]], summon[F[b]], summon[F[c]]) + case _: (a, b, c, d) => Array(summon[F[a]], summon[F[b]], summon[F[c]], summon[F[d]]) + case _: (a, b, c, d, e) => Array(summon[F[a]], summon[F[b]], summon[F[c]], summon[F[d]], summon[F[e]]) + // Add fallback for larger sizes + } + + type LiftP[F[_], T] <: Tuple = T match { + case Unit => Unit + case a *: b => F[a] *: LiftP[F, b] + } + + inline def summonFirst[F[_], T, U]: F[U] = summonFirst0[LiftP[F, T]].asInstanceOf[F[U]] + + inline def summonFirst0[T] <: Any = inline erasedValue[T] match { + case _: (a *: b) => implicit match { + case aa: `a` => aa + case _ => summonFirst0[b] + } + } + + given Ops { + inline def (gen: ProductGeneric[Obj]) toRepr [Obj] (o: Obj): gen.MirroredElemTypes = Tuple.fromProduct(o.asInstanceOf).asInstanceOf[gen.MirroredElemTypes] + inline def (gen: ProductGeneric[Obj]) fromRepr [Obj] (r: gen.MirroredElemTypes): Obj = gen.fromProduct(r.asInstanceOf).asInstanceOf[Obj] + + inline def (inst: ProductInstances[F, T]) construct [F[_], T] (f: [t] => F[t] => t): T = + inst.asInstanceOf[ErasedProductInstances[F[T]]].erasedConstruct(f.asInstanceOf).asInstanceOf + // Note the necessary cast here + inline def (inst: ProductInstances[F, T]) map2 [F[_], T] (x: T, y: T)(f: [t] => (F[t], t, t) => t): T = + inst.asInstanceOf[ErasedProductInstances[F[T]]].erasedMap2(x, y)(f.asInstanceOf).asInstanceOf + // Note the necessary cast here + } + + type ProductGenericR[O, R] = Mirror.Product { type MirroredType = O ; type MirroredElemTypes = R } + + inline given mkInstances[F[_], T] as Instances[F, T] given (gen: Generic[T]) = + inline gen match { + case p: ProductGeneric[T] => mkProductInstances[F, T] given p + } + + inline given mkProductInstances[F[_], T] as ProductInstances[F, T] given (gen: ProductGeneric[T]) = + new ErasedProductInstances(gen, summonAsArray[F, gen.MirroredElemTypes]).asInstanceOf[ProductInstances[F, T]] + + inline def derive[F[_], T](gen: Generic[T], pg: ProductInstances[F, T] => F[T]): F[T] = + inline gen match { + case p: ProductGeneric[T] => pg(mkProductInstances[F, T] given p) + } +} + +// -------------------------------------------------------------- + +abstract class ErasedInstances[FT] { + def erasedMap(x: Any)(f: (Any, Any) => Any): Any +} + +final class ErasedProductInstances[FT](val mirror: Mirror.Product, is0: => Array[Any]) extends ErasedInstances[FT] { + lazy val is = is0 + + inline def toProduct(x: Any): Product = x.asInstanceOf[Product] + + class ArrayProduct(val elems: Array[Any]) extends Product { + def canEqual(that: Any): Boolean = true + def productElement(n: Int) = elems(n) + def productArity = elems.length + override def productIterator: Iterator[Any] = elems.iterator + } + + def erasedConstruct(f: Any => Any): Any = { + val n = is.length + val arr = new Array[Any](n) + var i = 0 + while(i < n) { + arr(i) = f(is(i)) + i = i+1 + } + mirror.fromProduct(ArrayProduct(arr)) + } + + def erasedUnfold(a: Any)(f: (Any, Any) => (Any, Option[Any])): (Any, Option[Any]) = { + val n = is.length + val arr = new Array[Any](n) + var acc = a + var i = 0 + while(i < n) { + val (acc0, e0) = f(acc, is(i)) + e0 match { + case Some(e) => + acc = acc0 + arr(i) = e + case None => + return (acc0, None) + } + i = i+1 + } + (acc, Some(mirror.fromProduct(ArrayProduct(arr)))) + } + + def erasedMap(x0: Any)(f: (Any, Any) => Any): Any = { + val x = toProduct(x0) + val n = is.length + val arr = new Array[Any](n) + var i = 0 + while(i < n) { + arr(i) = f(is(i), x.productElement(i)) + i = i+1 + } + mirror.fromProduct(ArrayProduct(arr)) + } + + def erasedMap2(x0: Any, y0: Any)(f: (Any, Any, Any) => Any): Any = { + val x = toProduct(x0) + val y = toProduct(y0) + val n = is.length + val arr = new Array[Any](n) + var i = 0 + while(i < n) { + arr(i) = f(is(i), x.productElement(i), y.productElement(i)) + i = i+1 + } + mirror.fromProduct(ArrayProduct(arr)) + } + + def erasedFoldLeft(x0: Any)(i: Any)(f: (Any, Any, Any) => CompleteOr[Any]): Any = { + val x = toProduct(x0) + val n = x.productArity + @tailrec + def loop(i: Int, acc: Any): Any = + if(i >= n) acc + else + f(acc, is(i), x.productElement(i)) match { + case Complete(r) => r + case Continue(acc) => + loop(i+1, acc) + } + + loop(0, i) + } + + def erasedFoldLeft2(x0: Any, y0: Any)(i: Any)(f: (Any, Any, Any, Any) => CompleteOr[Any]): Any = { + val x = toProduct(x0) + val y = toProduct(y0) + val n = x.productArity + @tailrec + def loop(i: Int, acc: Any): Any = + if(i >= n) acc + else + f(acc, is(i), x.productElement(i), y.productElement(i)) match { + case Complete(r) => r + case Continue(acc) => + loop(i+1, acc) + } + + loop(0, i) + } +} + +// --------------------------------------------------- + +type Id[t] = t +type Const[c] = [t] =>> c +case class Wrap[T](t: T) + +type ~>[A[_], B[_]] = [t] => A[t] => B[t] + +inline def summon[T] = implicit match { + case t: T => t +} + +inline def summonValues[T] <: Tuple = inline erasedValue[T] match { + case _: Unit => () + case _: (a *: b) => constValue[a] *: summonValues[b] +} + +inline def summonValuesAsArray[T]: Array[Any] = inline erasedValue[Id[T]] match { + case _: Unit => Array() + case _: Tuple1[a] => Array(constValue[a]) + case _: (a, b) => Array(constValue[a], constValue[b]) + case _: (a, b, c) => Array(constValue[a], constValue[b], constValue[c]) + case _: (a, b, c, d) => Array(constValue[a], constValue[b], constValue[c], constValue[d]) + case _: (a, b, c, d, e) => Array(constValue[a], constValue[b], constValue[c], constValue[d], constValue[e]) + // Add fallback for larger sizes +} + +case class Labelling[T](label: String, elemLabels: Seq[String]) +object Labelling { + inline given apply[T0] as Labelling[T0] given (mirror: Mirror { type MirroredType = T0 }) = + Labelling[T0]( + constValue[mirror.MirroredLabel & String], + WrappedArray.make[String](summonValuesAsArray[mirror.MirroredElemLabels]) + ) +} + +sealed trait CompleteOr[T] +case class Complete[T](t: T) extends CompleteOr[T] +case class Continue[T](t: T) extends CompleteOr[T] + +object Complete { + inline def apply[T](c: Boolean)(t: T)(f: T): CompleteOr[T] = + if(c) Complete(t) + else Continue(f) +} + +// --------------------------------------------------------------------- + +case class ISB(i: Int) derives Monoid + +trait Monoid[A] { + def empty: A + def combine(x: A, y: A): A +} + +object Monoid { + given as Monoid[Int] = new Monoid[Int] { + def empty: Int = 0 + def combine(x: Int, y: Int): Int = x+y + } + + given monoidGen[A] as Monoid[A] given (inst: K0.ProductInstances[Monoid, A]) { + def empty: A = inst.construct([t] => (ma: Monoid[t]) => ma.empty) + def combine(x: A, y: A): A = inst.map2(x, y)([t] => (mt: Monoid[t], t0: t, t1: t) => mt.combine(t0, t1)) + } + + inline def derived[A] given (gen: K0.ProductGeneric[A]): Monoid[A] = monoidGen +} + + +} \ No newline at end of file