diff options
author | odersky <odersky@gmail.com> | 2016-12-15 17:42:45 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-12-15 17:42:45 +0100 |
commit | 9f2b5ad068c06c62e28a3543218f5c1bc0362f74 (patch) | |
tree | bc33fbec19274ae26fe593c4de6ed5d85edf8a62 /compiler | |
parent | 1773b37286e1b3363b756f0c061ae429b94d9b5d (diff) | |
parent | 9b8aaaf899474d3b6ce7b73f637866953f33fd48 (diff) | |
download | dotty-9f2b5ad068c06c62e28a3543218f5c1bc0362f74.tar.gz dotty-9f2b5ad068c06c62e28a3543218f5c1bc0362f74.tar.bz2 dotty-9f2b5ad068c06c62e28a3543218f5c1bc0362f74.zip |
Merge pull request #1682 from dotty-staging/vclass
Fix checks related to value classes
Diffstat (limited to 'compiler')
7 files changed, 65 insertions, 48 deletions
diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 57397a8bc..82943377a 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -438,7 +438,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = { val cls = tref.symbol.asClass val underlying = underlyingOfValueClass(cls) - if (underlying.exists) ErasedValueType(tref, valueErasure(underlying)) + if (underlying.exists && !isCyclic(cls)) ErasedValueType(tref, valueErasure(underlying)) else NoType } diff --git a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 5ae4e8a54..925ec08b2 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -135,14 +135,6 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful // TODO: this is state and should be per-run // todo: check that when transformation finished map is empty - private def checkNonCyclic(pos: Position, seen: Set[Symbol], clazz: ClassSymbol)(implicit ctx: Context): Unit = - if (seen contains clazz) - ctx.error("value class may not unbox to itself", pos) - else { - val unboxed = underlyingOfValueClass(clazz).typeSymbol - if (isDerivedValueClass(unboxed)) checkNonCyclic(pos, seen + clazz, unboxed.asClass) - } - override def transformTemplate(tree: tpd.Template)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { if (isDerivedValueClass(ctx.owner)) { /* This is currently redundant since value classes may not diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index aa4eefe43..328c8204d 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -135,11 +135,14 @@ class TreeChecker extends Phase with SymTransformer { } } - class Checker(phasesToCheck: Seq[Phase]) extends ReTyper { + class Checker(phasesToCheck: Seq[Phase]) extends ReTyper with Checking { val nowDefinedSyms = new mutable.HashSet[Symbol] val everDefinedSyms = new mutable.HashMap[Symbol, Tree] + // don't check value classes after typer, as the constraint about constructors doesn't hold after transform + override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context) = () + def withDefinedSym[T](tree: untpd.Tree)(op: => T)(implicit ctx: Context): T = tree match { case tree: DefTree => val sym = tree.symbol diff --git a/compiler/src/dotty/tools/dotc/transform/ValueClasses.scala b/compiler/src/dotty/tools/dotc/transform/ValueClasses.scala index 93005c57a..b16d05644 100644 --- a/compiler/src/dotty/tools/dotc/transform/ValueClasses.scala +++ b/compiler/src/dotty/tools/dotc/transform/ValueClasses.scala @@ -53,4 +53,14 @@ object ValueClasses { def underlyingOfValueClass(d: ClassDenotation)(implicit ctx: Context): Type = valueClassUnbox(d).info.resultType + /** Whether a value class wraps itself */ + def isCyclic(cls: ClassSymbol)(implicit ctx: Context): Boolean = { + def recur(seen: Set[Symbol], clazz: ClassSymbol)(implicit ctx: Context): Boolean = + (seen contains clazz) || { + val unboxed = underlyingOfValueClass(clazz).typeSymbol + (isDerivedValueClass(unboxed)) && recur(seen + clazz, unboxed.asClass) + } + + recur(Set[Symbol](), cls) + } } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index d80dfe7c0..f5f7bdbaa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -29,6 +29,7 @@ import ErrorReporting.{err, errorType} import config.Printers.typr import collection.mutable import SymDenotations.NoCompleter +import dotty.tools.dotc.transform.ValueClasses._ object Checking { import tpd._ @@ -56,7 +57,7 @@ object Checking { checkBounds(args, poly.paramBounds, _.substParams(poly, _)) /** Check applied type trees for well-formedness. This means - * - all arguments are within their corresponding bounds + * - all arguments are within their corresponding bounds * - if type is a higher-kinded application with wildcard arguments, * check that it or one of its supertypes can be reduced to a normal application. * Unreducible applications correspond to general existentials, and we @@ -88,12 +89,12 @@ object Checking { checkWildcardHKApply(tp.superType, pos) } case _ => - } + } def checkValidIfHKApply(implicit ctx: Context): Unit = checkWildcardHKApply(tycon.tpe.appliedTo(args.map(_.tpe)), tree.pos) checkValidIfHKApply(ctx.addMode(Mode.AllowLambdaWildcardApply)) } - + /** Check that `tp` refers to a nonAbstract class * and that the instance conforms to the self type of the created class. */ @@ -406,6 +407,43 @@ object Checking { notPrivate.errors.foreach { case (msg, pos) => ctx.errorOrMigrationWarning(msg, pos) } info } + + /** Verify classes extending AnyVal meet the requirements */ + def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context) = { + def checkValueClassMember(stat: Tree) = stat match { + case _: ValDef if !stat.symbol.is(ParamAccessor) => + ctx.error(s"value class may not define non-parameter field", stat.pos) + case d: DefDef if d.symbol.isConstructor => + ctx.error(s"value class may not define secondary constructor", stat.pos) + case _: MemberDef | _: Import | EmptyTree => + // ok + case _ => + ctx.error(s"value class may not contain initialization statements", stat.pos) + } + if (isDerivedValueClass(clazz)) { + if (clazz.is(Trait)) + ctx.error("Only classes (not traits) are allowed to extend AnyVal", clazz.pos) + if (clazz.is(Abstract)) + ctx.error("`abstract' modifier cannot be used with value classes", clazz.pos) + if (!clazz.isStatic) + ctx.error(s"value class may not be a ${if (clazz.owner.isTerm) "local class" else "member of another class"}", clazz.pos) + if (isCyclic(clazz.asClass)) + ctx.error("value class cannot wrap itself", clazz.pos) + else { + val clParamAccessors = clazz.asClass.paramAccessors.filter(_.isTerm) + clParamAccessors match { + case List(param) => + if (param.is(Mutable)) + ctx.error("value class parameter must not be a var", param.pos) + + case _ => + ctx.error("value class needs to have exactly one val parameter", clazz.pos) + } + } + stats.foreach(checkValueClassMember) + } + + } } trait Checking { @@ -553,6 +591,10 @@ trait Checking { errorTree(tpt, ex"Singleton type ${tpt.tpe} is not allowed $where") } else tpt + + /** Verify classes extending AnyVal meet the requirements */ + def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context) = + Checking.checkDerivedValueClass(clazz, stats) } trait NoChecking extends Checking { @@ -568,4 +610,5 @@ trait NoChecking extends Checking { override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context) = () override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt override def checkNotSingleton(tpt: Tree, where: String)(implicit ctx: Context): Tree = tpt + override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context) = () } diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 46bdbf3b3..dcbd444f9 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -18,7 +18,6 @@ import config.{ScalaVersion, NoScalaVersion} import Decorators._ import typer.ErrorReporting._ import DenotTransformers._ -import ValueClasses.isDerivedValueClass object RefChecks { import tpd._ @@ -688,39 +687,6 @@ object RefChecks { } } - /** Verify classes extending AnyVal meet the requirements */ - private def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context) = { - def checkValueClassMember(stat: Tree) = stat match { - case _: ValDef if !stat.symbol.is(ParamAccessor) => - ctx.error(s"value class may not define non-parameter field", stat.pos) - case _: DefDef if stat.symbol.isConstructor => - ctx.error(s"value class may not define secondary constructor", stat.pos) - case _: MemberDef | _: Import | EmptyTree => - // ok - case _ => - ctx.error(s"value class may not contain initialization statements", stat.pos) - } - if (isDerivedValueClass(clazz)) { - if (clazz.is(Trait)) - ctx.error("Only classes (not traits) are allowed to extend AnyVal", clazz.pos) - if (clazz.is(Abstract)) - ctx.error("`abstract' modifier cannot be used with value classes", clazz.pos) - if (!clazz.isStatic) - ctx.error(s"value class may not be a ${if (clazz.owner.isTerm) "local class" else "member of another class"}", clazz.pos) - else { - val clParamAccessors = clazz.asClass.paramAccessors.filter(sym => sym.isTerm && !sym.is(Method)) - clParamAccessors match { - case List(param) => - if (param.is(Mutable)) - ctx.error("value class parameter must not be a var", param.pos) - case _ => - ctx.error("value class needs to have exactly one val parameter", clazz.pos) - } - } - stats.foreach(checkValueClassMember) - } - } - type LevelAndIndex = immutable.Map[Symbol, (LevelInfo, Int)] class OptLevelInfo extends DotClass { @@ -836,7 +802,6 @@ class RefChecks extends MiniPhase { thisTransformer => checkParents(cls) checkCompanionNameClashes(cls) checkAllOverrides(cls) - checkDerivedValueClass(cls, tree.body) tree } catch { case ex: MergeError => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ae6b719e4..78c479433 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1293,6 +1293,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit ctx.featureWarning(nme.dynamics.toString, "extension of type scala.Dynamic", isScala2Feature = true, cls, isRequired, cdef.pos) } + + // check value class constraints + checkDerivedValueClass(cls, body1) + cdef1 // todo later: check that |