Skip to content

Fixes to Type#isRef #1593

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
wants to merge 2 commits into from
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
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/ast/CheckTrees.scala.disabled
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ object CheckTrees {
if (rtp isRef defn.BooleanClass)
check(args.isEmpty)
else {
check(rtp isRef defn.OptionClass)
check(rtp.isRef(defn.OptionClass, stripRefinements = true))
val normArgs = rtp.argTypesHi match {
case optionArg :: Nil =>
optionArg.argTypesHi match {
Expand Down
6 changes: 3 additions & 3 deletions src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ class Definitions {
if (ctx.erasedTypes) JavaArrayType(elem)
else ArrayType.appliedTo(elem :: Nil)
def unapply(tp: Type)(implicit ctx: Context): Option[Type] = tp.dealias match {
case at: RefinedType if (at isRef ArrayType.symbol) && at.argInfos.length == 1 => Some(at.argInfos.head)
case at: RefinedType if (at.parent.isRef(ArrayType.symbol)) && at.argInfos.length == 1 => Some(at.argInfos.head)
case _ => None
}
}
Expand Down Expand Up @@ -667,7 +667,7 @@ class Definitions {

def isTupleType(tp: Type)(implicit ctx: Context) = {
val arity = tp.dealias.argInfos.length
arity <= MaxTupleArity && TupleType(arity) != null && (tp isRef TupleType(arity).symbol)
arity <= MaxTupleArity && TupleType(arity) != null && (tp.isRef(TupleType(arity).symbol, stripRefinements = true))
}

def tupleType(elems: List[Type]) = {
Expand All @@ -679,7 +679,7 @@ class Definitions {

def isFunctionType(tp: Type)(implicit ctx: Context) = {
val arity = functionArity(tp)
0 <= arity && arity <= MaxFunctionArity && (tp isRef FunctionType(arity).symbol)
0 <= arity && arity <= MaxFunctionArity && (tp.isRef(FunctionType(arity).symbol, stripRefinements = true))
}

def functionArity(tp: Type)(implicit ctx: Context) = tp.dealias.argInfos.length - 1
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
// constructor method should not be semi-erased.
else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp)
else this(tp)
case RefinedType(parent, _, _) if !(parent isRef defn.ArrayClass) =>
case RefinedType(parent, _, _) if !(parent.isRef(defn.ArrayClass, stripRefinements = true)) =>
eraseResult(parent)
case _ =>
this(tp)
Expand Down
22 changes: 12 additions & 10 deletions src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,22 @@ object Types {
case _ => false
}

/** Is this type a (possibly refined or applied or aliased) type reference
* to the given type symbol?
* @sym The symbol to compare to. It must be a class symbol or abstract type.
* It makes no sense for it to be an alias type because isRef would always
* return false in that case.
/** Is this type a (possibly aliased) type reference to the given type symbol?
*
* @param sym The symbol to compare to. It must be a class symbol or abstract type.
* It makes no sense for it to be an alias type because isRef would always
* return false in that case.
* @param stripRefinements If true, go through refinements.
*/
def isRef(sym: Symbol)(implicit ctx: Context): Boolean = stripAnnots.stripTypeVar match {
def isRef(sym: Symbol, stripRefinements: Boolean = false)(implicit ctx: Context): Boolean = stripAnnots.stripTypeVar match {
case this1: TypeRef =>
this1.info match { // see comment in Namer#typeDefSig
case TypeAlias(tp) => tp.isRef(sym)
case TypeAlias(tp) => tp.isRef(sym, stripRefinements)
case _ => this1.symbol eq sym
}
case this1: RefinedOrRecType => this1.parent.isRef(sym)
case this1: HKApply => this1.superType.isRef(sym)
case this1: RefinedType if stripRefinements => this1.parent.isRef(sym, stripRefinements)
case this1: RecType => this1.parent.isRef(sym, stripRefinements)
case this1: HKApply => this1.superType.isRef(sym, stripRefinements) && this1.lowerBound.isRef(sym, stripRefinements)
Copy link
Contributor

Choose a reason for hiding this comment

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

If stripRefinements == false, it seems we should also stop at RecType and HKApply since they are conceptually analogous to RefinedType.

Copy link
Contributor

Choose a reason for hiding this comment

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

I tend to think it would be better to split the method in two. isRef and isRefInstance (?). isRefInstance could presumably just be this:

def isRefInstance(sym: Symbol)(implicit ctx: Context): Boolean = dealias match {
   case this1: TypeRef => this1.symbol eq sym
   case this1: RefinedOrRecType => this1.parent.isRef(sym)
   case this1: HKApply => this1.superType.isRef(sym) && this1.lowerBound.isRef(sym)
   case _ => false
}

Copy link
Contributor

Choose a reason for hiding this comment

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

And isRef would just be the remaining cases. That seems simpler logic and avoids the boolean arguments in the calls.

Copy link
Member Author

Choose a reason for hiding this comment

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

Having two methods would be nice yes, I avoided that so that cases like refined aliases of refined aliases of refined instances work, but that might not be important. An additional difficulty is that we cannot use dealias because of Namer#typeDefSig. Another option would be to have:

private[this] def isRef(sym: Symbol, stripRefinements: Boolean = false)(...) = ...
def isRef(sym: Symbol) = isRef(sym)
def isAppliedRef(sym: Symbol) = isRef(sym, true)

Copy link
Contributor

@odersky odersky Oct 14, 2016

Choose a reason for hiding this comment

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

I think the comment in Namer applies only to direct isRefs not to isRefInstance. Even if I am wrong, I claim the logic is still simpler splitting the method in two. I don't believe even an internal boolean is worth it.

case _ => false
}

Expand Down Expand Up @@ -3313,7 +3315,7 @@ object Types {
case mt: MethodType if !mt.isDependent => Some(absMems.head)
case _ => None
}
else if (tp isRef defn.PartialFunctionClass)
else if (tp.isRef(defn.PartialFunctionClass, stripRefinements = true))
// To maintain compatibility with 2.x, we treat PartialFunction specially,
// pretending it is a SAM type. In the future it would be better to merge
// Function and PartialFunction, have Function1 contain a isDefinedAt method
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle
val expr = readTerm()
val tpt = readTpt()
val expr1 = expr match {
case SeqLiteral(elems, elemtpt) if tpt.tpe.isRef(defn.ArrayClass) =>
case SeqLiteral(elems, elemtpt) if tpt.tpe.isRef(defn.ArrayClass, stripRefinements = true) =>
JavaSeqLiteral(elems, elemtpt)
case expr => expr
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ object Scala2Unpickler {
def arrayToRepeated(tp: Type)(implicit ctx: Context): Type = tp match {
case tp @ MethodType(paramNames, paramTypes) =>
val lastArg = paramTypes.last
assert(lastArg isRef defn.ArrayClass)
assert(lastArg.isRef(defn.ArrayClass, stripRefinements = true))
val elemtp0 :: Nil = lastArg.baseArgInfos(defn.ArrayClass)
val elemtp = elemtp0 match {
case AndType(t1, t2) if t1.typeSymbol.isAbstractType && (t2 isRef defn.ObjectClass) =>
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/transform/ExpandSAMs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class ExpandSAMs extends MiniPhaseTransform { thisTransformer =>
case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol =>
tpt.tpe match {
case NoType => tree // it's a plain function
case tpe @ SAMType(_) if tpe.isRef(defn.PartialFunctionClass) =>
case tpe @ SAMType(_) if tpe.isRef(defn.PartialFunctionClass, stripRefinements = true) =>
toPartialFunction(tree)
case tpe @ SAMType(_) if isPlatformSam(tpe.classSymbol.asClass) =>
tree
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ trait Implicits { self: Typer =>
* synthesize a class tag for `T`.
*/
def synthesizedClassTag(formal: Type, pos: Position)(implicit ctx: Context): Tree = {
if (formal.isRef(defn.ClassTagClass))
if (formal.isRef(defn.ClassTagClass, stripRefinements = true))
formal.argTypes match {
case arg :: Nil =>
val tp = fullyDefinedType(arg, "ClassTag argument", pos)
Expand Down
14 changes: 14 additions & 0 deletions tests/neg/isRef.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
trait Foo {
type A = (Any { type T = Int })
type B = (Any { type S = String })
def a: A
def b: B
def aandb: A & B = b // error: found: B, required: A & B
}

trait Foo2 {
type A[_]
type B[_]
def b: B[Int]
def aandb: A[Int] & B[Int] = b // error: found: B[Int], required: A[Int] & B[Int]
}
13 changes: 13 additions & 0 deletions tests/pos/isRef.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
trait Foo {
type A = (Any { type T = Int })
type B = (Any { type S = String })
def b: B
def aorb: A | B = b
}

trait Foo2 {
type A[_]
type B[_]
def b: B[Int]
def aorb: A[Int] | B[Int] = b
}