Skip to content

Commit

Permalink
Merge pull request scala#362 from dotty-staging/add/orphan-check
Browse files Browse the repository at this point in the history
Added test to TreeChecker that guards against orphan parameters.
  • Loading branch information
DarkDimius committed Feb 12, 2015
2 parents f1aa075 + b5c3a28 commit b2ca9fe
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 59 deletions.
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class Compiler {
List(new FirstTransform,
new SyntheticMethods),
List(new SuperAccessors),
// pickling goes here
//List(new Pickler), // Pickler needs to come last in a group since it should not pickle trees generated later
List(new RefChecks,
new ElimRepeated,
new ElimLocals,
Expand Down
139 changes: 88 additions & 51 deletions src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ object Types {
* (Note: no stripTypeVar needed because TypeVar's can't refer to ExprTypes.)
*/
final def widenExpr: Type = this match {
case tp: ExprType => tp.resultType
case tp: ExprType => tp.resType
case _ => this
}

Expand Down Expand Up @@ -858,33 +858,33 @@ object Types {
}

/** The parameter types of a PolyType or MethodType, Empty list for others */
final def paramTypess: List[List[Type]] = this match {
final def paramTypess(implicit ctx: Context): List[List[Type]] = this match {
case mt: MethodType => mt.paramTypes :: mt.resultType.paramTypess
case pt: PolyType => pt.resultType.paramTypess
case _ => Nil
}

/** The parameter types in the first parameter section of a PolyType or MethodType, Empty list for others */
final def firstParamTypes: List[Type] = this match {
final def firstParamTypes(implicit ctx: Context): List[Type] = this match {
case mt: MethodType => mt.paramTypes
case pt: PolyType => pt.resultType.firstParamTypes
case _ => Nil
}

/** Is this either not a method at all, or a parameterless method? */
final def isParameterless: Boolean = this match {
final def isParameterless(implicit ctx: Context): Boolean = this match {
case mt: MethodType => false
case pt: PolyType => pt.resultType.isParameterless
case _ => true
}

/** The resultType of a PolyType, MethodType, or ExprType, the type itself for others */
def resultType: Type = this
def resultType(implicit ctx: Context): Type = this

/** The final result type of a PolyType, MethodType, or ExprType, after skipping
* all parameter sections, the type itself for all others.
*/
def finalResultType: Type = resultType match {
def finalResultType(implicit ctx: Context): Type = resultType match {
case mt: MethodType => mt.resultType.finalResultType
case pt: PolyType => pt.resultType.finalResultType
case _ => resultType
Expand Down Expand Up @@ -1941,49 +1941,77 @@ object Types {
abstract case class MethodType(paramNames: List[TermName], paramTypes: List[Type])
(resultTypeExp: MethodType => Type)
extends CachedGroundType with BindingType with TermType with MethodOrPoly with NarrowCached { thisMethodType =>
import MethodType._

override val resultType = resultTypeExp(this)
assert(resultType.exists)
def isJava = false
def isImplicit = false

private val resType = resultTypeExp(this)
assert(resType.exists)

override def resultType(implicit ctx: Context): Type =
if (dependencyStatus == FalseDeps) { // dealias all false dependencies
val dealiasMap = new TypeMap {
def apply(tp: Type) = tp match {
case tp @ TypeRef(MethodParam(`thisMethodType`, _), name) => // follow type alias to avoid dependency
val TypeAlias(alias) = tp.info
apply(alias)
case _ =>
mapOver(tp)
}
}
dealiasMap(resType)
}
else resType

private[this] var myIsDependent: Boolean = _
private[this] var myIsDepKnown = false

/** Does result type contain references to parameters of this method type?
*/
def isDependent(implicit ctx: Context) = {
if (!myIsDepKnown) {
val isDepAcc = new TypeAccumulator[Boolean] {
def apply(x: Boolean, tp: Type) = x || {
tp match {
case MethodParam(`thisMethodType`, _) => true
case tp @ TypeRef(MethodParam(`thisMethodType`, _), name) =>
tp.info match { // follow type arguments to avoid dependency
case TypeAlias(tp)=> apply(x, tp)
case _ => true
}
case _ =>
foldOver(x, tp)
private[this] var myDependencyStatus: DependencyStatus = Unknown

/** The dependency status of this method. Some examples:
*
* class C extends { type S; type T = String }
* def f(x: C)(y: Boolean) // dependencyStatus = NoDeps
* def f(x: C)(y: x.S) // dependencyStatus = TrueDeps
* def f(x: C)(y: x.T) // dependencyStatus = FalseDeps, i.e.
* // dependency can be eliminated by dealiasing.
*/
private def dependencyStatus(implicit ctx: Context): DependencyStatus = {
if (myDependencyStatus == Unknown) {
val isDepAcc = new TypeAccumulator[DependencyStatus] {
def apply(x: DependencyStatus, tp: Type) =
if (x == TrueDeps) x
else x max {
tp match {
case MethodParam(`thisMethodType`, _) => TrueDeps
case tp @ TypeRef(MethodParam(`thisMethodType`, _), name) =>
tp.info match { // follow type alias to avoid dependency
case TypeAlias(alias) => apply(x, alias) max FalseDeps
case _ => TrueDeps
}
case _ =>
foldOver(x, tp)
}
}
}
}
myIsDependent = isDepAcc(false, resultType)
myIsDepKnown = true
myDependencyStatus = isDepAcc(NoDeps, resType)
}
myIsDependent
myDependencyStatus
}

/** Does result type contain references to parameters of this method type,
* which cannot be eliminated by de-aliasing?
*/
def isDependent(implicit ctx: Context): Boolean = dependencyStatus == TrueDeps

protected def computeSignature(implicit ctx: Context): Signature =
resultSignature.prepend(paramTypes, isJava)

def derivedMethodType(paramNames: List[TermName], paramTypes: List[Type], restpe: Type)(implicit ctx: Context) =
if ((paramNames eq this.paramNames) && (paramTypes eq this.paramTypes) && (restpe eq this.resultType)) this
def derivedMethodType(paramNames: List[TermName], paramTypes: List[Type], resType: Type)(implicit ctx: Context) =
if ((paramNames eq this.paramNames) && (paramTypes eq this.paramTypes) && (resType eq this.resType)) this
else {
val restpeFn = (x: MethodType) => restpe.subst(this, x)
if (isJava) JavaMethodType(paramNames, paramTypes)(restpeFn)
else if (isImplicit) ImplicitMethodType(paramNames, paramTypes)(restpeFn)
else MethodType(paramNames, paramTypes)(restpeFn)
val resTypeFn = (x: MethodType) => resType.subst(this, x)
if (isJava) JavaMethodType(paramNames, paramTypes)(resTypeFn)
else if (isImplicit) ImplicitMethodType(paramNames, paramTypes)(resTypeFn)
else MethodType(paramNames, paramTypes)(resTypeFn)
}

def instantiate(argTypes: => List[Type])(implicit ctx: Context): Type =
Expand All @@ -1994,15 +2022,15 @@ object Types {
case that: MethodType =>
this.paramNames == that.paramNames &&
this.paramTypes == that.paramTypes &&
this.resultType == that.resultType
this.resType == that.resType
case _ =>
false
}

override def computeHash = doHash(paramNames, resultType, paramTypes)
override def computeHash = doHash(paramNames, resType, paramTypes)

protected def prefixString = "MethodType"
override def toString = s"$prefixString($paramNames, $paramTypes, $resultType)"
override def toString = s"$prefixString($paramNames, $paramTypes, $resType)"
}

final class CachedMethodType(paramNames: List[TermName], paramTypes: List[Type])(resultTypeExp: MethodType => Type)
Expand Down Expand Up @@ -2052,6 +2080,12 @@ object Types {
object MethodType extends MethodTypeCompanion {
def apply(paramNames: List[TermName], paramTypes: List[Type])(resultTypeExp: MethodType => Type)(implicit ctx: Context) =
unique(new CachedMethodType(paramNames, paramTypes)(resultTypeExp))

private type DependencyStatus = Byte
private final val Unknown: DependencyStatus = 0 // not yet computed
private final val NoDeps: DependencyStatus = 1 // no dependent parameters found
private final val FalseDeps: DependencyStatus = 2 // all dependent parameters are prefixes of non-depended alias types
private final val TrueDeps: DependencyStatus = 3 // some truly dependent parameters exist
}

object JavaMethodType extends MethodTypeCompanion {
Expand All @@ -2065,13 +2099,14 @@ object Types {
}

/** A by-name parameter type of the form `=> T`, or the type of a method with no parameter list. */
abstract case class ExprType(override val resultType: Type)
abstract case class ExprType(resType: Type)
extends CachedProxyType with TermType with MethodicType {
override def underlying(implicit ctx: Context): Type = resultType
override def resultType(implicit ctx: Context): Type = resType
override def underlying(implicit ctx: Context): Type = resType
protected def computeSignature(implicit ctx: Context): Signature = resultSignature
def derivedExprType(resultType: Type)(implicit ctx: Context) =
if (resultType eq this.resultType) this else ExprType(resultType)
override def computeHash = doHash(resultType)
def derivedExprType(resType: Type)(implicit ctx: Context) =
if (resType eq this.resType) this else ExprType(resType)
override def computeHash = doHash(resType)
}

final class CachedExprType(resultType: Type) extends ExprType(resultType)
Expand All @@ -2087,7 +2122,9 @@ object Types {
extends CachedGroundType with BindingType with TermType with MethodOrPoly {

val paramBounds = paramBoundsExp(this)
override val resultType = resultTypeExp(this)
val resType = resultTypeExp(this)

override def resultType(implicit ctx: Context) = resType

protected def computeSignature(implicit ctx: Context) = resultSignature

Expand All @@ -2097,21 +2134,21 @@ object Types {
def instantiateBounds(argTypes: List[Type])(implicit ctx: Context): List[TypeBounds] =
paramBounds.mapConserve(_.substParams(this, argTypes).bounds)

def derivedPolyType(paramNames: List[TypeName], paramBounds: List[TypeBounds], restpe: Type)(implicit ctx: Context) =
if ((paramNames eq this.paramNames) && (paramBounds eq this.paramBounds) && (restpe eq this.resultType)) this
else duplicate(paramNames, paramBounds, restpe)
def derivedPolyType(paramNames: List[TypeName], paramBounds: List[TypeBounds], resType: Type)(implicit ctx: Context) =
if ((paramNames eq this.paramNames) && (paramBounds eq this.paramBounds) && (resType eq this.resType)) this
else duplicate(paramNames, paramBounds, resType)

def duplicate(paramNames: List[TypeName] = this.paramNames, paramBounds: List[TypeBounds] = this.paramBounds, restpe: Type)(implicit ctx: Context) =
def duplicate(paramNames: List[TypeName] = this.paramNames, paramBounds: List[TypeBounds] = this.paramBounds, resType: Type)(implicit ctx: Context) =
PolyType(paramNames)(
x => paramBounds mapConserve (_.subst(this, x).bounds),
x => restpe.subst(this, x))
x => resType.subst(this, x))

// need to override hashCode and equals to be object identity
// because paramNames by itself is not discriminatory enough
override def equals(other: Any) = this eq other.asInstanceOf[AnyRef]
override def computeHash = identityHash

override def toString = s"PolyType($paramNames, $paramBounds, $resultType)"
override def toString = s"PolyType($paramNames, $paramBounds, $resType)"
}

object PolyType {
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ object Erasure extends TypeTestsCasts{
}

private def protoArgs(pt: Type): List[untpd.Tree] = pt match {
case pt: FunProto => pt.args ++ protoArgs(pt.resultType)
case pt: FunProto => pt.args ++ protoArgs(pt.resType)
case _ => Nil
}

Expand Down
19 changes: 19 additions & 0 deletions src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,28 @@ class TreeChecker {
assert(isSubType(tree1.tpe, tree.typeOpt), divergenceMsg(tree1.tpe, tree.typeOpt))
tree1
}
checkNoOrphans(res.tpe)
phasesToCheck.foreach(_.checkPostCondition(res))
res
}

/** Check that PolyParams and MethodParams refer to an enclosing type */
def checkNoOrphans(tp: Type)(implicit ctx: Context) = new TypeMap() {
val definedBinders = mutable.Set[Type]()
def apply(tp: Type): Type = {
tp match {
case tp: BindingType =>
definedBinders += tp
mapOver(tp)
definedBinders -= tp
case tp: ParamType =>
assert(definedBinders.contains(tp.binder), s"orphan param: $tp")
case _ =>
mapOver(tp)
}
tp
}
}.apply(tp)

override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = {
assert(tree.isTerm || !ctx.isAfterTyper, tree.show + " at " + ctx.phase)
Expand Down
18 changes: 13 additions & 5 deletions src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,11 @@ object ProtoTypes {
*
* [](args): resultType
*/
case class FunProto(args: List[untpd.Tree], override val resultType: Type, typer: Typer)(implicit ctx: Context)
case class FunProto(args: List[untpd.Tree], resType: Type, typer: Typer)(implicit ctx: Context)
extends UncachedGroundType with ApplyingProto {
private var myTypedArgs: List[Tree] = Nil

override def resultType(implicit ctx: Context) = resType

/** A map in which typed arguments can be stored to be later integrated in `typedArgs`. */
private var myTypedArg: SimpleMap[untpd.Tree, Tree] = SimpleMap.Empty
Expand Down Expand Up @@ -241,8 +243,11 @@ object ProtoTypes {
*
* []: argType => resultType
*/
abstract case class ViewProto(argType: Type, override val resultType: Type)
abstract case class ViewProto(argType: Type, resType: Type)
extends CachedGroundType with ApplyingProto {

override def resultType(implicit ctx: Context) = resType

def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean =
ctx.typer.isApplicable(tp, argType :: Nil, resultType)

Expand Down Expand Up @@ -274,7 +279,10 @@ object ProtoTypes {
*
* [] [targs] resultType
*/
case class PolyProto(targs: List[Type], override val resultType: Type) extends UncachedGroundType with ProtoType {
case class PolyProto(targs: List[Type], resType: Type) extends UncachedGroundType with ProtoType {

override def resultType(implicit ctx: Context) = resType

override def isMatchedBy(tp: Type)(implicit ctx: Context) = {
def isInstantiatable(tp: Type) = tp.widen match {
case PolyType(paramNames) => paramNames.length == targs.length
Expand All @@ -284,8 +292,8 @@ object ProtoTypes {
}

def derivedPolyProto(targs: List[Type], resultType: Type) =
if ((targs eq this.targs) && (resultType eq this.resultType)) this
else PolyProto(targs, resultType)
if ((targs eq this.targs) && (resType eq this.resType)) this
else PolyProto(targs, resType)

def map(tm: TypeMap)(implicit ctx: Context): PolyProto =
derivedPolyProto(targs mapConserve tm, tm(resultType))
Expand Down
2 changes: 1 addition & 1 deletion test/dotc/tests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class tests extends CompilerTest {

implicit val defaultOptions = noCheckOptions ++ List(
"-Yno-deep-subtypes",
"-Ycheck:resolveSuper,mixin,restoreScopes",
"-Ycheck:tailrec,resolveSuper,mixin,restoreScopes",
"-d", "./out/"
)

Expand Down

0 comments on commit b2ca9fe

Please sign in to comment.