diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 69fc29c9318c..4ed01a5fbe0d 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -556,9 +556,12 @@ trait ConstraintHandling { inst end approximation - private def isTransparent(tp: Type)(using Context): Boolean = tp match - case AndType(tp1, tp2) => isTransparent(tp1) && isTransparent(tp2) - case _ => tp.typeSymbol.isTransparentClass && !tp.isLambdaSub + private def isTransparent(tp: Type, traitOnly: Boolean)(using Context): Boolean = tp match + case AndType(tp1, tp2) => + isTransparent(tp1, traitOnly) && isTransparent(tp2, traitOnly) + case _ => + val cls = tp.underlyingClassRef(refinementOK = false).typeSymbol + cls.isTransparentClass && (!traitOnly || cls.is(Trait)) /** If `tp` is an intersection such that some operands are transparent trait instances * and others are not, replace as many transparent trait instances as possible with Any @@ -567,28 +570,27 @@ trait ConstraintHandling { * types (since in this case the type was not a true intersection of transparent traits * and other types to start with). */ - def dropTransparentClasses(tp: Type, bound: Type)(using Context): Type = + def dropTransparentTraits(tp: Type, bound: Type)(using Context): Type = var kept: Set[Type] = Set() // types to keep since otherwise bound would not fit var dropped: List[Type] = List() // the types dropped so far, last one on top - def dropOneTransparentClass(tp: Type): Type = - val tpd = tp.dealias - if isTransparent(tpd) && !kept.contains(tpd) then - dropped = tpd :: dropped + def dropOneTransparentTrait(tp: Type): Type = + if isTransparent(tp, traitOnly = true) && !kept.contains(tp) then + dropped = tp :: dropped defn.AnyType - else tpd match + else tp match case AndType(tp1, tp2) => - val tp1w = dropOneTransparentClass(tp1) + val tp1w = dropOneTransparentTrait(tp1) if tp1w ne tp1 then tp1w & tp2 else - val tp2w = dropOneTransparentClass(tp2) + val tp2w = dropOneTransparentTrait(tp2) if tp2w ne tp2 then tp1 & tp2w - else tpd + else tp case _ => tp def recur(tp: Type): Type = - val tpw = dropOneTransparentClass(tp) + val tpw = dropOneTransparentTrait(tp) if tpw eq tp then tp else if tpw <:< bound then recur(tpw) else @@ -605,7 +607,7 @@ trait ConstraintHandling { tp else tpw - end dropTransparentClasses + end dropTransparentTraits /** If `tp` is an applied match type alias which is also an unreducible application * of a higher-kinded type to a wildcard argument, widen to the match type's bound, @@ -631,7 +633,7 @@ trait ConstraintHandling { * union type (except for unions | Null, which are kept in the state they were). * 3. Widen some irreducible applications of higher-kinded types to wildcard arguments * (see @widenIrreducible). - * 4. Drop transparent traits from intersections (see @dropTransparentClasses). + * 4. Drop transparent traits from intersections (see @dropTransparentTraits). * * Don't do these widenings if `bound` is a subtype of `scala.Singleton`. * Also, if the result of these widenings is a TypeRef to a module class, @@ -662,10 +664,10 @@ trait ConstraintHandling { val widenedFromSingle = widenSingle(inst) val widenedFromUnion = widenOr(widenedFromSingle) val widened = - if (widenedFromUnion ne widenedFromSingle) && isTransparent(widenedFromUnion) then + if (widenedFromUnion ne widenedFromSingle) && isTransparent(widenedFromUnion, traitOnly = false) then widenedFromSingle else - dropTransparentClasses(widenedFromUnion, bound) + dropTransparentTraits(widenedFromUnion, bound) widenIrreducible(widened) wideInst match diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 3c8403ae8a79..658bf4122aa4 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3011,8 +3011,8 @@ object TypeComparer { def widenInferred(inst: Type, bound: Type, widenUnions: Boolean)(using Context): Type = comparing(_.widenInferred(inst, bound, widenUnions)) - def dropTransparentClasses(tp: Type, bound: Type)(using Context): Type = - comparing(_.dropTransparentClasses(tp, bound)) + def dropTransparentTraits(tp: Type, bound: Type)(using Context): Type = + comparing(_.dropTransparentTraits(tp, bound)) def constrainPatternType(pat: Type, scrut: Type, forceInvariantRefinement: Boolean = false)(using Context): Boolean = comparing(_.constrainPatternType(pat, scrut, forceInvariantRefinement)) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 448c66838164..ec72c48b2422 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1210,7 +1210,7 @@ trait Applications extends Compatibility { && tree.tpe.classSymbol.isEnumCase && tree.tpe.widen.isValueType then - val widened = TypeComparer.dropTransparentClasses( + val widened = TypeComparer.dropTransparentTraits( tree.tpe.parents.reduceLeft(TypeComparer.andType(_, _)), pt) if widened <:< pt then Typed(tree, TypeTree(widened)) diff --git a/docs/_docs/reference/other-new-features/transparent-traits.md b/docs/_docs/reference/other-new-features/transparent-traits.md index f2837d53401c..b930ffbfde00 100644 --- a/docs/_docs/reference/other-new-features/transparent-traits.md +++ b/docs/_docs/reference/other-new-features/transparent-traits.md @@ -85,7 +85,7 @@ declared transparent. Transparent traits and classes can be given as explicit types as usual. But they are often elided when types are inferred. Roughly, the rules for type inference imply the following. - - Transparent traits and classes are dropped from intersections where possible. + - Transparent traits are dropped from intersections where possible. - Union types are not widened if widening would result in only transparent supertypes. The precise rules are as follows: @@ -94,8 +94,8 @@ The precise rules are as follows: - where that type is not higher-kinded, - and where `B` is its known upper bound or `Any` if none exists: - If the type inferred so far is of the form `T1 & ... & Tn` where - `n >= 1`, replace the maximal number of transparent `Ti`s by `Any`, while ensuring that + `n >= 1`, replace the maximal number of transparent traits `Ti`s by `Any`, while ensuring that the resulting type is still a subtype of the bound `B`. -- However, do not perform this widening if all transparent types `Ti` can get replaced in that way. This clause ensures that a single transparent trait instance such as [`Product`](https://scala-lang.org/api/3.x/scala/Product.html) is not widened to [`Any`](https://scala-lang.org/api/3.x/scala/Any.html). Transparent trait instances are only dropped when they appear in conjunction with some other type. +- However, do not perform this widening if all types `Ti` can get replaced in that way. This clause ensures that a single transparent trait instance such as [`Product`](https://scala-lang.org/api/3.x/scala/Product.html) is not widened to [`Any`](https://scala-lang.org/api/3.x/scala/Any.html). Transparent trait instances are only dropped when they appear in conjunction with some other type. -- If the original type was a is union type that got widened in a previous step to a product consisting only of transparent types, keep the original union type instead of its widened form. \ No newline at end of file +- If the original type was a is union type that got widened in a previous step to a product consisting only of transparent traits and classes, keep the original union type instead of its widened form. \ No newline at end of file diff --git a/tests/pos-deep-subtype/i15365.scala b/tests/pending/pos/i15365.scala similarity index 100% rename from tests/pos-deep-subtype/i15365.scala rename to tests/pending/pos/i15365.scala diff --git a/tests/pos-deep-subtype/i16311.scala b/tests/pending/pos/i16311.scala similarity index 100% rename from tests/pos-deep-subtype/i16311.scala rename to tests/pending/pos/i16311.scala diff --git a/tests/pos/i16338.scala b/tests/pos/i16338.scala new file mode 100644 index 000000000000..46a05ceee3c6 --- /dev/null +++ b/tests/pos/i16338.scala @@ -0,0 +1,25 @@ +package de.sciss.kollflitz + +import scala.collection.* + +type Tagged[U] = { type Tag = U } +type @@ [+T, U] = T with Tagged[U] +private val anyTagger = new Tagger[Any] +final class Tagger[U] private[kollflitz] { + def apply[T](t : T): T @@ U = t.asInstanceOf[T @@ U] +} +def tag[U]: Tagger[U] = anyTagger.asInstanceOf[Tagger[U]] + +sealed trait Sorted + + +/** Enrichment methods for random access collections. */ +implicit final class KollFlitzSortedIndexedSeq[A, CC[_], Repr](val self: SeqOps[A, CC, Repr] @@ Sorted) + extends AnyVal { + + /** Nearest percentile (rounded index, no interpolation). */ + def percentile(n: Int): A = self((self.size * n - 50) / 100) + + /** Median found by rounding the index (no interpolation). */ + def median: A = percentile(50) +} diff --git a/tests/pos/i16342.scala b/tests/pos/i16342.scala new file mode 100644 index 000000000000..912790c459c2 --- /dev/null +++ b/tests/pos/i16342.scala @@ -0,0 +1,18 @@ +type Opaque = Base with Tag + +type Base = Any { + type Hack +} + +trait Tag + +object Opaque { + def apply(value: String): Opaque = value.asInstanceOf[Opaque] + + def unapply(userId: Opaque): Option[String] = Option(userId).map(_.value) + def unappy2(userId: Base with Tag): Option[String] = Option(userId).map(_.value) +} + +final implicit class Ops(private val userId: Opaque) extends AnyVal { + def value: String = userId.asInstanceOf[String] +} \ No newline at end of file diff --git a/tests/pos/transparent-intersect.scala b/tests/pos/transparent-intersect.scala new file mode 100644 index 000000000000..21fe4bd8a633 --- /dev/null +++ b/tests/pos/transparent-intersect.scala @@ -0,0 +1,6 @@ +object Test: + + val x: Any { type T = Int } & Object = ??? + val y = if ??? then x else x + val _ : Object { type T = Int } = y +