From 76130f5666275d14e9fc312d57256e5226ac60dc Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 15 Nov 2022 10:47:05 +0100 Subject: [PATCH 1/3] Fix calculation to drop transparent classes Two fixes: 1. Don't forget about refinements 2. Don't dealias Fixes #16342 The first fix is essential for $16342. The second fix is just to keep types tidy and not open aliases needlessly. The previous incorrect version hid errors in previous regressions #15365 and #16311 which will need to be re-opened now. --- .../tools/dotc/core/ConstraintHandling.scala | 15 ++++++++------- .../pos}/i15365.scala | 0 .../pos}/i16311.scala | 0 tests/pos/i16342.scala | 18 ++++++++++++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) rename tests/{pos-deep-subtype => pending/pos}/i15365.scala (100%) rename tests/{pos-deep-subtype => pending/pos}/i16311.scala (100%) create mode 100644 tests/pos/i16342.scala diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 69fc29c9318c..142648a3ae5c 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -557,8 +557,10 @@ trait ConstraintHandling { 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 + case AndType(tp1, tp2) => + isTransparent(tp1) && isTransparent(tp2) + case _ => + tp.underlyingClassRef(refinementOK = false).typeSymbol.isTransparentClass /** 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 @@ -572,18 +574,17 @@ trait ConstraintHandling { 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 + if isTransparent(tp) && !kept.contains(tp) then + dropped = tp :: dropped defn.AnyType - else tpd match + else tp match case AndType(tp1, tp2) => val tp1w = dropOneTransparentClass(tp1) if tp1w ne tp1 then tp1w & tp2 else val tp2w = dropOneTransparentClass(tp2) if tp2w ne tp2 then tp1 & tp2w - else tpd + else tp case _ => tp 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/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 From 1717ed742ccfad314453b75d5418934d3afc340a Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 15 Nov 2022 10:55:27 +0100 Subject: [PATCH 2/3] Test case 16338 --- tests/pos/i16338.scala | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/pos/i16338.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) +} From 2e76eac2f4f05aad1cfbbb0969f68581e09f9caf Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 15 Nov 2022 13:25:41 +0100 Subject: [PATCH 3/3] Drop only transparent traits from intersections Don't drop transparent classes. This means that a type such as ``` Any { type T = A } & Object ``` will be kept as is. Previously it was reduced to `Any { type T = A }` but that feels wrong. Transparent classes now only enter the picture for typing union types. If the join of a union types consists only of transparent traits or classes, keep it instead of widening it. --- .../tools/dotc/core/ConstraintHandling.scala | 27 ++++++++++--------- .../dotty/tools/dotc/core/TypeComparer.scala | 4 +-- .../dotty/tools/dotc/typer/Applications.scala | 2 +- .../other-new-features/transparent-traits.md | 8 +++--- tests/pos/transparent-intersect.scala | 6 +++++ 5 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 tests/pos/transparent-intersect.scala diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 142648a3ae5c..4ed01a5fbe0d 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -556,11 +556,12 @@ trait ConstraintHandling { inst end approximation - private def isTransparent(tp: Type)(using Context): Boolean = tp match + private def isTransparent(tp: Type, traitOnly: Boolean)(using Context): Boolean = tp match case AndType(tp1, tp2) => - isTransparent(tp1) && isTransparent(tp2) + isTransparent(tp1, traitOnly) && isTransparent(tp2, traitOnly) case _ => - tp.underlyingClassRef(refinementOK = false).typeSymbol.isTransparentClass + 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 @@ -569,27 +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 = - if isTransparent(tp) && !kept.contains(tp) then + def dropOneTransparentTrait(tp: Type): Type = + if isTransparent(tp, traitOnly = true) && !kept.contains(tp) then dropped = tp :: dropped defn.AnyType 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 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 @@ -606,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, @@ -632,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, @@ -663,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/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 +