Skip to content

Commit

Permalink
Merge pull request #10670 from dotty-staging/parametric-top
Browse files Browse the repository at this point in the history
Add Matchable trait
  • Loading branch information
odersky authored Dec 14, 2020
2 parents 15a25e7 + 7fad5c7 commit 1ec8041
Show file tree
Hide file tree
Showing 34 changed files with 348 additions and 71 deletions.
26 changes: 17 additions & 9 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -397,17 +397,14 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
case TypeApply(fn, _) =>
if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of || fn.symbol == defn.Predef_classOf) Pure else exprPurity(fn)
case Apply(fn, args) =>
def isKnownPureOp(sym: Symbol) =
sym.owner.isPrimitiveValueClass
|| sym.owner == defn.StringClass
|| defn.pureMethods.contains(sym)
if (tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure.
|| (fn.symbol.isStableMember && !fn.symbol.is(Lazy))) // constructors of no-inits classes are stable
if isPureApply(tree, fn) then
minOf(exprPurity(fn), args.map(exprPurity)) `min` Pure
else if (fn.symbol.is(Erased)) Pure
else if (fn.symbol.isStableMember) /* && fn.symbol.is(Lazy) */
else if fn.symbol.is(Erased) then
Pure
else if fn.symbol.isStableMember /* && fn.symbol.is(Lazy) */ then
minOf(exprPurity(fn), args.map(exprPurity)) `min` Idempotent
else Impure
else
Impure
case Typed(expr, _) =>
exprPurity(expr)
case Block(stats, expr) =>
Expand Down Expand Up @@ -440,6 +437,17 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>

def isPureBinding(tree: Tree)(using Context): Boolean = statPurity(tree) >= Pure

/** Is the application `tree` with function part `fn` known to be pure?
* Function value and arguments can still be impure.
*/
def isPureApply(tree: Tree, fn: Tree)(using Context): Boolean =
def isKnownPureOp(sym: Symbol) =
sym.owner.isPrimitiveValueClass
|| sym.owner == defn.StringClass
|| defn.pureMethods.contains(sym)
tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure.
|| fn.symbol.isStableMember && !fn.symbol.is(Lazy) // constructors of no-inits classes are stable

/** The purity level of this reference.
* @return
* PurePath if reference is (nonlazy and stable)
Expand Down
25 changes: 19 additions & 6 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -259,10 +259,19 @@ class Definitions {
*/
@tu lazy val AnyClass: ClassSymbol = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Any, Abstract, Nil), ensureCtor = false)
def AnyType: TypeRef = AnyClass.typeRef
@tu lazy val AnyValClass: ClassSymbol = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.AnyVal, Abstract, List(AnyClass.typeRef)))
@tu lazy val MatchableClass: ClassSymbol = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Matchable, Trait, AnyType :: Nil), ensureCtor = false)
def MatchableType: TypeRef = MatchableClass.typeRef
@tu lazy val AnyValClass: ClassSymbol =
val res = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.AnyVal, Abstract, List(AnyType, MatchableType)))
// Mark companion as absent, so that class does not get re-completed
val companion = ScalaPackageVal.info.decl(nme.AnyVal).symbol
companion.moduleClass.markAbsent()
companion.markAbsent()
res

def AnyValType: TypeRef = AnyValClass.typeRef

@tu lazy val Any_== : TermSymbol = enterMethod(AnyClass, nme.EQ, methOfAny(BooleanType), Final)
@tu lazy val Any_== : TermSymbol = enterMethod(AnyClass, nme.EQ, methOfAny(BooleanType), Final)
@tu lazy val Any_!= : TermSymbol = enterMethod(AnyClass, nme.NE, methOfAny(BooleanType), Final)
@tu lazy val Any_equals: TermSymbol = enterMethod(AnyClass, nme.equals_, methOfAny(BooleanType))
@tu lazy val Any_hashCode: TermSymbol = enterMethod(AnyClass, nme.hashCode_, MethodType(Nil, IntType))
Expand All @@ -288,7 +297,7 @@ class Definitions {
@tu lazy val ObjectClass: ClassSymbol = {
val cls = requiredClass("java.lang.Object")
assert(!cls.isCompleted, "race for completing java.lang.Object")
cls.info = ClassInfo(cls.owner.thisType, cls, AnyClass.typeRef :: Nil, newScope)
cls.info = ClassInfo(cls.owner.thisType, cls, List(AnyType, MatchableType), newScope)
cls.setFlag(NoInits | JavaDefined)

// The companion object doesn't really exist, so it needs to be marked as
Expand Down Expand Up @@ -444,7 +453,7 @@ class Definitions {
MethodType(List(ThrowableType), NothingType))

@tu lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol(
ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyClass.typeRef))
ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyType))
def NothingType: TypeRef = NothingClass.typeRef
@tu lazy val NullClass: ClassSymbol = {
val parent = if (ctx.explicitNulls) AnyType else ObjectType
Expand Down Expand Up @@ -520,7 +529,7 @@ class Definitions {
// but does not define it as an explicit class.
enterCompleteClassSymbol(
ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final,
List(AnyClass.typeRef), EmptyScope)
List(AnyType), EmptyScope)
@tu lazy val SingletonType: TypeRef = SingletonClass.typeRef

@tu lazy val CollectionSeqType: TypeRef = requiredClassRef("scala.collection.Seq")
Expand Down Expand Up @@ -1144,6 +1153,8 @@ class Definitions {

// ----- Symbol sets ---------------------------------------------------

@tu lazy val topClasses: Set[Symbol] = Set(AnyClass, MatchableClass, ObjectClass, AnyValClass)

@tu lazy val AbstractFunctionType: Array[TypeRef] = mkArityArray("scala.runtime.AbstractFunction", MaxImplementedFunctionArity, 0)
val AbstractFunctionClassPerRun: PerRun[Array[Symbol]] = new PerRun(AbstractFunctionType.map(_.symbol.asClass))
def AbstractFunctionClass(n: Int)(using Context): Symbol = AbstractFunctionClassPerRun()(using ctx)(n)
Expand Down Expand Up @@ -1372,7 +1383,7 @@ class Definitions {
@tu lazy val ShadowableImportNames: Set[TermName] = Set("Predef".toTermName)

/** Class symbols for which no class exist at runtime */
@tu lazy val NotRuntimeClasses: Set[Symbol] = Set(AnyClass, AnyValClass, NullClass, NothingClass)
@tu lazy val NotRuntimeClasses: Set[Symbol] = Set(AnyClass, MatchableClass, AnyValClass, NullClass, NothingClass)

@tu lazy val SpecialClassTagClasses: Set[Symbol] = Set(UnitClass, AnyClass, AnyValClass)

Expand Down Expand Up @@ -1672,6 +1683,7 @@ class Definitions {
@tu lazy val specialErasure: SimpleIdentityMap[Symbol, ClassSymbol] =
SimpleIdentityMap.empty[Symbol]
.updated(AnyClass, ObjectClass)
.updated(MatchableClass, ObjectClass)
.updated(AnyValClass, ObjectClass)
.updated(SingletonClass, ObjectClass)
.updated(TupleClass, ProductClass)
Expand All @@ -1683,6 +1695,7 @@ class Definitions {
@tu lazy val syntheticScalaClasses: List[TypeSymbol] = {
val synth = List(
AnyClass,
MatchableClass,
AnyRefAlias,
AnyKindClass,
andType,
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,7 @@ object Mode {

/** Are we typechecking the rhs of an extension method? */
val InExtensionMethod: Mode = newMode(26, "InExtensionMethod")

/** Are we resolving a TypeTest node? */
val InTypeTest: Mode = newMode(27, "InTypeTest")
}
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
/** If `inst` is a TypeBounds, make sure it does not contain toplevel
* references to `param` (see `Constraint#occursAtToplevel` for a definition
* of "toplevel").
* Any such references are replace by `Nothing` in the lower bound and `Any`
* Any such references are replaced by `Nothing` in the lower bound and `Any`
* in the upper bound.
* References can be direct or indirect through instantiations of other
* parameters in the constraint.
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ object StdNames {
val Import: N = "Import"
val Literal: N = "Literal"
val LiteralAnnotArg: N = "LiteralAnnotArg"
val Matchable: N = "Matchable"
val MatchCase: N = "MatchCase"
val MirroredElemTypes: N = "MirroredElemTypes"
val MirroredElemLabels: N = "MirroredElemLabels"
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2246,9 +2246,7 @@ object SymDenotations {
if (pcls.isCompleting) recur(pobjs1, acc)
else
val pobjMembers = pcls.nonPrivateMembersNamed(name).filterWithPredicate { d =>
// Drop members of `Any` and `Object`
val owner = d.symbol.maybeOwner
(owner ne defn.AnyClass) && (owner ne defn.ObjectClass)
!defn.topClasses.contains(d.symbol.maybeOwner) // Drop members of top classes
}
recur(pobjs1, acc.union(pobjMembers))
case nil =>
Expand Down
22 changes: 18 additions & 4 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ object Types {
def isAnyRef(using Context): Boolean = isRef(defn.ObjectClass, skipRefined = false)
def isAnyKind(using Context): Boolean = isRef(defn.AnyKindClass, skipRefined = false)

def isTopType(using Context): Boolean = dealias match
case tp: TypeRef => defn.topClasses.contains(tp.symbol)
case _ => false

/** Is this type exactly Nothing (no vars, aliases, refinements etc allowed)? */
def isExactlyNothing(using Context): Boolean = this match {
case tp: TypeRef =>
Expand Down Expand Up @@ -512,6 +516,20 @@ object Types {
case _ =>
false

/** Same as hasClassSmbol(MatchableClass), except that we also follow the constraint
* bounds of type variables in the constraint.
*/
def isMatchableBound(using Context): Boolean = dealias match
case tp: TypeRef => tp.symbol == defn.MatchableClass
case tp: TypeParamRef =>
ctx.typerState.constraint.entry(tp) match
case bounds: TypeBounds => bounds.hi.isMatchableBound
case _ => false
case tp: TypeProxy => tp.underlying.isMatchableBound
case tp: AndType => tp.tp1.isMatchableBound || tp.tp2.isMatchableBound
case tp: OrType => tp.tp1.isMatchableBound && tp.tp2.isMatchableBound
case _ => false

/** The term symbol associated with the type */
@tailrec final def termSymbol(using Context): Symbol = this match {
case tp: TermRef => tp.symbol
Expand Down Expand Up @@ -3812,10 +3830,6 @@ object Types {

def unapply(tl: PolyType): Some[(List[LambdaParam], Type)] =
Some((tl.typeParams, tl.resType))

def any(n: Int)(using Context): PolyType =
apply(syntheticParamNames(n))(
pt => List.fill(n)(TypeBounds.empty), pt => defn.AnyType)
}

private object DepStatus {
Expand Down
5 changes: 1 addition & 4 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,7 @@ import transform.SymUtils._
val postScript = addenda.find(!_.isEmpty) match
case Some(p) => p
case None =>
if expected.isAny
|| expected.isAnyRef
|| expected.isRef(defn.AnyValClass)
|| found.isBottomType
if expected.isTopType || found.isBottomType
then ""
else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected))
val (where, printCtx) = Formatting.disambiguateTypes(found2, expected2)
Expand Down
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ class Erasure extends Phase with DenotTransformer {
// After erasure, all former Any members are now Object members
val ClassInfo(pre, _, ps, decls, selfInfo) = ref.info
val extendedScope = decls.cloneScope
for (decl <- defn.AnyClass.classInfo.decls)
if (!decl.isConstructor) extendedScope.enter(decl)
for decl <- defn.AnyClass.classInfo.decls do
if !decl.isConstructor then extendedScope.enter(decl)
ref.copySymDenotation(
info = transformInfo(ref.symbol,
ClassInfo(pre, defn.ObjectClass, ps, extendedScope, selfInfo))
Expand All @@ -71,11 +71,12 @@ class Erasure extends Phase with DenotTransformer {
else {
val oldSymbol = ref.symbol
val newSymbol =
if ((oldSymbol.owner eq defn.AnyClass) && oldSymbol.isConstructor)
if ((oldSymbol.owner eq defn.AnyClass) && oldSymbol.isConstructor) then
//assert(false)
defn.ObjectClass.primaryConstructor
else oldSymbol
val oldOwner = ref.owner
val newOwner = if (oldOwner eq defn.AnyClass) defn.ObjectClass else oldOwner
val newOwner = if oldOwner == defn.AnyClass then defn.ObjectClass else oldOwner
val oldName = ref.name
val newName = ref.targetName
val oldInfo = ref.info
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,8 @@ trait Applications extends Compatibility {
def typedUnApply(tree: untpd.Apply, selType: Type)(using Context): Tree = {
record("typedUnApply")
val Apply(qual, args) = tree
if !ctx.mode.is(Mode.InTypeTest) then
checkMatchable(selType, tree.srcPos, pattern = true)

def notAnExtractor(tree: Tree): Tree =
// prefer inner errors
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,13 @@ trait Checking {
if preExisting.exists || seen.contains(tname) then
report.error(em"@targetName annotation ${'"'}$tname${'"'} clashes with other definition in same scope", stat.srcPos)
if stat.isDef then seen += tname

def checkMatchable(tp: Type, pos: SrcPos, pattern: Boolean)(using Context): Unit =
if !tp.derivesFrom(defn.MatchableClass) && sourceVersion.isAtLeast(`3.1-migration`) then
val kind = if pattern then "pattern selector" else "value"
report.warning(
em"""${kind} should be an instance of Matchable,
|but it has unmatchable type $tp instead""", pos)
}

trait ReChecking extends Checking {
Expand All @@ -1256,6 +1263,7 @@ trait ReChecking extends Checking {
override def checkFullyAppliedType(tree: Tree)(using Context): Unit = ()
override def checkEnumCaseRefsLegal(cdef: TypeDef, enumCtx: Context)(using Context): Unit = ()
override def checkAnnotApplicable(annot: Tree, sym: Symbol)(using Context): Boolean = true
override def checkMatchable(tp: Type, pos: SrcPos, pattern: Boolean)(using Context): Unit = ()
}

trait NoChecking extends ReChecking {
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,8 @@ trait Implicits:
case TypeBounds(lo, hi) if lo.ne(hi) && !t.symbol.is(Opaque) => apply(hi)
case _ => t
}
case t: SingletonType =>
apply(t.widen)
case t: RefinedType =>
apply(t.parent)
case _ =>
Expand Down
7 changes: 1 addition & 6 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -545,10 +545,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
case TypeApply(fn, _) =>
if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of) true else apply(fn)
case Apply(fn, args) =>
def isKnownPureOp(sym: Symbol) =
sym.owner.isPrimitiveValueClass
|| sym.owner == defn.StringClass
|| defn.pureMethods.contains(sym)
val isCaseClassApply = {
val cls = tree.tpe.classSymbol
val meth = fn.symbol
Expand All @@ -557,8 +553,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
meth.owner.linkedClass.is(Case) &&
cls.isNoInitsRealClass
}
if (tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure.
|| (fn.symbol.isStableMember && !fn.symbol.is(Lazy))) // constructors of no-inits classes are stable
if isPureApply(tree, fn) then
apply(fn) && args.forall(apply)
else if (isCaseClassApply)
args.forall(apply)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
val arg :: Nil = args
val t = arg.tpe & tp2
If(
arg.select(defn.Any_isInstanceOf).appliedToType(tp2),
arg.isInstance(tp2),
ref(defn.SomeClass.companionModule.termRef).select(nme.apply)
.appliedToType(t)
.appliedTo(arg.select(nme.asInstanceOf_).appliedToType(t)),
Expand Down
34 changes: 27 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,10 @@ class Typer extends Namer
case templ: untpd.Template =>
import untpd._
var templ1 = templ
def isEligible(tp: Type) = tp.exists && !tp.typeSymbol.is(Final) && !tp.isRef(defn.AnyClass)
def isEligible(tp: Type) =
tp.exists
&& !tp.typeSymbol.is(Final)
&& (!tp.isTopType || tp.isAnyRef) // Object is the only toplevel class that can be instantiated
if (templ1.parents.isEmpty &&
isFullyDefined(pt, ForceDegree.flipBottom) &&
isSkolemFree(pt) &&
Expand Down Expand Up @@ -768,12 +771,20 @@ class Typer extends Namer
def typedTpt = checkSimpleKinded(typedType(tree.tpt))
def handlePattern: Tree = {
val tpt1 = typedTpt
if (!ctx.isAfterTyper && pt != defn.ImplicitScrutineeTypeRef)
if !ctx.isAfterTyper && pt != defn.ImplicitScrutineeTypeRef then
withMode(Mode.GadtConstraintInference) {
TypeComparer.constrainPatternType(tpt1.tpe, pt)
}
val matched = ascription(tpt1, isWildcard = true)
// special case for an abstract type that comes with a class tag
tryWithTypeTest(ascription(tpt1, isWildcard = true), pt)
val result = tryWithTypeTest(matched, pt)
if (result eq matched)
&& pt != defn.ImplicitScrutineeTypeRef
&& !(pt <:< tpt1.tpe)
then
// no check for matchability if TestTest was applied
checkMatchable(pt, tree.srcPos, pattern = true)
result
}
cases(
ifPat = handlePattern,
Expand All @@ -794,13 +805,15 @@ class Typer extends Namer
inferImplicit(tpe, EmptyTree, tree.tpt.span)
) match
case SearchSuccess(clsTag, _, _) =>
Some(typed(untpd.Apply(untpd.TypedSplice(clsTag), untpd.TypedSplice(tree.expr)), pt))
withMode(Mode.InTypeTest) {
Some(typed(untpd.Apply(untpd.TypedSplice(clsTag), untpd.TypedSplice(tree.expr)), pt))
}
case _ =>
None
}
val tag = withTag(defn.TypeTestClass.typeRef.appliedTo(pt, tref))
.orElse(withTag(defn.ClassTagClass.typeRef.appliedTo(tref)))
.getOrElse(tree)
.orElse(withTag(defn.ClassTagClass.typeRef.appliedTo(tref)))
.getOrElse(tree)
if tag.symbol.owner == defn.ClassTagClass && config.Feature.sourceVersion.isAtLeast(config.SourceVersion.`3.1`) then
report.warning("Use of `scala.reflect.ClassTag` for type testing may be unsound. Consider using `scala.reflect.TypeTest` instead.", tree.srcPos)
tag
Expand Down Expand Up @@ -1320,7 +1333,7 @@ class Typer extends Namer
typed(desugar.makeCaseLambda(tree.cases, checkMode, protoFormals.length).withSpan(tree.span), pt)
}
case _ =>
if (tree.isInline) checkInInlineContext("inline match", tree.srcPos)
if tree.isInline then checkInInlineContext("inline match", tree.srcPos)
val sel1 = typedExpr(tree.selector)
val selType = fullyDefinedType(sel1.tpe, "pattern selector", tree.span).widen

Expand Down Expand Up @@ -3529,6 +3542,13 @@ class Typer extends Namer
case _ =>
}

// try an Any -> Matchable conversion
if pt.isMatchableBound && !wtp.derivesFrom(defn.MatchableClass) then
checkMatchable(wtp, tree.srcPos, pattern = false)
val target = AndType(tree.tpe.widenExpr, defn.MatchableType)
if target <:< pt then
return readapt(tree.cast(target))

// try an implicit conversion
val prevConstraint = ctx.typerState.constraint
def recover(failure: SearchFailureType) =
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/repl/ReplDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,7 @@ class ReplDriver(settings: Array[String],
info.bounds.hi.finalResultType
.membersBasedOnFlags(required = Method, excluded = Accessor | ParamAccessor | Synthetic | Private)
.filterNot { denot =>
denot.symbol.owner == defn.AnyClass ||
denot.symbol.owner == defn.ObjectClass ||
denot.symbol.isConstructor
defn.topClasses.contains(denot.symbol.owner) || denot.symbol.isConstructor
}

val vals =
Expand Down
Loading

0 comments on commit 1ec8041

Please sign in to comment.