Skip to content

Implement avoid in terms of AproximatingTypeMap #2945

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

Merged
merged 10 commits into from
Aug 21, 2017

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Aug 2, 2017

Based on #2908. Last two commits are new.

@odersky odersky requested a review from smarter August 2, 2017 16:51
@odersky odersky changed the title [WIP] Implement avoid in terms of AproximatingTypeMap Implement avoid in terms of AproximatingTypeMap Aug 2, 2017
@odersky odersky force-pushed the change-avoid-approx branch 2 times, most recently from 48def35 to 139b248 Compare August 13, 2017 17:45
@@ -64,9 +64,19 @@ trait TypeAssigner {
}

/** An upper approximation of the given type `tp` that does not refer to any symbol in `symsToAvoid`.
* We need to approximate with ranges:
*
* term references to symbols in `symsToAvoid`,
Copy link
Member

Choose a reason for hiding this comment

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

This would be easier to read with - in front of each list item.

/** Needs to handle the case where the prefix type does not have a member
* named `tp.name` anymmore.
/** Two deviations from standard derivedSelect:
* 1. The teh approximation result is a singleton references C#x.type, we
Copy link
Member

Choose a reason for hiding this comment

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

Extra teh in the sentence ?

val parentType = info.parentsWithArgs.reduceLeft(ctx.typeComparer.andType(_, _))
def addRefinement(parent: Type, decl: Symbol) = {
val inherited =
parentType.findMember(decl.name, info.cls.thisType, Private)
Copy link
Member

Choose a reason for hiding this comment

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

I would explicitly write excluded = Private for clarity.

}
avoided
Copy link
Member

Choose a reason for hiding this comment

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

I guess the intermediate avoided was used for debugging, but its definition could be inlined here.

def apply(tp: Type): Type = {

/** Map a `C.this` type to the right prefix. If the prefix is unstable and
* the `C.this` occurs in nonvariant or contravariant position, mark the map
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we mark the map to be unstable anymore.

case _ =>
if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) {
if (theMap != null && theMap.currentVariance <= 0 && !isLegalPrefix(pre)) {
ctx.base.unsafeNonvariant = ctx.runId
Copy link
Member

Choose a reason for hiding this comment

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

The field unsafeNonvariant isn't used anymore and should be removed.

case tp: NamedType => // inlined for performance; TODO: factor out into inline method
if (tp.symbol.isStatic) tp
else {
val saved = variance
Copy link
Member

Choose a reason for hiding this comment

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

Should use atVariance like the base case does.

.mapOver(tp)
case tp: ThisType =>
toPrefix(pre, cls, tp.cls)
case _: BoundType | NoPrefix =>
Copy link
Member

Choose a reason for hiding this comment

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

Add // Inlined for performance ? Same for the two cases below.

if (v1 > 0 && v2 > 0) propagate(infoLo, infoHi)
else if (v1 < 0 && v2 < 0) propagate(infoHi, infoLo)
else if (!infoLo.isAlias && !infoHi.isAlias) propagate(infoLo, infoHi)
else range(tp.bottomType, tp.topType)
Copy link
Member

Choose a reason for hiding this comment

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

Could parent be used instead of tp.topType ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a comment explaining why not:

            // Using `parent` instead of `tp.topType` would be better for normal refinements,
            // but it would also turn *-types to a hk-types, which is not what we want.
            // We should revisit this point in case we represent applied types not as refinements anymore. 

Copy link
Member

Choose a reason for hiding this comment

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

it would also turn *-types to a hk-types

Would it? TypeApplications#isHK only returns true for HKTypeLambda, and before this PR avoid used to return the parent for refinement types and it worked fine.

Copy link
Member

Choose a reason for hiding this comment

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

Alternatively we could return derivedRefinedType(tp, parent, WildcardType) I think

if (distributeArgs(args, tp.typeParams))
range(tp.derivedAppliedType(tycon, loBuf.toList),
tp.derivedAppliedType(tycon, hiBuf.toList))
else range(tp.bottomType, tp.topType)
Copy link
Member

Choose a reason for hiding this comment

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

Could tp.superType be used instead of tp.topType ?

Copy link
Contributor Author

@odersky odersky Aug 14, 2017

Choose a reason for hiding this comment

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

Not sure. tp.superType is not necessarily a supertype of the derived application because the arguments have changed. We could try to re-do the application on the upper bound of the constructor, but this looks murky with what we know right now. Theoretical underpinnings would be good to have here!

@smarter
Copy link
Member

smarter commented Aug 14, 2017

The new avoid is a bit too strict I think:

object Test {
  def test = {
    object Hi {
      type A = Int
    }

    val x: Hi.A = 1

    List(x)
  }

  val hi: List[Int] = test // Fails because `test` has type `List[Any]` instead of `List[Int]`
}

Because when we avoid Hi we return Object. This can easily be fixed by modifying classBound to return Object { type A = Int }:

diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
index 4340cae22c..11849b870e 100644
--- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
+++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
@@ -49,7 +49,7 @@ trait TypeAssigner {
         parentType.findMember(decl.name, info.cls.thisType, excluded = Private)
           .suchThat(decl.matches(_))
       val inheritedInfo = inherited.info
-      if (inheritedInfo.exists && decl.info <:< inheritedInfo && !(inheritedInfo <:< decl.info)) {
+      if (!inheritedInfo.exists || (decl.info <:< inheritedInfo && !(inheritedInfo <:< decl.info))) {
         val r = RefinedType(parent, decl.name, decl.info)
         typr.println(i"add ref $parent $decl --> " + r)
         r

Do you think that's a good way to go?

@odersky
Copy link
Contributor Author

odersky commented Aug 16, 2017

@smarter I developed another fix, which is more direct.

@odersky odersky assigned smarter and unassigned odersky Aug 16, 2017
@smarter
Copy link
Member

smarter commented Aug 16, 2017

For a Block, TypeAssigner does an avoid just for term symbols whereas
Typer does one for all local symbols.

How come we don't avoid type symbols in TypeAssigner anyway?

@odersky
Copy link
Contributor Author

odersky commented Aug 16, 2017

How come we don't avoid type symbols in TypeAssigner anyway?

I think some of the later phases might allow anonymous classes defined in one block to be used in another. As long as they are still in the same method, that's OK. But I am not sure about specifics.

@smarter
Copy link
Member

smarter commented Aug 16, 2017

OK, opened #2987 to track this.

Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

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

LGTM!

* 1. We first try a widening conversion to the type's info with
* the original prefix. Since the original prefix is known to
* be a subtype of the returned prefix, this can improve results.
* 2. IThen, if the approximation result is a singleton reference C#x.type, we
Copy link
Member

Choose a reason for hiding this comment

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

typo: IThen -> Then

@odersky odersky force-pushed the change-avoid-approx branch from e7e643c to 45ffdf0 Compare August 16, 2017 16:17
The logic before was overcomplicated since I did not take into
account that Range refinedInfos could only happen at variance 0.
On the othet hand, it did not take into account all the subtleties of
alias types with variances.
Test case in i2945.scala.
For a Block, TypeAssigner does an avoid just for term symbols whereas
Typer does one for all local symbols. Since Typer called TypeAssigner,
we got a double avoidance pass. This is eliminated by assigning types
directly in Typer without passing through TypeAssigner.
@odersky odersky force-pushed the change-avoid-approx branch from 45ffdf0 to 491fe97 Compare August 17, 2017 15:57
@liufengyun
Copy link
Contributor

test performance please

@dottybot
Copy link
Member

performance test scheduled: 1 jobs in total.

@dottybot
Copy link
Member

Performance test finished successfully:

Visit https://liufengyun.github.io/bench/2945 to see the changes.

@odersky odersky merged commit 076df81 into scala:master Aug 21, 2017
@allanrenucci allanrenucci deleted the change-avoid-approx branch December 14, 2017 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants