From 893a4e3fd898a5e7298922f7786ccac23e3d3933 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Fri, 24 Jan 2025 12:22:53 +0100 Subject: [PATCH 1/3] Do not approximate prefixes when using memberType in reflect API --- .../src/dotty/tools/dotc/core/TypeOps.scala | 10 ++++---- .../src/dotty/tools/dotc/core/Types.scala | 7 ++++-- .../quoted/runtime/impl/QuotesImpl.scala | 2 +- tests/pos-macros/i22424/Macro_1.scala | 25 +++++++++++++++++++ tests/pos-macros/i22424/Test_2.scala | 4 +++ 5 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 tests/pos-macros/i22424/Macro_1.scala create mode 100644 tests/pos-macros/i22424/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index a7f41a71d7ce..86351428af9e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -31,7 +31,7 @@ object TypeOps: /** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec * for what this means. */ - final def asSeenFrom(tp: Type, pre: Type, cls: Symbol)(using Context): Type = { + final def asSeenFrom(tp: Type, pre: Type, cls: Symbol, approximateUnstablePrefixes: Boolean)(using Context): Type = { pre match { case pre: QualSkolemType => // When a selection has an unstable qualifier, the qualifier type gets @@ -42,7 +42,7 @@ object TypeOps: // compute the type as seen from the widened prefix, and in the rare // cases where this leads to an approximated type we recompute it with // the skolemized prefix. See the i6199* tests for usecases. - val widenedAsf = new AsSeenFromMap(pre.info, cls) + val widenedAsf = new AsSeenFromMap(pre.info, cls, approximateUnstablePrefixes) val ret = widenedAsf.apply(tp) if widenedAsf.approxCount == 0 then @@ -52,11 +52,11 @@ object TypeOps: case _ => } - new AsSeenFromMap(pre, cls).apply(tp) + new AsSeenFromMap(pre, cls, approximateUnstablePrefixes).apply(tp) } /** The TypeMap handling the asSeenFrom */ - class AsSeenFromMap(pre: Type, cls: Symbol)(using Context) extends ApproximatingTypeMap, IdempotentCaptRefMap { + class AsSeenFromMap(pre: Type, cls: Symbol, approximateUnstablePrefixes: Boolean)(using Context) extends ApproximatingTypeMap, IdempotentCaptRefMap { /** The number of range approximations in invariant or contravariant positions * performed by this TypeMap. @@ -81,7 +81,7 @@ object TypeOps: case pre: SuperType => toPrefix(pre.thistpe, cls, thiscls) case _ => if (thiscls.derivesFrom(cls) && pre.baseType(thiscls).exists) - if (variance <= 0 && !isLegalPrefix(pre)) + if (approximateUnstablePrefixes && variance <= 0 && !isLegalPrefix(pre)) approxCount += 1 range(defn.NothingType, pre) else pre diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 7c0c89da97ee..fbf77ca1721b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1106,11 +1106,14 @@ object Types extends TypeUtils { /** This type seen as if it were the type of a member of prefix type `pre` * declared in class `cls`. + * + * @param approximateUnstablePrefixes - needed for mamberType method in quotes reflect API, + * where we want to approximate less */ - final def asSeenFrom(pre: Type, cls: Symbol)(using Context): Type = { + final def asSeenFrom(pre: Type, cls: Symbol, approximateUnstablePrefixes: Boolean = true)(using Context): Type = { record("asSeenFrom") if (!cls.membersNeedAsSeenFrom(pre)) this - else TypeOps.asSeenFrom(this, pre, cls) + else TypeOps.asSeenFrom(this, pre, cls, approximateUnstablePrefixes) } // ----- Subtype-related -------------------------------------------- diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index bd94df123929..d34a1e2ab8f2 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -1837,7 +1837,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def termSymbol: Symbol = self.termSymbol def isSingleton: Boolean = self.isSingleton def memberType(member: Symbol): TypeRepr = - member.info.asSeenFrom(self, member.owner) + member.info.asSeenFrom(self, member.owner, approximateUnstablePrefixes = false) def baseClasses: List[Symbol] = self.baseClasses def baseType(cls: Symbol): TypeRepr = self.baseType(cls) def derivesFrom(cls: Symbol): Boolean = self.derivesFrom(cls) diff --git a/tests/pos-macros/i22424/Macro_1.scala b/tests/pos-macros/i22424/Macro_1.scala new file mode 100644 index 000000000000..634cfb0055c7 --- /dev/null +++ b/tests/pos-macros/i22424/Macro_1.scala @@ -0,0 +1,25 @@ + +import scala.quoted.* + +object MockMaker: + inline def inlineMock[T]: Unit = ${instance[T]} + transparent inline def transparentInlineMock[T]: Unit = ${instance[T]} + + def instance[T: Type](using quotes: Quotes): Expr[Unit] = + import quotes.reflect._ + val tpe = TypeRepr.of[T] + val symbol = tpe.typeSymbol.methodMember("innerTraitInOptions").head + tpe.memberType(symbol) match + case mt @ MethodType(_, args, _) => + assert(args.head.typeSymbol != TypeRepr.of[Nothing].typeSymbol, "argument is incorrectly approximated") + val shownType = mt.show + val expectedType = "(x: m.Embedded#ATrait[scala.Predef.String, scala.Int])m.Embedded#ATrait[scala.Predef.String, scala.Int]" + assert(shownType == expectedType, s"Incorrect type shown. Obtained: $shownType, Expected: $expectedType") + '{()} + +trait PolymorphicTrait { + trait Embedded { + trait ATrait[A, B] + def innerTraitInOptions(x: ATrait[String, Int]): ATrait[String, Int] + } +} diff --git a/tests/pos-macros/i22424/Test_2.scala b/tests/pos-macros/i22424/Test_2.scala new file mode 100644 index 000000000000..0a231d820381 --- /dev/null +++ b/tests/pos-macros/i22424/Test_2.scala @@ -0,0 +1,4 @@ +@main def Test = + val m = new PolymorphicTrait {} + MockMaker.inlineMock[m.Embedded] + MockMaker.transparentInlineMock[m.Embedded] From 17217b58bdde2937815443e69cca176f53bf58b0 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Tue, 4 Feb 2025 14:13:51 +0100 Subject: [PATCH 2/3] use thistype instead --- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 10 +++++----- compiler/src/dotty/tools/dotc/core/Types.scala | 7 ++----- .../src/scala/quoted/runtime/impl/QuotesImpl.scala | 7 ++++++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 86351428af9e..a7f41a71d7ce 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -31,7 +31,7 @@ object TypeOps: /** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec * for what this means. */ - final def asSeenFrom(tp: Type, pre: Type, cls: Symbol, approximateUnstablePrefixes: Boolean)(using Context): Type = { + final def asSeenFrom(tp: Type, pre: Type, cls: Symbol)(using Context): Type = { pre match { case pre: QualSkolemType => // When a selection has an unstable qualifier, the qualifier type gets @@ -42,7 +42,7 @@ object TypeOps: // compute the type as seen from the widened prefix, and in the rare // cases where this leads to an approximated type we recompute it with // the skolemized prefix. See the i6199* tests for usecases. - val widenedAsf = new AsSeenFromMap(pre.info, cls, approximateUnstablePrefixes) + val widenedAsf = new AsSeenFromMap(pre.info, cls) val ret = widenedAsf.apply(tp) if widenedAsf.approxCount == 0 then @@ -52,11 +52,11 @@ object TypeOps: case _ => } - new AsSeenFromMap(pre, cls, approximateUnstablePrefixes).apply(tp) + new AsSeenFromMap(pre, cls).apply(tp) } /** The TypeMap handling the asSeenFrom */ - class AsSeenFromMap(pre: Type, cls: Symbol, approximateUnstablePrefixes: Boolean)(using Context) extends ApproximatingTypeMap, IdempotentCaptRefMap { + class AsSeenFromMap(pre: Type, cls: Symbol)(using Context) extends ApproximatingTypeMap, IdempotentCaptRefMap { /** The number of range approximations in invariant or contravariant positions * performed by this TypeMap. @@ -81,7 +81,7 @@ object TypeOps: case pre: SuperType => toPrefix(pre.thistpe, cls, thiscls) case _ => if (thiscls.derivesFrom(cls) && pre.baseType(thiscls).exists) - if (approximateUnstablePrefixes && variance <= 0 && !isLegalPrefix(pre)) + if (variance <= 0 && !isLegalPrefix(pre)) approxCount += 1 range(defn.NothingType, pre) else pre diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index fbf77ca1721b..7c0c89da97ee 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1106,14 +1106,11 @@ object Types extends TypeUtils { /** This type seen as if it were the type of a member of prefix type `pre` * declared in class `cls`. - * - * @param approximateUnstablePrefixes - needed for mamberType method in quotes reflect API, - * where we want to approximate less */ - final def asSeenFrom(pre: Type, cls: Symbol, approximateUnstablePrefixes: Boolean = true)(using Context): Type = { + final def asSeenFrom(pre: Type, cls: Symbol)(using Context): Type = { record("asSeenFrom") if (!cls.membersNeedAsSeenFrom(pre)) this - else TypeOps.asSeenFrom(this, pre, cls, approximateUnstablePrefixes) + else TypeOps.asSeenFrom(this, pre, cls) } // ----- Subtype-related -------------------------------------------- diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index d34a1e2ab8f2..c36a1045ec50 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -1837,7 +1837,12 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def termSymbol: Symbol = self.termSymbol def isSingleton: Boolean = self.isSingleton def memberType(member: Symbol): TypeRepr = - member.info.asSeenFrom(self, member.owner, approximateUnstablePrefixes = false) + // we use thisType to avoid resolving otherwise unstable prefixes into Nothing + val classSymbol = self.classSymbol + member.info + .asSeenFrom(classSymbol.thisType, member.owner) + .substThis(classSymbol.asClass, self) // and we remove the previously added This(_) for compatibility + def baseClasses: List[Symbol] = self.baseClasses def baseType(cls: Symbol): TypeRepr = self.baseType(cls) def derivesFrom(cls: Symbol): Boolean = self.derivesFrom(cls) From 2545c8cfe1621d8fbf0e1ace4c2f1e80891e6f8d Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Fri, 28 Feb 2025 14:38:19 +0100 Subject: [PATCH 3/3] Adjust member.info (removing This) instead of changing self --- .../scala/quoted/runtime/impl/QuotesImpl.scala | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index c36a1045ec50..84ea374ea246 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -1694,7 +1694,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end SimpleSelectorTypeTest object SimpleSelector extends SimpleSelectorModule: - def apply(name: String): SimpleSelector = + def apply(name: String): SimpleSelector = withDefaultPos(untpd.ImportSelector(untpd.Ident(name.toTermName))) def unapply(x: SimpleSelector): Some[String] = Some(x.name.toString) end SimpleSelector @@ -1837,11 +1837,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def termSymbol: Symbol = self.termSymbol def isSingleton: Boolean = self.isSingleton def memberType(member: Symbol): TypeRepr = - // we use thisType to avoid resolving otherwise unstable prefixes into Nothing - val classSymbol = self.classSymbol - member.info - .asSeenFrom(classSymbol.thisType, member.owner) - .substThis(classSymbol.asClass, self) // and we remove the previously added This(_) for compatibility + // we replace thisTypes here to avoid resolving otherwise unstable prefixes into Nothing + val memberInfo = + if self.typeSymbol.isClassDef then + member.info.substThis(self.classSymbol.asClass, self) + else + member.info + memberInfo + .asSeenFrom(self, member.owner) def baseClasses: List[Symbol] = self.baseClasses def baseType(cls: Symbol): TypeRepr = self.baseType(cls)