Skip to content

Change/simplify projections #231

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
92 changes: 52 additions & 40 deletions src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 <this . name> , 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 <this . name> , 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 <this . name> 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 --------------------------------------------

Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe replace body with prefix.lookupRefined(name) orElse this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be valid and more elegant. But reduceProjection is called often, so to not have unforeseen performance degradations we tend to eschew object allocations in hot paths. Can and should come back to this once we have macros or guaranteed inlining. But then it's likely to be a global cleanup by means of a regexp search.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. Maybe a // TODO: use Type's orElse combinator once it is inlined then?

if (reduced.exists) reduced else this
}

def symbol(implicit ctx: Context): Symbol = {
val now = ctx.period
if (checkedPeriod == now ||
Expand Down
2 changes: 1 addition & 1 deletion test/dotc/tests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions tests/pos/reduce-projections.scala
Original file line number Diff line number Diff line change
@@ -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

}