diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 7a853ae12c95..1ef997684cb6 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -56,7 +56,13 @@ trait TypeOps { this: Context => final def simplify(tp: Type, theMap: SimplifyMap): Type = tp match { case tp: NamedType => if (tp.symbol.isStatic) tp - else tp.derivedSelect(simplify(tp.prefix, theMap)) + else tp.derivedSelect(simplify(tp.prefix, theMap)) match { + case tp1: NamedType if tp1.denotationIsCurrent => + val tp2 = tp1.reduceProjection + //if (tp2 ne tp1) println(i"simplified $tp1 -> $tp2") + tp2 + case tp1 => tp1 + } case tp: PolyParam => typerState.constraint.typeVarOfParam(tp) orElse tp case _: ThisType | _: BoundType | NoPrefix => diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index aa8036fc5972..7282a80e69fb 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -713,7 +713,7 @@ object Types { /** A prefix-less termRef to a new skolem symbol that has the given type as info */ def narrow(implicit ctx: Context): TermRef = TermRef(NoPrefix, ctx.newSkolem(this)) - // ----- Normalizing typerefs over refined types ---------------------------- + // ----- Normalizing typerefs over refined types ---------------------------- /** If this is a refinement type that has a refinement for `name` (which might be followed * by other refinements), and the refined info is a type alias, return the alias, @@ -724,58 +724,58 @@ object Types { * to just U. Does not perform the reduction if the resulting type would contain * a reference to the "this" of the current refined type. */ - def lookupRefined(name: Name)(implicit ctx: Context): Type = stripTypeVar match { - case pre: RefinedType => - def dependsOnThis(tp: Type): Boolean = tp match { - case tp @ TypeRef(RefinedThis(rt), _) if rt refines pre => - tp.info match { - case TypeBounds(lo, hi) if lo eq hi => dependsOnThis(hi) - case _ => true - } - case RefinedThis(rt) => - rt refines pre - case _ => false - } - if (pre.refinedName ne name) - pre.parent.lookupRefined(name) - else pre.refinedInfo match { - case TypeBounds(lo, hi) if lo eq hi => - if (hi.existsPart(dependsOnThis)) NoType else hi - case _ => NoType - } - case RefinedThis(rt) => - rt.lookupRefined(name) - case pre: WildcardType => - WildcardType - case _ => - NoType + def lookupRefined(name: Name)(implicit ctx: Context): Type = { + + def dependsOnRefinedThis(tp: Type): Boolean = tp.stripTypeVar match { + case tp @ TypeRef(RefinedThis(rt), _) if rt refines this => + tp.info match { + case TypeBounds(lo, hi) if lo eq hi => dependsOnRefinedThis(hi) + case _ => true + } + case RefinedThis(rt) => rt refines this + case tp: NamedType => + !tp.symbol.isStatic && dependsOnRefinedThis(tp.prefix) + case tp: RefinedType => dependsOnRefinedThis(tp.refinedInfo) || dependsOnRefinedThis(tp.parent) + case tp: TypeBounds => dependsOnRefinedThis(tp.lo) || dependsOnRefinedThis(tp.hi) + case tp: AnnotatedType => dependsOnRefinedThis(tp.underlying) + case tp: AndOrType => dependsOnRefinedThis(tp.tp1) || dependsOnRefinedThis(tp.tp2) + case _ => false + } + + def loop(pre: Type): Type = pre.stripTypeVar match { + case pre: RefinedType => + if (pre.refinedName ne name) loop(pre.parent) + else this.member(name).info match { + case TypeBounds(lo, hi) if (lo eq hi) && !dependsOnRefinedThis(hi) => hi + case _ => NoType + } + case RefinedThis(rt) => + rt.lookupRefined(name) + case pre: WildcardType => + WildcardType + case _ => + NoType + } + + loop(this) } /** The type , reduced if possible */ def select(name: Name)(implicit ctx: Context): Type = name match { - case name: TermName => - TermRef.all(this, name) - case name: TypeName => - val res = lookupRefined(name) - if (res.exists) res else TypeRef(this, name) + case name: TermName => TermRef.all(this, name) + case name: TypeName => TypeRef(this, name).reduceProjection } /** The type , reduced if possible, with given denotation if unreduced */ def select(name: Name, denot: Denotation)(implicit ctx: Context): Type = name match { - case name: TermName => - TermRef(this, name, denot) - case name: TypeName => - val res = lookupRefined(name) - if (res.exists) res else TypeRef(this, name, denot) + case name: TermName => TermRef(this, name, denot) + case name: TypeName => TypeRef(this, name, denot).reduceProjection } /** The type with given symbol, reduced if possible */ def select(sym: Symbol)(implicit ctx: Context): Type = if (sym.isTerm) TermRef(this, sym.asTerm) - else { - val res = lookupRefined(sym.name) - if (res.exists) res else TypeRef(this, sym.asType) - } + else TypeRef(this, sym.asType).reduceProjection // ----- Access to parts -------------------------------------------- @@ -1300,6 +1300,18 @@ object Types { if (name.isInheritedName) prefix.nonPrivateMember(name.revertInherited) else prefix.member(name) + /** (1) Reduce a type-ref `W # X` or `W { ... } # U`, where `W` is a wildcard type + * to an (unbounded) wildcard type. + * + * (2) Reduce a type-ref `T { X = U; ... } # X` to `U` + * provided `U` does not refer with a RefinedThis to the + * refinement type `T { X = U; ... }` + */ + def reduceProjection(implicit ctx: Context): Type = { + val reduced = prefix.lookupRefined(name) + if (reduced.exists) reduced else this + } + def symbol(implicit ctx: Context): Symbol = { val now = ctx.period if (checkedPeriod == now || diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 32f6a0b12ada..11023e9226d8 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -112,7 +112,7 @@ class tests extends CompilerTest { @Test def dotc_config = compileDir(dotcDir + "tools/dotc/config", twice) @Test def dotc_core = compileDir(dotcDir + "tools/dotc/core", twice)(allowDeepSubtypes) @Test def dotc_core_pickling = compileDir(dotcDir + "tools/dotc/core/pickling", twice)(allowDeepSubtypes) - @Test def dotc_transform = compileDir(dotcDir + "tools/dotc/transform", twice) + @Test def dotc_transform = compileDir(dotcDir + "tools/dotc/transform", twice)(allowDeepSubtypes) @Test def dotc_parsing = compileDir(dotcDir + "tools/dotc/parsing", twice) @Test def dotc_printing = compileDir(dotcDir + "tools/dotc/printing", twice) @Test def dotc_reporting = compileDir(dotcDir + "tools/dotc/reporting", twice) diff --git a/tests/pos/reduce-projections.scala b/tests/pos/reduce-projections.scala new file mode 100644 index 000000000000..263c0d5aba60 --- /dev/null +++ b/tests/pos/reduce-projections.scala @@ -0,0 +1,18 @@ +// This test case is intended to verify that reduce projection +// works across multiple refinements. When running with -Xprint:front +// The inferred type of y and yy should be String. +// It would be good to improve our testing framework so +// that we can verify this. With partest, it would be easy. +class ABC { type A; type B; type C } + +object Test { + + val x: (ABC { type C = String; type B = C; type A = B }) # A = ??? + + val y = x // should expand to: val y: String = x + + val xx: (ABC { type C = String } { type B = C } { type A = B }) # A = ??? + + val yy = x // should expand to: val y: String = x + +}