Skip to content

Drop phase.isTyper use in isLegalPrefix/asf #21954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/CheckRealizable.scala
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 9 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
@@ -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)

2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
@@ -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 = {
29 changes: 13 additions & 16 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
@@ -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
@@ -5577,24 +5573,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
@@ -5603,7 +5600,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 {
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/Recheck.scala
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions compiler/test/dotc/pos-test-pickling.blacklist
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ t5031_2.scala
i16997.scala
i7414.scala
i17588.scala
i8300.scala
i9804.scala
i13433.scala
i16649-irrefutable.scala
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions tests/neg/6314-6.check
Original file line number Diff line number Diff line change
@@ -4,13 +4,13 @@
|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
| ^
|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
| )
2 changes: 1 addition & 1 deletion tests/neg/i6225.scala
Original file line number Diff line number Diff line change
@@ -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
}
30 changes: 30 additions & 0 deletions tests/pos/i17222.2.scala
Original file line number Diff line number Diff line change
@@ -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??
39 changes: 39 additions & 0 deletions tests/pos/i17222.3.scala
Original file line number Diff line number Diff line change
@@ -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
71 changes: 71 additions & 0 deletions tests/pos/i17222.4.scala
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions tests/pos/i17222.5.scala
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions tests/pos/i17222.8.scala
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions tests/pos/i17222.izumi.min.scala
Original file line number Diff line number Diff line change
@@ -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]
59 changes: 59 additions & 0 deletions tests/pos/i17222.izumi.rep/InspectorBase.scala
Original file line number Diff line number Diff line change
@@ -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)
}

}
331 changes: 331 additions & 0 deletions tests/pos/i17222.izumi.rep/ReflectionUtil.scala
Original file line number Diff line number Diff line change
@@ -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*)
}

}

}
33 changes: 33 additions & 0 deletions tests/pos/i17222.scala
Original file line number Diff line number Diff line change
@@ -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