diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index d8241f3ff304..81b03d765676 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -131,8 +131,8 @@ class CheckRealizable(using Context) { /** `Realizable` if `tp` has good bounds, a `HasProblem...` instance * pointing to a bad bounds member otherwise. "Has good bounds" means: * - * - all type members have good bounds (except for opaque helpers) - * - all refinements of the underlying type have good bounds (except for opaque companions) + * - all type members have good bounds + * - all refinements of the underlying type have good bounds * - all base types are class types, and if their arguments are wildcards * they have good bounds. * - base types do not appear in multiple instances with different arguments. diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 17d427513e58..8414c3795f49 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -369,7 +369,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling } compareWild case tp2: LazyRef => - isBottom(tp1) || !tp2.evaluating && recur(tp1, tp2.ref) + isBottom(tp1) + || !tp2.evaluating && recur(tp1, tp2.ref) case CapturingType(_, _) => secondTry case tp2: AnnotatedType if !tp2.isRefining => @@ -489,7 +490,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // If `tp1` is in train of being evaluated, don't force it // because that would cause an assertionError. Return false instead. // See i859.scala for an example where we hit this case. - tp2.isRef(AnyClass, skipRefined = false) + tp2.isAny || !tp1.evaluating && recur(tp1.ref, tp2) case AndType(tp11, tp12) => if tp11.stripTypeVar eq tp12.stripTypeVar then recur(tp11, tp2) @@ -2133,11 +2134,16 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // resort to reflection to invoke the member. And Java reflection needs to know exact // erased parameter types. See neg/i12211.scala. Other reflection algorithms could // conceivably dispatch without knowing precise parameter signatures. One can signal - // this by inheriting from the `scala.reflect.SignatureCanBeImprecise` marker trait, + // this by inheriting from the `scala.Selectable.WithoutPreciseParameterTypes` marker trait, // in which case the signature test is elided. + // We also relax signature checking when checking bounds, + // for instance in tests/pos/i17222.izumi.min.scala + // the `go` method info as seen from `Foo` is `>: (in: Any): Unit <: (Nothing): Unit` + // So the parameter types conform but their signatures don't match. def sigsOK(symInfo: Type, info2: Type) = tp2.underlyingClassRef(refinementOK = true).member(name).exists || tp2.derivesFrom(defn.WithoutPreciseParameterTypesClass) + || ctx.mode.is(Mode.CheckBoundsOrSelfType) || symInfo.isInstanceOf[MethodType] && symInfo.signature.consistentParams(info2.signature) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 1106ba68fb97..7ae790c62a2c 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -124,7 +124,7 @@ object TypeOps: } def isLegalPrefix(pre: Type)(using Context): Boolean = - pre.isStable || !ctx.phase.isTyper + pre.isStable /** Implementation of Types#simplified */ def simplify(tp: Type, theMap: SimplifyMap | Null)(using Context): Type = { diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 821363c74a25..e497c541166c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -99,12 +99,8 @@ object Types extends TypeUtils { // ----- Tests ----------------------------------------------------- // // debug only: a unique identifier for a type -// val uniqId = { -// nextId = nextId + 1 -// if (nextId == 19555) -// println("foo") -// nextId -// } +// val uniqId = { nextId = nextId + 1; nextId } +// if uniqId == 19555 then trace.dumpStack() /** A cache indicating whether the type was still provisional, last time we checked */ @sharable private var mightBeProvisional = true @@ -5578,24 +5574,25 @@ object Types extends TypeUtils { } def & (that: TypeBounds)(using Context): TypeBounds = + val lo1 = this.lo.stripLazyRef + val lo2 = that.lo.stripLazyRef + val hi1 = this.hi.stripLazyRef + val hi2 = that.hi.stripLazyRef + // This will try to preserve the FromJavaObjects type in upper bounds. // For example, (? <: FromJavaObjects | Null) & (? <: Any), // we want to get (? <: FromJavaObjects | Null) intead of (? <: Any), // because we may check the result <:< (? <: Object | Null) later. - if this.hi.containsFromJavaObject - && (this.hi frozen_<:< that.hi) - && (that.lo frozen_<:< this.lo) then + if hi1.containsFromJavaObject && (hi1 frozen_<:< hi2) && (lo2 frozen_<:< lo1) then // FromJavaObject in tp1.hi guarantees tp2.hi <:< tp1.hi // prefer tp1 if FromJavaObject is in its hi this - else if that.hi.containsFromJavaObject - && (that.hi frozen_<:< this.hi) - && (this.lo frozen_<:< that.lo) then + else if hi2.containsFromJavaObject && (hi2 frozen_<:< hi1) && (lo1 frozen_<:< lo2) then // Similarly, prefer tp2 if FromJavaObject is in its hi that - else if (this.lo frozen_<:< that.lo) && (that.hi frozen_<:< this.hi) then that - else if (that.lo frozen_<:< this.lo) && (this.hi frozen_<:< that.hi) then this - else TypeBounds(this.lo | that.lo, this.hi & that.hi) + else if (lo1 frozen_<:< lo2) && (hi2 frozen_<:< hi1) then that + else if (lo2 frozen_<:< lo1) && (hi1 frozen_<:< hi2) then this + else TypeBounds(lo1 | lo2, hi1 & hi2) def | (that: TypeBounds)(using Context): TypeBounds = if ((this.lo frozen_<:< that.lo) && (that.hi frozen_<:< this.hi)) this @@ -5604,7 +5601,7 @@ object Types extends TypeUtils { override def & (that: Type)(using Context): Type = that match { case that: TypeBounds => this & that - case _ => super.& (that) + case _ => super.&(that) } override def | (that: Type)(using Context): Type = that match { diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 8df9e5966920..be1d9d8bee54 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -217,10 +217,10 @@ abstract class Recheck extends Phase, SymTransformer: sharpen: Denotation => Denotation)(using Context): Type = if name.is(OuterSelectName) then tree.tpe else - //val pre = ta.maybeSkolemizePrefix(qualType, name) + val pre = ta.maybeSkolemizePrefix(qualType, name) val mbr = sharpen( - qualType.findMember(name, qualType, + qualType.findMember(name, pre, excluded = if tree.symbol.is(Private) then EmptyFlags else Private )).suchThat(tree.symbol == _) val newType = tree.tpe match diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 032b53150e49..ebdd414ea7f2 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -23,6 +23,7 @@ t5031_2.scala i16997.scala i7414.scala i17588.scala +i8300.scala i9804.scala i13433.scala i16649-irrefutable.scala diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index d3c93aaba8c7..ee12755c7f98 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -577,7 +577,8 @@ trait ClassLikeSupport: def unwrapMemberInfo(c: ClassDef, symbol: Symbol): MemberInfo = - val baseTypeRepr = typeForClass(c).memberType(symbol) + val qualTypeRepr = if c.symbol.isClassDef then This(c.symbol).tpe else typeForClass(c) + val baseTypeRepr = qualTypeRepr.memberType(symbol) def isSyntheticEvidence(name: String) = if !name.startsWith(NameKinds.ContextBoundParamName.separator) then false else diff --git a/tests/neg/6314-6.check b/tests/neg/6314-6.check index 7d6bd182173d..df988f1db9dd 100644 --- a/tests/neg/6314-6.check +++ b/tests/neg/6314-6.check @@ -4,7 +4,7 @@ |object creation impossible, since def apply(fa: String): Int in trait XX in object Test3 is not defined |(Note that | parameter String in def apply(fa: String): Int in trait XX in object Test3 does not match - | parameter Test3.Bar[X & Object with Test3.YY {...}#Foo] in def apply(fa: Test3.Bar[X & YY.this.Foo]): Test3.Bar[Y & YY.this.Foo] in trait YY in object Test3 + | parameter Test3.Bar[X & (X & Y)] in def apply(fa: Test3.Bar[X & YY.this.Foo]): Test3.Bar[Y & YY.this.Foo] in trait YY in object Test3 | ) -- Error: tests/neg/6314-6.scala:52:3 ---------------------------------------------------------------------------------- 52 | (new YY {}).boom // error: object creation impossible @@ -12,5 +12,5 @@ |object creation impossible, since def apply(fa: String): Int in trait XX in object Test4 is not defined |(Note that | parameter String in def apply(fa: String): Int in trait XX in object Test4 does not match - | parameter Test4.Bar[X & Object with Test4.YY {...}#FooAlias] in def apply(fa: Test4.Bar[X & YY.this.FooAlias]): Test4.Bar[Y & YY.this.FooAlias] in trait YY in object Test4 + | parameter Test4.Bar[X & (X & Y)] in def apply(fa: Test4.Bar[X & YY.this.FooAlias]): Test4.Bar[Y & YY.this.FooAlias] in trait YY in object Test4 | ) diff --git a/tests/neg/i6225.scala b/tests/neg/i6225.scala index 148a484fd0f1..bb936c9a79b1 100644 --- a/tests/neg/i6225.scala +++ b/tests/neg/i6225.scala @@ -1,4 +1,4 @@ -object O1 { +object O1 { // error: cannot be instantiated type A[X] = X opaque type T = A // error: opaque type alias must be fully applied } diff --git a/tests/pos/i17222.2.scala b/tests/pos/i17222.2.scala new file mode 100644 index 000000000000..34db494750c4 --- /dev/null +++ b/tests/pos/i17222.2.scala @@ -0,0 +1,30 @@ +import scala.compiletime.* + +trait Reader[-In, Out] + +trait A: + type T + type F[X] + type Q = F[T] + +object Reader: + + given [X]: Reader[A { type Q = X }, X] with {} + +object Test: + + trait B[X] extends A: + type T = X + + trait C extends A: + type F[X] = X + + trait D[X] extends B[X] with C + + val d = new D[Int] {} + val bc = new B[Int] with C + + summonAll[(Reader[d.type, Int], Reader[d.type, Int])] // works + summonAll[(Reader[bc.type, Int], Reader[bc.type, Int])] // error + summonInline[Reader[d.type, Int]] // works + summonInline[Reader[bc.type, Int]] // works?? diff --git a/tests/pos/i17222.3.scala b/tests/pos/i17222.3.scala new file mode 100644 index 000000000000..7ca85f65278f --- /dev/null +++ b/tests/pos/i17222.3.scala @@ -0,0 +1,39 @@ +import scala.compiletime.* + +trait Reader[-In, Out] + +trait A: + type T + type F[X] + type Q = F[T] + +object Reader: + + given [X]: Reader[A { type Q = X }, X] with {} + +object Test: + + trait B[X] extends A: + type T = X + + trait C extends A: + type F[X] = X + + trait D[X] extends B[X] with C + + val d = new D[Int] {} + val bc = new B[Int] with C + + case class Box[T](value: T) + + /** compiletime.summonAll, but with one case */ + inline def summonOne[T <: Box[?]]: T = + val res = + inline erasedValue[T] match + case _: Box[t] => summonInline[t] + end match + Box(res).asInstanceOf[T] + end summonOne + + summonOne[Box[Reader[d.type, Int]]] // works + summonOne[Box[Reader[bc.type, Int]]] // errors diff --git a/tests/pos/i17222.4.scala b/tests/pos/i17222.4.scala new file mode 100644 index 000000000000..209425d47915 --- /dev/null +++ b/tests/pos/i17222.4.scala @@ -0,0 +1,71 @@ +import scala.compiletime.* + +trait Reader[-In, Out] + +trait A: + type T + type F[X] + type Q = F[T] + +given [X]: Reader[A { type Q = X }, X] with {} + +case class Box[T](x: T) + +/** compiletime.summonAll, but with one case */ +inline def summonOne[T]: T = + val res = + inline erasedValue[T] match + case _: Box[t] => summonInline[t] + end match + Box(res).asInstanceOf[T] +end summonOne + + +@main def main = + + + trait B[X] extends A: + type T = X + + trait C extends A: + type F[X] = X + + + val bc = new B[Int] with C + + summonOne[Box[Reader[bc.type, Int]]] // errors + + + val bc2: A { type Q = Int } = new B[Int] with C + + summonOne[Box[Reader[bc2.type, Int]]] // works + + + object BC extends B[Int] with C + + summonOne[Box[Reader[BC.type, Int]]] // works + + + val a = new A: + type T = Int + type F[X] = X + + summonOne[Box[Reader[a.type, Int]]] // works + + + val b = new B[Int]: + type F[X] = X + + summonOne[Box[Reader[b.type, Int]]] // works + + + val ac = new A with C: + type T = Int + + summonOne[Box[Reader[ac.type, Int]]] // works + + + trait D[X] extends B[X] with C + val d = new D[Int] {} + + summonOne[Box[Reader[d.type, Int]]] // works diff --git a/tests/pos/i17222.5.scala b/tests/pos/i17222.5.scala new file mode 100644 index 000000000000..dc608e94235c --- /dev/null +++ b/tests/pos/i17222.5.scala @@ -0,0 +1,26 @@ +import scala.compiletime.* + +trait Reader[-In, Out] + +trait A: + type T + type F[X] + type Q = F[T] + +given [X]: Reader[A { type Q = X }, X] with {} + +case class Box[T](x: T) + +inline def summonOne[T]: T = + summonInline[T] +end summonOne + +@main def main = + trait B[X] extends A: + type T = X + trait C extends A: + type F[X] = X + + val bc = new B[Int] with C + summonInline[Reader[bc.type, Int]] // (I) Works + summonOne[Reader[bc.type, Int]] // (II) Errors diff --git a/tests/pos/i17222.8.scala b/tests/pos/i17222.8.scala new file mode 100644 index 000000000000..a415a78e0703 --- /dev/null +++ b/tests/pos/i17222.8.scala @@ -0,0 +1,18 @@ +import scala.compiletime.* + +trait A: + type F + type Q = F + +trait Reader[-In, Out] +object Reader: + given [X]: Reader[A { type Q = X }, X] with {} + +class Test: + //type BC = A { type F = Int } & A // ok + type BC = A & A { type F = Int } // fail, also ok when manually de-aliased + + inline def summonOne: Unit = summonInline[Reader[BC, Int]] + + def t1(): Unit = summonInline[Reader[BC, Int]] // ok + def t2(): Unit = summonOne // error diff --git a/tests/pos/i17222.izumi.min.scala b/tests/pos/i17222.izumi.min.scala new file mode 100644 index 000000000000..06eadca73130 --- /dev/null +++ b/tests/pos/i17222.izumi.min.scala @@ -0,0 +1,7 @@ +class Foo: + type In + type Bar = { def go(in: In): Unit } + type False = false + +class Test: + def t1: Unit = valueOf[Foo#False] diff --git a/tests/pos/i17222.izumi.rep/InspectorBase.scala b/tests/pos/i17222.izumi.rep/InspectorBase.scala new file mode 100644 index 000000000000..56075831898d --- /dev/null +++ b/tests/pos/i17222.izumi.rep/InspectorBase.scala @@ -0,0 +1,59 @@ +package izumi.reflect.dottyreflection + +import scala.quoted.Quotes + +trait InspectorBase extends ReflectionUtil { + + val qctx: Quotes + import qctx.reflect._ + + protected def shift: Int + + // FIXME reimplement TrivialMacroLogger on Scala 3 + inline def debug: debug = valueOf[debug] + final type debug = false + + // println instead of report.info because report.info eats all the subsequent report.info's after first. + inline final protected def logStart(inline s: String): Unit = { + inline if (debug) println(" " * shift + currentPositionStr + s) + } + + inline final protected def log(inline s: String): Unit = { + inline if (debug) println(" " * shift + currentPositionStr + " -> " + s) + } + + inline final protected def logTpeAttrs[T](inline typeRepr: TypeRepr): Unit = { + inline if (debug) { + val tree = TypeTree.of(using typeRepr.asType) + val symbol = tree.symbol + System + .err.println( + currentPositionStr + ": " + + s"Attrs[${tree.show}]: type=${symbol.isType}, term=${symbol.isTerm}, packageDef=${symbol.isPackageDef}, classDef=${symbol.isClassDef}, typeDef=${symbol.isValDef}, defdef=${symbol.isDefDef}, bind=${symbol.isBind}, nosymbol=${symbol.isNoSymbol}" + ) + } + } + + private def currentPositionStr: String = { + val pos = qctx.reflect.Position.ofMacroExpansion + s"${pos.sourceFile.name}:${pos.endLine}" + } + +} + +object InspectorBase { + + private[reflect] inline def ifDebug[A](inline f: => Unit): Unit = { + inline if (valueOf[InspectorBase#debug]) { +//[error] ^^^^^^^^^^^^^ +//[error] izumi.reflect.dottyreflection.InspectorBase is not a legal path +//[error] since it has a member InternalTypeRefOrParamRef with possibly conflicting bounds Object{def underlying(ctx: Any): Nothing} <: ... <: Object{def underlying(ctx: Nothing): Matchable} + f + } + } + + private[reflect] inline def log(inline shift: Int, s: String): Unit = { + inline if (valueOf[InspectorBase#debug]) println(" " * shift + " -> " + s) + } + +} diff --git a/tests/pos/i17222.izumi.rep/ReflectionUtil.scala b/tests/pos/i17222.izumi.rep/ReflectionUtil.scala new file mode 100644 index 000000000000..c8ff667191af --- /dev/null +++ b/tests/pos/i17222.izumi.rep/ReflectionUtil.scala @@ -0,0 +1,331 @@ +package izumi.reflect.dottyreflection + +import scala.annotation.{tailrec, unused} +import scala.collection.immutable.Queue +import scala.quoted.Quotes + +private[dottyreflection] trait ReflectionUtil { this: InspectorBase => + + import qctx.reflect.* + + private final lazy val ignoredInIntersections0: Set[TypeRepr] = { + Set( + defn.AnyClass.typeRef, + defn.MatchableClass.typeRef, + defn.AnyRefClass.typeRef, + defn.ObjectClass.typeRef + ) + } + def ignoredInIntersections(repr: qctx.reflect.TypeRepr): Boolean = { + ignoredInIntersections0.exists(_ =:= repr) + } + def ignoredInUnions(repr: qctx.reflect.TypeRepr): Boolean = { + repr =:= defn.NothingClass.typeRef + } + + protected final def flattenAnd(tpe: TypeRepr): List[TypeRepr] = + tpe.dealias match { + case AndType(lhs, rhs) => flattenAnd(lhs) ++ flattenAnd(rhs) + case _ => List(tpe) + } + + protected final def flattenOr(tpe: TypeRepr): List[TypeRepr] = + tpe.dealias match { + case OrType(lhs, rhs) => flattenOr(lhs) ++ flattenOr(rhs) + case _ => List(tpe) + } + + protected final def intersectionUnionRefinementClassPartsOf(tpe: TypeRepr): List[TypeRepr] = { + tpe.dealias match { + case AndType(lhs, rhs) => + intersectionUnionRefinementClassPartsOf(lhs) ++ intersectionUnionRefinementClassPartsOf(rhs) + case OrType(lhs, rhs) => + intersectionUnionRefinementClassPartsOf(lhs) ++ intersectionUnionRefinementClassPartsOf(rhs) + case refinement: Refinement => + intersectionUnionRefinementClassPartsOf(refinement.parent) + case _ => + List(tpe) + } + } + + protected final def refinementInfoToParts(tpe0: TypeRepr): List[TypeRepr] = { + tpe0 match { + case ByNameType(tpe) => + refinementInfoToParts(tpe) + case MethodType(_, args, res) => + args.flatMap(refinementInfoToParts) ++ refinementInfoToParts(res) + case PolyType(_, tbounds, res) => + // FIXME we need to do FullDbInspector.inspectTypeReprToFullBases.lambdify/LightTypeTagImpl.makeLambdaOnlyBases.makeLambdaParents + // to wrap the unresolved type params in `res` into a lambda. + // As is, if type parameters are used in `res`, we'll add lots of trash types into db + tbounds.flatMap { case TypeBounds(lo, hi) => List(lo, hi) } ++ refinementInfoToParts(res) + case tpe => + List(tpe) + } + } + + protected final def flattenRefinements(ref: Refinement): (Queue[(Symbol, String, TypeRepr)], TypeRepr) = { + val refinementDecl = (ref.typeSymbol, ref.name, ref.info) + ref.parent match { + case innerRefinement: Refinement => + val (innerRefs, nonRefinementParent) = flattenRefinements(innerRefinement) + (innerRefs :+ refinementDecl, nonRefinementParent) + case nonRefinementParent => + (Queue(refinementDecl), nonRefinementParent) + } + } + + protected final def allPartsStrong(outerOwnerClassDefs: Set[Symbol], typeRepr: TypeRepr): Boolean = { + ReflectionUtil.allPartsStrong(using qctx)(shift, outerOwnerClassDefs, Set.empty, typeRepr) + } + + protected final def getClassDefOwners(symbol: Symbol): Set[Symbol] = { + ReflectionUtil.getClassDefOwners(using qctx)(symbol) + } + + import ReflectionUtil.reflectiveUncheckedNonOverloadedSelectable + import InternalContext.InternalContext + + extension (typeRef: TypeRef | ParamRef) { + protected final def _underlying: TypeRepr = { + // This works as a substitution for `TypeRef#underlying` call, + // but I'm not sure if it's a reliable substitution. + +// typeRef.typeSymbol.owner._typeRef.memberType(typeRef.typeSymbol) + + // No, It's not a reliable substitution. When used on a TypeParamRef it returns Any instead of the underlying TypeBounds + // https://github.com/lampepfl/dotty/issues/15799 + +// val underlying = typeRef +// .getClass.getMethods.collect { case m if m.getName == "underlying" => m }.head.invoke( +// typeRef, +// qctx.getClass.getMethods.collect { case m if m.getName == "ctx" => m }.head.invoke(qctx) +// ) +// underlying.asInstanceOf[TypeRepr] + + typeRef.asInstanceOf[InternalTypeRefOrParamRef].underlying(qctx._ctx) + } + } + + extension (typeRepr: TypeRepr) { + protected final def _paramVariancesIfHKTypeLambda: Option[List[Flags]] = { + try { + val params = typeRepr.asInstanceOf[InternalHKTypeLambda].typeParams + val flags = params.map(_.paramVariance(qctx._ctx)) + Some(flags) + } catch { + case _: NoSuchMethodException => None + } + } + + @tailrec + protected final def _dealiasSimplifiedFull: TypeRepr = { +// val res = typeRepr.dealias.simplified + // simplified does everything below functions do, with exception of `_removeTautologicalUnions` for some reason + // All of these would be more useful, if not for forced type simplification on implicit macro - https://github.com/lampepfl/dotty/issues/17544 + val res = typeRepr.dealias._removeTautologicalIntersections._removeTautologicalUnions._simplifyMatchCase + if (res.asInstanceOf[AnyRef] eq typeRepr.asInstanceOf[AnyRef]) { + res + } else { + res._dealiasSimplifiedFull + } + } + + // Calling .simplified will remove too many intersections - we only want to remove those with Any/AnyRef/Object/Matchable + @tailrec private def _removeTautologicalIntersections: TypeRepr = { + typeRepr match { + case AndType(a, b) => + if (ignoredInIntersections(a)) { + b._removeTautologicalIntersections + } else if (ignoredInIntersections(b)) { + a._removeTautologicalIntersections + } else { + removeTautologicalIntersectionsNonTailRec(a, b) + } + case _ => + typeRepr + } + } + + private def removeTautologicalIntersectionsNonTailRec(a: TypeRepr, b: TypeRepr): TypeRepr = { + val a0 = a._removeTautologicalIntersections + val b0 = b._removeTautologicalIntersections + if ((a.asInstanceOf[AnyRef] ne a0.asInstanceOf[AnyRef]) || (b.asInstanceOf[AnyRef] ne b0.asInstanceOf[AnyRef])) { + AndType(a0, b0) + } else { + typeRepr + } + } + + @tailrec private def _removeTautologicalUnions: TypeRepr = { + typeRepr match { + case OrType(a, b) => + if (ignoredInUnions(a)) { + b._removeTautologicalUnions + } else if (ignoredInUnions(b)) { + a._removeTautologicalUnions + } else { + removeTautologicaUnionsNonTailRec(a, b) + } + case _ => + typeRepr + } + } + + private def removeTautologicaUnionsNonTailRec(a: TypeRepr, b: TypeRepr): TypeRepr = { + val superA = ignoredInIntersections(a) + val superB = ignoredInIntersections(b) + if (superA && superB) { + (if (a <:< b) b else a)._removeTautologicalUnions + } else if (superA) { + a + } else if (superB) { + b + } else { + val a0 = a._removeTautologicalUnions + val b0 = b._removeTautologicalUnions + if ((a.asInstanceOf[AnyRef] ne a0.asInstanceOf[AnyRef]) || (b.asInstanceOf[AnyRef] ne b0.asInstanceOf[AnyRef])) { + AndType(a0, b0) + } else { + typeRepr + } + } + } + + inline private def _simplifyMatchCase: TypeRepr = { + typeRepr match { + case _: MatchCase | _: MatchType => + // no other way to evaluate a match type other than calling simplified, + // even though that'll also cause a collapse of tautological intersections + // other than with Any/AnyRef/Object/Matchable + typeRepr.simplified + case _ => + typeRepr + } + } + + } + + extension (qctx: Quotes) { + final def _ctx: InternalContext = qctx.asInstanceOf[{ def ctx: InternalContext }].ctx + } + + type InternalTypeRefOrParamRef = { + def underlying(ctx: InternalContext): TypeRepr + } + + type InternalHKTypeLambda = { + val typeParams: List[InternalLambdaParam] + } + + type InternalLambdaParam = { + def paramVariance(ctx: InternalContext): Flags + } + + object InternalContext { + opaque type InternalContext = Any + } + +} + +private[reflect] object ReflectionUtil { + + private[reflect] inline implicit def reflectiveUncheckedNonOverloadedSelectable(x: Any): UncheckedNonOverloadedSelectable = new UncheckedNonOverloadedSelectable(x) + + /** + * Returns true if the given type contains no type parameters + * (this means the type is not "weak" https://stackoverflow.com/questions/29435985/weaktypetag-v-typetag) + */ + private[reflect] def allPartsStrong( + using qctx: Quotes + )(shift: Int, + outerOwnerClassDefs: Set[qctx.reflect.Symbol], + outerLambdas: Set[qctx.reflect.TypeRepr], + typeRepr: qctx.reflect.TypeRepr + ): Boolean = { + import qctx.reflect.* + typeRepr.dealias match { + case x if topLevelWeakType(outerOwnerClassDefs, outerLambdas, x) => false + case AppliedType(tpe, args) => + allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) && args.forall(allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, _)) + case AndType(lhs, rhs) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, lhs) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, rhs) + case OrType(lhs, rhs) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, lhs) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, rhs) + case TypeRef(tpe, _) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) + case TermRef(tpe, _) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) + case ThisType(tpe) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) + case NoPrefix() => true + case TypeBounds(lo, hi) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, lo) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, hi) + case lam @ TypeLambda(_, _, body) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas + lam, body) + case Refinement(parent, _, tpe) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, parent) + case ByNameType(tpe) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) + case strange => + InspectorBase.log(shift, s"Got unknown type component when checking strength: $strange") + true + } + } + + private[reflect] def topLevelWeakType( + using qctx: Quotes + )(outerOwnerClassDefs: Set[qctx.reflect.Symbol], + outerLambdas: Set[qctx.reflect.TypeRepr], + typeRepr: qctx.reflect.TypeRepr + ): Boolean = { + import qctx.reflect.* + typeRepr match { + case x if x.typeSymbol.isTypeParam => + x match { + case t: ParamRef if outerLambdas.contains(t.binder) => false + case _ => true + } + // we regard abstract types like T in trait X { type T; Tag[this.T] } - when we are _inside_ the definition template + // as 'type parameters' too. So that you could define `implicit def tagForT: Tag[this.T]` and the tag would be resolved + // to this implicit correctly, instead of generating a useless `X::this.type::T` tag. + // TODO: Due to https://github.com/lampepfl/dotty/issues/16107 not being fixed we have to make sure we're actually + // inside the definition of the this-type prefix to count it as 'weak' - unlike Scala 2 we're not protected + // from this-types leaking in and have to carry the owner chain here - until that issue is fixed. + case x @ TypeRef(ThisType(prefix), _) if x.typeSymbol.isAbstractType && !x.typeSymbol.isClassDef && outerOwnerClassDefs.contains(prefix.typeSymbol) => + true + case _ => false + } + } + + private[reflect] def getClassDefOwners(using qctx: Quotes)(symbol: qctx.reflect.Symbol): Set[qctx.reflect.Symbol] = { + Iterator + .iterate(symbol) { + s => + val owner = s.owner + if (owner == null || owner.isNoSymbol || owner == qctx.reflect.defn.RootClass) { + null.asInstanceOf[qctx.reflect.Symbol] + } else { + owner + } + } + .takeWhile(_ ne null) + .filter(s => s.isClassDef && !s.isAbstractType) + .toSet + } + + private[reflect] final class UncheckedNonOverloadedSelectable(private val selectable: Any) extends AnyVal with Selectable { + + inline def selectDynamic(name: String): Any = { + applyDynamic(name)() + } + + def applyDynamic(name: String, @unused paramTypes: Class[_]*)(args: Any*): Any = { + val cls = selectable.getClass + val method = { + if (args.isEmpty) { + cls.getMethod(name) + } else { + cls.getMethods.collectFirst { case m if m.getName == name => m } match { + case Some(m) => m + case None => throw new NoSuchMethodException(s"No method named `$name` found in class `$cls`") + } + } + } + method.invoke(selectable, args*) + } + + } + +} diff --git a/tests/pos/i17222.scala b/tests/pos/i17222.scala new file mode 100644 index 000000000000..2af9fc2861a8 --- /dev/null +++ b/tests/pos/i17222.scala @@ -0,0 +1,33 @@ +import scala.deriving.Mirror +import scala.compiletime.* + +trait Reader[-In, Out] + +trait A: + type T + type F[X] + type Q = F[T] + +object Reader: + + given [X]: Reader[A { type Q = X }, X] with {} + + type Map2[Tup1 <: Tuple, Tup2 <: Tuple, F[_, _]] <: Tuple = (Tup1, Tup2) match + case (h1 *: t1, h2 *: t2) => F[h1, h2] *: Map2[t1, t2, F] + case (EmptyTuple, EmptyTuple) => EmptyTuple + + inline given productReader[In <: Product, Out <: Product](using mi: Mirror.ProductOf[In])(using mo: Mirror.ProductOf[Out]): Reader[In, Out] = + summonAll[Map2[mi.MirroredElemTypes, mo.MirroredElemTypes, Reader]] + ??? + +object Test: + + trait B[X] extends A: + type T = X + + trait C extends A: + type F[X] = X + + val bc = new B[Int] with C + + summon[Reader[(bc.type, bc.type), (Int, Int)]] // fails