From da6b00d8d5cc29f828af6fa71d310072f88210fc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 23 Apr 2021 17:03:47 +0200 Subject: [PATCH 1/3] Normalize after avoidance Avoid steps can yield match types that should be normalized to keep things clean. This means avoidance does the same steps as simplification in this respect. Fixes #12194 --- .../src/dotty/tools/dotc/core/TypeOps.scala | 82 ++++++++++--------- .../test/dotc/run-test-pickling.blacklist | 1 + tests/run/i12194.check | 1 + tests/run/i12194.scala | 16 ++++ 4 files changed, 60 insertions(+), 40 deletions(-) create mode 100644 tests/run/i12194.check create mode 100644 tests/run/i12194.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index eb353744ae82..1cb2e0e0f0c6 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -420,46 +420,48 @@ object TypeOps: sym.is(Package) || sym.isStatic && isStaticPrefix(pre.prefix) case _ => true - def apply(tp: Type): Type = tp match { - case tp: TermRef - if toAvoid(tp.symbol) || partsToAvoid(Nil, tp.info).nonEmpty => - tp.info.widenExpr.dealias match { - case info: SingletonType => apply(info) - case info => range(defn.NothingType, apply(info)) - } - case tp: TypeRef if toAvoid(tp.symbol) => - tp.info match { - case info: AliasingBounds => - apply(info.alias) - case TypeBounds(lo, hi) => - range(atVariance(-variance)(apply(lo)), apply(hi)) - case info: ClassInfo => - range(defn.NothingType, apply(classBound(info))) - case _ => - emptyRange // should happen only in error cases - } - case tp: ThisType => - // ThisType is only used inside a class. - // Therefore, either they don't appear in the type to be avoided, or - // it must be a class that encloses the block whose type is to be avoided. - tp - case tp: SkolemType if partsToAvoid(Nil, tp.info).nonEmpty => - range(defn.NothingType, apply(tp.info)) - case tp: TypeVar if mapCtx.typerState.constraint.contains(tp) => - val lo = TypeComparer.instanceType( - tp.origin, fromBelow = variance > 0 || variance == 0 && tp.hasLowerBound)(using mapCtx) - val lo1 = apply(lo) - if (lo1 ne lo) lo1 else tp - case tp: LazyRef => - if localParamRefs.contains(tp.ref) then tp - else if isExpandingBounds then emptyRange - else mapOver(tp) - case tl: HKTypeLambda => - localParamRefs ++= tl.paramRefs - mapOver(tl) - case _ => - mapOver(tp) - } + def apply(tp: Type): Type = + val tp1 = tp match + case tp: TermRef + if toAvoid(tp.symbol) || partsToAvoid(Nil, tp.info).nonEmpty => + tp.info.widenExpr.dealias match { + case info: SingletonType => apply(info) + case info => range(defn.NothingType, apply(info)) + } + case tp: TypeRef if toAvoid(tp.symbol) => + tp.info match { + case info: AliasingBounds => + apply(info.alias) + case TypeBounds(lo, hi) => + range(atVariance(-variance)(apply(lo)), apply(hi)) + case info: ClassInfo => + range(defn.NothingType, apply(classBound(info))) + case _ => + emptyRange // should happen only in error cases + } + case tp: ThisType => + // ThisType is only used inside a class. + // Therefore, either they don't appear in the type to be avoided, or + // it must be a class that encloses the block whose type is to be avoided. + tp + case tp: SkolemType if partsToAvoid(Nil, tp.info).nonEmpty => + range(defn.NothingType, apply(tp.info)) + case tp: TypeVar if mapCtx.typerState.constraint.contains(tp) => + val lo = TypeComparer.instanceType( + tp.origin, fromBelow = variance > 0 || variance == 0 && tp.hasLowerBound)(using mapCtx) + val lo1 = apply(lo) + if (lo1 ne lo) lo1 else tp + case tp: LazyRef => + if localParamRefs.contains(tp.ref) then tp + else if isExpandingBounds then emptyRange + else mapOver(tp) + case tl: HKTypeLambda => + localParamRefs ++= tl.paramRefs + mapOver(tl) + case _ => + mapOver(tp) + if tp1 ne tp then tp1.normalized else tp + end apply /** Three deviations from standard derivedSelect: * 1. We first try a widening conversion to the type's info with diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index d36314e896d8..5bcca090e8c6 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -33,3 +33,4 @@ typeclass-derivation2d.scala typeclass-derivation3.scala varargs-abstract zero-arity-case-class.scala +i12194.scala diff --git a/tests/run/i12194.check b/tests/run/i12194.check new file mode 100644 index 000000000000..9e90577cbd4f --- /dev/null +++ b/tests/run/i12194.check @@ -0,0 +1 @@ +List(foo, bar) diff --git a/tests/run/i12194.scala b/tests/run/i12194.scala new file mode 100644 index 000000000000..29916c461549 --- /dev/null +++ b/tests/run/i12194.scala @@ -0,0 +1,16 @@ +import scala.annotation.implicitNotFound +import scala.compiletime.package$package.summonAll +import scala.util.Try +import scala.util.Success +import scala.util.Failure +import scala.util.NotGiven +import scala.deriving.* + +def f(): Unit = + var t = (??? : Tuple1[ValueOf["foo"]]); t.toList.map(identity) + (??? : Tuple1[ValueOf["foo"]]).toList.map(identity) + +@main def Test(): Unit = + println(summonAll[Tuple.Map[("foo", "bar"), ValueOf]].toList.map{ + case str: ValueOf[_] ⇒ str.value + }) \ No newline at end of file From 2d852d764463f6d9854fcab053cc345aae070964 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Apr 2021 12:17:05 +0200 Subject: [PATCH 2/3] Better fix: Fix isMatch An applied type that is an alias of an applied match type is an applied match type itself. Previously that was not the case, which led to typed that could logically be reduced but were not. --- .../src/dotty/tools/dotc/core/TypeOps.scala | 80 +++++++++---------- .../src/dotty/tools/dotc/core/Types.scala | 4 + tests/pos/i12178.scala | 24 ++++++ 3 files changed, 67 insertions(+), 41 deletions(-) create mode 100644 tests/pos/i12178.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 1cb2e0e0f0c6..2e13641d0f9b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -420,47 +420,45 @@ object TypeOps: sym.is(Package) || sym.isStatic && isStaticPrefix(pre.prefix) case _ => true - def apply(tp: Type): Type = - val tp1 = tp match - case tp: TermRef - if toAvoid(tp.symbol) || partsToAvoid(Nil, tp.info).nonEmpty => - tp.info.widenExpr.dealias match { - case info: SingletonType => apply(info) - case info => range(defn.NothingType, apply(info)) - } - case tp: TypeRef if toAvoid(tp.symbol) => - tp.info match { - case info: AliasingBounds => - apply(info.alias) - case TypeBounds(lo, hi) => - range(atVariance(-variance)(apply(lo)), apply(hi)) - case info: ClassInfo => - range(defn.NothingType, apply(classBound(info))) - case _ => - emptyRange // should happen only in error cases - } - case tp: ThisType => - // ThisType is only used inside a class. - // Therefore, either they don't appear in the type to be avoided, or - // it must be a class that encloses the block whose type is to be avoided. - tp - case tp: SkolemType if partsToAvoid(Nil, tp.info).nonEmpty => - range(defn.NothingType, apply(tp.info)) - case tp: TypeVar if mapCtx.typerState.constraint.contains(tp) => - val lo = TypeComparer.instanceType( - tp.origin, fromBelow = variance > 0 || variance == 0 && tp.hasLowerBound)(using mapCtx) - val lo1 = apply(lo) - if (lo1 ne lo) lo1 else tp - case tp: LazyRef => - if localParamRefs.contains(tp.ref) then tp - else if isExpandingBounds then emptyRange - else mapOver(tp) - case tl: HKTypeLambda => - localParamRefs ++= tl.paramRefs - mapOver(tl) - case _ => - mapOver(tp) - if tp1 ne tp then tp1.normalized else tp + def apply(tp: Type): Type = tp match + case tp: TermRef + if toAvoid(tp.symbol) || partsToAvoid(Nil, tp.info).nonEmpty => + tp.info.widenExpr.dealias match { + case info: SingletonType => apply(info) + case info => range(defn.NothingType, apply(info)) + } + case tp: TypeRef if toAvoid(tp.symbol) => + tp.info match { + case info: AliasingBounds => + apply(info.alias) + case TypeBounds(lo, hi) => + range(atVariance(-variance)(apply(lo)), apply(hi)) + case info: ClassInfo => + range(defn.NothingType, apply(classBound(info))) + case _ => + emptyRange // should happen only in error cases + } + case tp: ThisType => + // ThisType is only used inside a class. + // Therefore, either they don't appear in the type to be avoided, or + // it must be a class that encloses the block whose type is to be avoided. + tp + case tp: SkolemType if partsToAvoid(Nil, tp.info).nonEmpty => + range(defn.NothingType, apply(tp.info)) + case tp: TypeVar if mapCtx.typerState.constraint.contains(tp) => + val lo = TypeComparer.instanceType( + tp.origin, fromBelow = variance > 0 || variance == 0 && tp.hasLowerBound)(using mapCtx) + val lo1 = apply(lo) + if (lo1 ne lo) lo1 else tp + case tp: LazyRef => + if localParamRefs.contains(tp.ref) then tp + else if isExpandingBounds then emptyRange + else mapOver(tp) + case tl: HKTypeLambda => + localParamRefs ++= tl.paramRefs + mapOver(tl) + case _ => + mapOver(tp) end apply /** Three deviations from standard derivedSelect: diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8a0b0f12c167..c0395ace5313 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -423,6 +423,10 @@ object Types { def isMatch(using Context): Boolean = stripped match { case _: MatchType => true case tp: HKTypeLambda => tp.resType.isMatch + case tp: AppliedType => + tp.tycon match + case tycon: TypeRef => tycon.info.isInstanceOf[MatchAlias] + case _ => false case _ => false } diff --git a/tests/pos/i12178.scala b/tests/pos/i12178.scala new file mode 100644 index 000000000000..4247ad92301b --- /dev/null +++ b/tests/pos/i12178.scala @@ -0,0 +1,24 @@ +opaque type LabelTagged[TLabel <: Singleton & String, TValue] = TValue + +object LabelTagged: + def apply[TLabel <: Singleton & String, TValue] + ( + label: TLabel, + value: TValue, + ) + : LabelTagged[TLabel, TValue] = value + +extension[TLabel <: Singleton & String, TValue] (labelTagged: LabelTagged[TLabel, TValue]) + def value + : TValue = labelTagged + + def label + (using label: ValueOf[TLabel]) + : TLabel + = label.value + +@main def hello(): Unit = { + val foo: LabelTagged["foo", Int] = LabelTagged("foo", 10) + println(label(foo)) // OK + //println(foo.label) // not OK +} From c8d9a00019ae345b815541fb311df61ae98d609d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 25 Apr 2021 10:28:45 +0200 Subject: [PATCH 3/3] Make opaque types work with new formulation of isMatch --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 4 ++-- tests/pos/i12194-opaque.scala | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i12194-opaque.scala diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 2d71bd350b3b..33470c9f41bc 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -432,8 +432,8 @@ object SymDenotations { TypeBounds.empty info match - case TypeAlias(alias) if isOpaqueAlias && owner.isClass => - setAlias(alias) + case info: AliasingBounds if isOpaqueAlias && owner.isClass => + setAlias(info.alias) HKTypeLambda.boundsFromParams(tparams, bounds(rhs)) case _ => info diff --git a/tests/pos/i12194-opaque.scala b/tests/pos/i12194-opaque.scala new file mode 100644 index 000000000000..b800d27bb140 --- /dev/null +++ b/tests/pos/i12194-opaque.scala @@ -0,0 +1,3 @@ +opaque type ProductK[F[_], T <: Tuple] = Tuple.Map[T, F] +object ProductK: + def of[F[_], T <: Tuple](t: Tuple.Map[T, F]): ProductK[F, T] = t