Skip to content

Commit

Permalink
Fix calculation to drop transparent classes (scala#16344)
Browse files Browse the repository at this point in the history
Two fixes:

 1. Don't forget about refinements
 2. Don't dealias

Fixes scala#16342
Fixes scala#16338

The first fix is essential for scala#16342. The second fix is just to keep
types tidy and not open aliases needlessly.

It probably fixes issues scala#16337 and scala#16336 as well, but the test cases
were not self-contained, so I could not try them out. It might fix other
recent regressions as well.

The previous incorrect version hid errors in previous regressions scala#15365
and scala#16311 which will need to be re-opened now.
  • Loading branch information
odersky authored Nov 15, 2022
2 parents 4ae4dc8 + 2e76eac commit e587a81
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 24 deletions.
36 changes: 19 additions & 17 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
8 changes: 4 additions & 4 deletions docs/_docs/reference/other-new-features/transparent-traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.
- 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.
File renamed without changes.
File renamed without changes.
25 changes: 25 additions & 0 deletions tests/pos/i16338.scala
Original file line number Diff line number Diff line change
@@ -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)
}
18 changes: 18 additions & 0 deletions tests/pos/i16342.scala
Original file line number Diff line number Diff line change
@@ -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]
}
6 changes: 6 additions & 0 deletions tests/pos/transparent-intersect.scala
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit e587a81

Please sign in to comment.