From 984e6df9fe3e4307561b9cc869b1ad21edc150c7 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 5 Dec 2024 09:47:45 +0000 Subject: [PATCH 1/4] Fix ASF call in ResolveSuper --- .../tools/dotc/transform/ResolveSuper.scala | 4 +-- tests/pos/i22062.scala | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i22062.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index dd3f41be5a8e..6bc5db79fee5 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -109,8 +109,8 @@ object ResolveSuper { sym = other.symbol // Having a matching denotation is not enough: it should also be a subtype // of the superaccessor's type, see i5433.scala for an example where this matters - val otherTp = other.asSeenFrom(base.typeRef).info - val accTp = acc.asSeenFrom(base.typeRef).info + val otherTp = other.asSeenFrom(base.thisType).info + val accTp = acc.asSeenFrom(base.thisType).info // Since the super class can be Java defined, // we use relaxed overriding check for explicit nulls if one of the symbols is Java defined. // This forces `Null` to be a subtype of non-primitive value types during override checking. diff --git a/tests/pos/i22062.scala b/tests/pos/i22062.scala new file mode 100644 index 000000000000..73289deac0a4 --- /dev/null +++ b/tests/pos/i22062.scala @@ -0,0 +1,32 @@ +class Label +class Component + +trait RenderableCellsCompanion: + type Renderer[-A] <: CellRenderer[A] + type DefaultRenderer[-A] <: Label & Renderer[A] + + trait CellRendererCompanion: + type CellInfo + def labeled[A](): DefaultRenderer[A] + protected trait LabelRenderer[-A] extends CellRenderer[A]: + override abstract def componentFor(info: companion.CellInfo): Component = super.componentFor(info) + + trait CellRenderer[-A]: + val companion: CellRendererCompanion + def componentFor(cellInfo: companion.CellInfo): Component + +sealed trait TreeRenderers extends RenderableCellsCompanion: + this: Tree.type => + + trait Renderer[-A] extends CellRenderer[A]: + final override val companion = Renderer + + object Renderer extends CellRendererCompanion: + final override class CellInfo + override def labeled[A]() = new DefaultRenderer[A] with LabelRenderer[A] {} + + class DefaultRenderer[-A] extends Label with Renderer[A]: + override def componentFor(info: Renderer.CellInfo): Component = ??? + +class Tree extends Component +object Tree extends TreeRenderers From 3ac5f2def9222cdd25436f387ed6883d2d8687f4 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 12 Dec 2024 16:51:27 +0000 Subject: [PATCH 2/4] Teach AvoidMap to strip opaque alias refinements --- .../src/dotty/tools/dotc/core/TypeOps.scala | 21 +++++++++++++- tests/pos/i22068.less-min.scala | 21 ++++++++++++++ tests/pos/i22068.orig.scala | 29 +++++++++++++++++++ tests/pos/i22068.scala | 14 +++++++++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i22068.less-min.scala create mode 100644 tests/pos/i22068.orig.scala create mode 100644 tests/pos/i22068.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 7ae790c62a2c..e80cadcd3b8c 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -511,7 +511,26 @@ object TypeOps: tp else (if pre.isSingleton then NoType else tryWiden(tp, tp.prefix)).orElse { if (tp.isTerm && variance > 0 && !pre.isSingleton) - apply(tp.info.widenExpr) + tp.prefix match + case prefix: TermRef if prefix.name.is(InlineBinderName) && prefix.symbol.denot.is(Synthetic, butNot=InlineProxy) => + prefix.info.widenExpr.dealias match + case info: RefinedType => + // Strip refinements on an opaque alias proxy + // Using pos/i22068 as an example, + // Inliner#addOpaqueProxies add the following opaque alias proxy: + // val $proxy1: foos.type { type Foo[T] = String } = + // foos.$asInstanceOf[foos.type { type Foo[T] = String }] + // Then when InlineCall#expand creates a typed Inlined, + // we type avoid any local bindings, which includes that opaque alias proxy. + // To avoid that the replacement is a non-singleton RefinedType, + // we drop the refinements too and return foos.type. + // That way, when we inline `def m1` and we calculate the asSeenFrom + // of `b1.and(..)` b1 doesn't have an unstable prefix. + derivedSelect(tp, info.stripRefinement) + case _ => + apply(tp.info.widenExpr) + case _ => + apply(tp.info.widenExpr) else if (upper(pre).member(tp.name).exists) super.derivedSelect(tp, pre) else diff --git a/tests/pos/i22068.less-min.scala b/tests/pos/i22068.less-min.scala new file mode 100644 index 000000000000..e520d1d717c0 --- /dev/null +++ b/tests/pos/i22068.less-min.scala @@ -0,0 +1,21 @@ +class A +class B extends A +class C extends A + +object foos: + opaque type Tag[A] = String + object Tag: + inline given mkTag[A]: Tag[A] = ??? + type Full[A] = Tag[A] | Set[A] + sealed trait Set[A] extends Any + case class Union[A](tags: Seq[Tag[Any]]) extends AnyVal with Set[A]: + infix def and[B](t2: Full[B]): Unit = ??? + object Union: + inline given mkUnion[A]: Union[A] = ??? +import foos.Tag.* + +class Test: + inline def m1[K1, K2](using b1: Union[K1], b2: Union[K2]): Unit = + b1.and(b2) + + def t1(): Unit = m1[B | C, A] diff --git a/tests/pos/i22068.orig.scala b/tests/pos/i22068.orig.scala new file mode 100644 index 000000000000..9b2b54037a5a --- /dev/null +++ b/tests/pos/i22068.orig.scala @@ -0,0 +1,29 @@ +trait AnyFreeSpecLike: + inline implicit def convertToFreeSpecStringWrapper(s: String): FreeSpecStringWrapper = ??? + protected final class FreeSpecStringWrapper(string: String): + infix def in(testFun: => Any): Unit = ??? + + +import types.Tag.* +class TagTest extends AnyFreeSpecLike{ + inline def test[T1, T2](using k1: Union[T1], k2: Union[T2]): Unit = + "T1 <:< T2" in { + val kresult = k1 <:< k2 + ??? + } + class A + class B extends A + class C extends A + test[B | C, A] +} + +object types: + opaque type Tag[A] = String + object Tag: + inline given apply[A]: Tag[A] = ??? + type Full[A] = Tag[A] | Set[A] + sealed trait Set[A] extends Any + case class Union[A](tags: Seq[Tag[Any]]) extends AnyVal with Set[A]: + infix def <:<[B](t2: Full[B]): Boolean = ??? + object Union: + inline given apply[A]: Union[A] = ??? diff --git a/tests/pos/i22068.scala b/tests/pos/i22068.scala new file mode 100644 index 000000000000..1af942a2c46e --- /dev/null +++ b/tests/pos/i22068.scala @@ -0,0 +1,14 @@ +object foos: + opaque type Foo[T] = String + object bars: + class Bar1[A] { def and(b: Bar2): Unit = () } + class Bar2 + inline def mkBar1[A]: Bar1[A] = new Bar1[A] + def mkBar2 : Bar2 = new Bar2 +import foos.*, bars.* + +class Test: + inline def m1[X](b1: Bar1[X], b2: Bar2): Unit = + b1.and(b2) + + def t1(): Unit = m1(mkBar1[Int], mkBar2) From 56b60acbc975321a81e52abd5c88e1f785982f46 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 17 Dec 2024 14:11:52 +0000 Subject: [PATCH 3/4] Re-skolem type prefixes post-unpickling --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 3 ++- .../src/dotty/tools/dotc/inlines/Inliner.scala | 1 + tests/pos/i22070/macro.scala | 18 ++++++++++++++++++ tests/pos/i22070/usage.scala | 1 + 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i22070/macro.scala create mode 100644 tests/pos/i22070/usage.scala diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index de99ce0105ea..7e94bcfc6ae3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -395,9 +395,10 @@ class TreeUnpickler(reader: TastyReader, case TYPEREFin => val name = readName().toTypeName val prefix = readType() + def pre = if TypeOps.isLegalPrefix(prefix) then prefix else QualSkolemType(prefix) val space = readType() space.decl(name) match { - case symd: SymDenotation if prefix.isArgPrefixOf(symd.symbol) => TypeRef(prefix, symd.symbol) + case symd: SymDenotation if prefix.isArgPrefixOf(symd.symbol) => TypeRef(pre, symd.symbol) case _ => TypeRef(prefix, name, space.decl(name).asSeenFrom(prefix)) } case REFINEDtype => diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index e06e6b3e1615..1ea9b680e431 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -768,6 +768,7 @@ class Inliner(val call: tpd.Tree)(using Context): override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { val locked = ctx.typerState.ownedVars val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)) + selectionType(tree, qual1) // side-effect val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) val reducedProjection = reducer.reduceProjection(resNoReduce) if reducedProjection.isType then diff --git a/tests/pos/i22070/macro.scala b/tests/pos/i22070/macro.scala new file mode 100644 index 000000000000..bcf6ec6dd70f --- /dev/null +++ b/tests/pos/i22070/macro.scala @@ -0,0 +1,18 @@ +trait Featureful[T]: + def toFeatures(value: T): IArray[Float] + +object Featureful: + inline def derived[T](using scala.deriving.Mirror.Of[T]) = ${ derivedImpl[T] } + + import scala.quoted.* + private def derivedImpl[T: Type](using Quotes): Expr[Featureful[T]] = + import quotes.reflect.* + '{ + new Featureful[T]: + def toFeatures(value: T) = + val feats = IArray.empty[Featureful[?]] + val product = value.asInstanceOf[Product] + product.productIterator.zipWithIndex.foreach: (any, idx) => + feats(idx).toFeatures(any.asInstanceOf) + IArray.empty + } diff --git a/tests/pos/i22070/usage.scala b/tests/pos/i22070/usage.scala new file mode 100644 index 000000000000..6fc662dfcc13 --- /dev/null +++ b/tests/pos/i22070/usage.scala @@ -0,0 +1 @@ +case class Breaks(x: Boolean, y: Boolean) derives Featureful From 08fef874ca96dfcb3880f7dfc75fd6e89edcb6f6 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 18 Dec 2024 15:08:56 +0000 Subject: [PATCH 4/4] Refactor OpaqueProxy (de)construction --- .../src/dotty/tools/dotc/core/TypeOps.scala | 30 +++++------ .../dotty/tools/dotc/inlines/Inliner.scala | 52 +++++++++++++------ 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index e80cadcd3b8c..a7f41a71d7ce 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -512,23 +512,19 @@ object TypeOps: else (if pre.isSingleton then NoType else tryWiden(tp, tp.prefix)).orElse { if (tp.isTerm && variance > 0 && !pre.isSingleton) tp.prefix match - case prefix: TermRef if prefix.name.is(InlineBinderName) && prefix.symbol.denot.is(Synthetic, butNot=InlineProxy) => - prefix.info.widenExpr.dealias match - case info: RefinedType => - // Strip refinements on an opaque alias proxy - // Using pos/i22068 as an example, - // Inliner#addOpaqueProxies add the following opaque alias proxy: - // val $proxy1: foos.type { type Foo[T] = String } = - // foos.$asInstanceOf[foos.type { type Foo[T] = String }] - // Then when InlineCall#expand creates a typed Inlined, - // we type avoid any local bindings, which includes that opaque alias proxy. - // To avoid that the replacement is a non-singleton RefinedType, - // we drop the refinements too and return foos.type. - // That way, when we inline `def m1` and we calculate the asSeenFrom - // of `b1.and(..)` b1 doesn't have an unstable prefix. - derivedSelect(tp, info.stripRefinement) - case _ => - apply(tp.info.widenExpr) + case inlines.Inliner.OpaqueProxy(ref) => + // Strip refinements on an opaque alias proxy + // Using pos/i22068 as an example, + // Inliner#addOpaqueProxies add the following opaque alias proxy: + // val $proxy1: foos.type { type Foo[T] = String } = + // foos.$asInstanceOf[foos.type { type Foo[T] = String }] + // Then when InlineCall#expand creates a typed Inlined, + // we type avoid any local bindings, which includes that opaque alias proxy. + // To avoid that the replacement is a non-singleton RefinedType, + // we drop the refinements too and return foos.type. + // That way, when we inline `def m1` and we calculate the asSeenFrom + // of `b1.and(..)` b1 doesn't have an unstable prefix. + derivedSelect(tp, ref) case _ => apply(tp.info.widenExpr) else if (upper(pre).member(tp.name).exists) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 1ea9b680e431..a5beceba3e46 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -131,6 +131,39 @@ object Inliner: case _ => tree else super.transformInlined(tree) end InlinerMap + + object OpaqueProxy: + + def apply(ref: TermRef, cls: ClassSymbol, span: Span)(using Context): TermRef = + def openOpaqueAliases(selfType: Type): List[(Name, Type)] = selfType match + case RefinedType(parent, rname, TypeAlias(alias)) => + val opaq = cls.info.member(rname).symbol + if opaq.isOpaqueAlias then + (rname, alias.stripLazyRef.asSeenFrom(ref, cls)) + :: openOpaqueAliases(parent) + else Nil + case _ => Nil + val refinements = openOpaqueAliases(cls.givenSelfType) + val refinedType = refinements.foldLeft(ref: Type): (parent, refinement) => + RefinedType(parent, refinement._1, TypeAlias(refinement._2)) + val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType, span) + refiningSym.termRef + + def unapply(refiningRef: TermRef)(using Context): Option[TermRef] = + val refiningSym = refiningRef.symbol + if refiningSym.name.is(InlineBinderName) && refiningSym.is(Synthetic, butNot=InlineProxy) then + refiningRef.info match + case refinedType: RefinedType => refinedType.stripRefinement match + case ref: TermRef => Some(ref) + case _ => None + case _ => None + else + None + + end OpaqueProxy + + private[inlines] def newSym(name: Name, flags: FlagSet, info: Type, span: Span)(using Context): Symbol = + newSymbol(ctx.owner, name, flags, info, coord = span) end Inliner /** Produces an inlined version of `call` via its `inlined` method. @@ -189,7 +222,7 @@ class Inliner(val call: tpd.Tree)(using Context): private val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] private[inlines] def newSym(name: Name, flags: FlagSet, info: Type)(using Context): Symbol = - newSymbol(ctx.owner, name, flags, info, coord = call.span) + Inliner.newSym(name, flags, info, call.span) /** A binding for the parameter of an inline method. This is a `val` def for * by-value parameters and a `def` def for by-name parameters. `val` defs inherit @@ -351,20 +384,9 @@ class Inliner(val call: tpd.Tree)(using Context): && (forThisProxy || inlinedMethod.isContainedIn(cls)) && mapRef(ref).isEmpty then - def openOpaqueAliases(selfType: Type): List[(Name, Type)] = selfType match - case RefinedType(parent, rname, TypeAlias(alias)) => - val opaq = cls.info.member(rname).symbol - if opaq.isOpaqueAlias then - (rname, alias.stripLazyRef.asSeenFrom(ref, cls)) - :: openOpaqueAliases(parent) - else Nil - case _ => - Nil - val refinements = openOpaqueAliases(cls.givenSelfType) - val refinedType = refinements.foldLeft(ref: Type) ((parent, refinement) => - RefinedType(parent, refinement._1, TypeAlias(refinement._2)) - ) - val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType).asTerm + val refiningRef = OpaqueProxy(ref, cls, call.span) + val refiningSym = refiningRef.symbol.asTerm + val refinedType = refiningRef.info val refiningDef = ValDef(refiningSym, tpd.ref(ref).cast(refinedType), inferred = true).withSpan(span) inlining.println(i"add opaque alias proxy $refiningDef for $ref in $tp") bindingsBuf += refiningDef