From dae0c06cc0e031457bf71de430f83ca0bad8b1ec Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 16 Nov 2014 19:22:21 +0100 Subject: [PATCH 1/3] Improve simplifications of type projections. An example where this helps: Previously, the private value `mnemonics` in Coder.scala was fof the form Lambda$IP { ... } # Apply It now simplifies to a Map[...] type. --- src/dotty/tools/dotc/core/TypeOps.scala | 8 +++++- src/dotty/tools/dotc/core/Types.scala | 33 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) 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..05873eed4dc0 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1300,6 +1300,39 @@ object Types { if (name.isInheritedName) prefix.nonPrivateMember(name.revertInherited) else prefix.member(name) + /** 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) = + if (projectsRefinement(prefix)) + info match { + case TypeBounds(lo, hi) if (lo eq hi) && !dependsOnRefinedThis(hi) => hi + case _ => this + } + else this + + private def projectsRefinement(tp: Type)(implicit ctx: Context): Boolean = tp.stripTypeVar match { + case tp: RefinedType => (tp.refinedName eq name) || projectsRefinement(tp.parent) + case _ => false + } + + private def dependsOnRefinedThis(tp: Type)(implicit ctx: Context): Boolean = tp.stripTypeVar match { + case tp @ TypeRef(RefinedThis(rt), _) if rt refines prefix => + tp.info match { + case TypeBounds(lo, hi) if lo eq hi => dependsOnRefinedThis(hi) + case _ => true + } + case RefinedThis(rt) => rt refines prefix + 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 symbol(implicit ctx: Context): Symbol = { val now = ctx.period if (checkedPeriod == now || From 0f4c85250e93bf767c0f10cf737df5736abb7225 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Nov 2014 11:46:56 +0100 Subject: [PATCH 2/3] Make reduceProjection use lookupRefined Needed some fixes to lookup refined. The potential alias type is now calculated by taking the member of the original refined type, instead of by simply following the refined info. This takes into account refinements that were defined after the refinement type that contains the alias. The change amde another test (transform) hit the deep subtype limit, which is now disabled. --- src/dotty/tools/dotc/core/Types.scala | 117 +++++++++++--------------- test/dotc/tests.scala | 2 +- 2 files changed, 49 insertions(+), 70 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 05873eed4dc0..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,37 +1300,16 @@ object Types { if (name.isInheritedName) prefix.nonPrivateMember(name.revertInherited) else prefix.member(name) - /** Reduce a type-ref `T { X = U; ... } # X` to `U` + /** (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; ... }`. + * refinement type `T { X = U; ... }` */ - def reduceProjection(implicit ctx: Context) = - if (projectsRefinement(prefix)) - info match { - case TypeBounds(lo, hi) if (lo eq hi) && !dependsOnRefinedThis(hi) => hi - case _ => this - } - else this - - private def projectsRefinement(tp: Type)(implicit ctx: Context): Boolean = tp.stripTypeVar match { - case tp: RefinedType => (tp.refinedName eq name) || projectsRefinement(tp.parent) - case _ => false - } - - private def dependsOnRefinedThis(tp: Type)(implicit ctx: Context): Boolean = tp.stripTypeVar match { - case tp @ TypeRef(RefinedThis(rt), _) if rt refines prefix => - tp.info match { - case TypeBounds(lo, hi) if lo eq hi => dependsOnRefinedThis(hi) - case _ => true - } - case RefinedThis(rt) => rt refines prefix - 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 reduceProjection(implicit ctx: Context): Type = { + val reduced = prefix.lookupRefined(name) + if (reduced.exists) reduced else this } def symbol(implicit ctx: Context): Symbol = { 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) From 8daa7a05c09e60558968d70911cb3439097b57fe Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 18 Nov 2014 18:06:22 +0100 Subject: [PATCH 3/3] Added test case for mulitple simplifications in a row. --- tests/pos/reduce-projections.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/pos/reduce-projections.scala 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 + +}