From 8a61ff432543a29234193cd1f7c14abd3f3d31a0 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 2 Nov 2016 11:08:28 +0100 Subject: Move compiler and compiler tests to compiler dir --- .../src/dotty/tools/dotc/core/Annotations.scala | 162 + .../dotty/tools/dotc/core/CheckRealizable.scala | 132 + compiler/src/dotty/tools/dotc/core/Comments.scala | 459 +++ compiler/src/dotty/tools/dotc/core/Constants.scala | 235 ++ .../src/dotty/tools/dotc/core/Constraint.scala | 154 + .../dotty/tools/dotc/core/ConstraintHandling.scala | 458 +++ .../dotty/tools/dotc/core/ConstraintRunInfo.scala | 17 + compiler/src/dotty/tools/dotc/core/Contexts.scala | 709 ++++ .../src/dotty/tools/dotc/core/Decorators.scala | 185 + .../src/dotty/tools/dotc/core/Definitions.scala | 807 ++++ .../dotty/tools/dotc/core/DenotTransformers.scala | 78 + .../src/dotty/tools/dotc/core/Denotations.scala | 1217 ++++++ compiler/src/dotty/tools/dotc/core/Flags.scala | 640 ++++ compiler/src/dotty/tools/dotc/core/Hashable.scala | 103 + compiler/src/dotty/tools/dotc/core/Mode.scala | 89 + compiler/src/dotty/tools/dotc/core/NameOps.scala | 432 +++ compiler/src/dotty/tools/dotc/core/Names.scala | 372 ++ .../dotty/tools/dotc/core/OrderingConstraint.scala | 636 ++++ compiler/src/dotty/tools/dotc/core/Periods.scala | 159 + compiler/src/dotty/tools/dotc/core/Phases.scala | 377 ++ compiler/src/dotty/tools/dotc/core/Scopes.scala | 437 +++ compiler/src/dotty/tools/dotc/core/Signature.scala | 103 + compiler/src/dotty/tools/dotc/core/StdNames.scala | 844 +++++ .../src/dotty/tools/dotc/core/Substituters.scala | 306 ++ .../src/dotty/tools/dotc/core/SymDenotations.scala | 2004 ++++++++++ .../src/dotty/tools/dotc/core/SymbolLoaders.scala | 267 ++ compiler/src/dotty/tools/dotc/core/Symbols.scala | 602 +++ .../dotty/tools/dotc/core/TypeApplications.scala | 688 ++++ .../src/dotty/tools/dotc/core/TypeComparer.scala | 1502 ++++++++ .../src/dotty/tools/dotc/core/TypeErasure.scala | 514 +++ compiler/src/dotty/tools/dotc/core/TypeOps.scala | 554 +++ .../src/dotty/tools/dotc/core/TypeParamInfo.scala | 40 + .../src/dotty/tools/dotc/core/TyperState.scala | 210 ++ compiler/src/dotty/tools/dotc/core/Types.overflow | 66 + compiler/src/dotty/tools/dotc/core/Types.scala | 3865 ++++++++++++++++++++ compiler/src/dotty/tools/dotc/core/Uniques.scala | 128 + .../dotc/core/classfile/AbstractFileReader.scala | 88 + .../tools/dotc/core/classfile/ByteCodecs.scala | 221 ++ .../dotc/core/classfile/ClassfileConstants.scala | 378 ++ .../dotc/core/classfile/ClassfileParser.scala | 1100 ++++++ .../tools/dotc/core/tasty/DottyUnpickler.scala | 53 + .../dotty/tools/dotc/core/tasty/NameBuffer.scala | 101 + .../tools/dotc/core/tasty/PositionPickler.scala | 79 + .../tools/dotc/core/tasty/PositionUnpickler.scala | 39 + .../dotty/tools/dotc/core/tasty/TastyBuffer.scala | 188 + .../dotty/tools/dotc/core/tasty/TastyFormat.scala | 553 +++ .../dotty/tools/dotc/core/tasty/TastyName.scala | 30 + .../dotty/tools/dotc/core/tasty/TastyPickler.scala | 71 + .../dotty/tools/dotc/core/tasty/TastyPrinter.scala | 122 + .../dotty/tools/dotc/core/tasty/TastyReader.scala | 141 + .../tools/dotc/core/tasty/TastyUnpickler.scala | 95 + .../dotty/tools/dotc/core/tasty/TreeBuffer.scala | 188 + .../dotty/tools/dotc/core/tasty/TreePickler.scala | 641 ++++ .../tools/dotc/core/tasty/TreeUnpickler.scala | 1161 ++++++ .../dotc/core/unpickleScala2/PickleBuffer.scala | 299 ++ .../dotc/core/unpickleScala2/Scala2Unpickler.scala | 1260 +++++++ 56 files changed, 26359 insertions(+) create mode 100644 compiler/src/dotty/tools/dotc/core/Annotations.scala create mode 100644 compiler/src/dotty/tools/dotc/core/CheckRealizable.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Comments.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Constants.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Constraint.scala create mode 100644 compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala create mode 100644 compiler/src/dotty/tools/dotc/core/ConstraintRunInfo.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Contexts.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Decorators.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Definitions.scala create mode 100644 compiler/src/dotty/tools/dotc/core/DenotTransformers.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Denotations.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Flags.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Hashable.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Mode.scala create mode 100644 compiler/src/dotty/tools/dotc/core/NameOps.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Names.scala create mode 100644 compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Periods.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Phases.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Scopes.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Signature.scala create mode 100644 compiler/src/dotty/tools/dotc/core/StdNames.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Substituters.scala create mode 100644 compiler/src/dotty/tools/dotc/core/SymDenotations.scala create mode 100644 compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Symbols.scala create mode 100644 compiler/src/dotty/tools/dotc/core/TypeApplications.scala create mode 100644 compiler/src/dotty/tools/dotc/core/TypeComparer.scala create mode 100644 compiler/src/dotty/tools/dotc/core/TypeErasure.scala create mode 100644 compiler/src/dotty/tools/dotc/core/TypeOps.scala create mode 100644 compiler/src/dotty/tools/dotc/core/TypeParamInfo.scala create mode 100644 compiler/src/dotty/tools/dotc/core/TyperState.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Types.overflow create mode 100644 compiler/src/dotty/tools/dotc/core/Types.scala create mode 100644 compiler/src/dotty/tools/dotc/core/Uniques.scala create mode 100644 compiler/src/dotty/tools/dotc/core/classfile/AbstractFileReader.scala create mode 100644 compiler/src/dotty/tools/dotc/core/classfile/ByteCodecs.scala create mode 100644 compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala create mode 100644 compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/PositionUnpickler.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/TastyBuffer.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/TastyName.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/TastyReader.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala create mode 100644 compiler/src/dotty/tools/dotc/core/unpickleScala2/PickleBuffer.scala create mode 100644 compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala (limited to 'compiler/src/dotty/tools/dotc/core') diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala new file mode 100644 index 000000000..0e8e5a1f0 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -0,0 +1,162 @@ +package dotty.tools.dotc +package core + +import Symbols._, Types._, util.Positions._, Contexts._, Constants._, ast.tpd._ +import config.ScalaVersion +import StdNames._ +import dotty.tools.dotc.ast.{tpd, untpd} + +object Annotations { + + abstract class Annotation { + def tree(implicit ctx: Context): Tree + def symbol(implicit ctx: Context): Symbol = + if (tree.symbol.isConstructor) tree.symbol.owner + else tree.tpe.typeSymbol + def matches(cls: Symbol)(implicit ctx: Context): Boolean = symbol.derivesFrom(cls) + def appliesToModule: Boolean = true // for now; see remark in SymDenotations + + def derivedAnnotation(tree: Tree)(implicit ctx: Context) = + if (tree eq this.tree) this else Annotation(tree) + + def arguments(implicit ctx: Context) = ast.tpd.arguments(tree) + def argument(i: Int)(implicit ctx: Context): Option[Tree] = { + val args = arguments + if (i < args.length) Some(args(i)) else None + } + def argumentConstant(i: Int)(implicit ctx: Context): Option[Constant] = + for (ConstantType(c) <- argument(i) map (_.tpe)) yield c + + def ensureCompleted(implicit ctx: Context): Unit = tree + } + + case class ConcreteAnnotation(t: Tree) extends Annotation { + def tree(implicit ctx: Context): Tree = t + } + + abstract case class LazyAnnotation(sym: Symbol) extends Annotation { + private var myTree: Tree = null + def tree(implicit ctx: Context) = { + if (myTree == null) myTree = complete(ctx) + myTree + } + def complete(implicit ctx: Context): Tree + override def symbol(implicit ctx: Context): Symbol = sym + } + + /** An annotation indicating the body of a right-hand side, + * typically of an inline method. Treated specially in + * pickling/unpickling and TypeTreeMaps + */ + abstract class BodyAnnotation extends Annotation { + override def symbol(implicit ctx: Context) = defn.BodyAnnot + override def derivedAnnotation(tree: Tree)(implicit ctx: Context) = + if (tree eq this.tree) this else ConcreteBodyAnnotation(tree) + override def arguments(implicit ctx: Context) = Nil + override def ensureCompleted(implicit ctx: Context) = () + } + + case class ConcreteBodyAnnotation(body: Tree) extends BodyAnnotation { + def tree(implicit ctx: Context) = body + } + + case class LazyBodyAnnotation(bodyExpr: Context => Tree) extends BodyAnnotation { + private var evaluated = false + private var myBody: Tree = _ + def tree(implicit ctx: Context) = { + if (evaluated) assert(myBody != null) + else { + evaluated = true + myBody = bodyExpr(ctx) + } + myBody + } + def isEvaluated = evaluated + } + + object Annotation { + + def apply(tree: Tree) = ConcreteAnnotation(tree) + + def apply(cls: ClassSymbol)(implicit ctx: Context): Annotation = + apply(cls, Nil) + + def apply(cls: ClassSymbol, arg: Tree)(implicit ctx: Context): Annotation = + apply(cls, arg :: Nil) + + def apply(cls: ClassSymbol, arg1: Tree, arg2: Tree)(implicit ctx: Context): Annotation = + apply(cls, arg1 :: arg2 :: Nil) + + def apply(cls: ClassSymbol, args: List[Tree])(implicit ctx: Context): Annotation = + apply(cls.typeRef, args) + + def apply(atp: Type, arg: Tree)(implicit ctx: Context): Annotation = + apply(atp, arg :: Nil) + + def apply(atp: Type, arg1: Tree, arg2: Tree)(implicit ctx: Context): Annotation = + apply(atp, arg1 :: arg2 :: Nil) + + def apply(atp: Type, args: List[Tree])(implicit ctx: Context): Annotation = + apply(New(atp, args)) + + private def resolveConstructor(atp: Type, args:List[Tree])(implicit ctx: Context): Tree = { + val targs = atp.argTypes + tpd.applyOverloaded(New(atp withoutArgs targs), nme.CONSTRUCTOR, args, targs, atp, isAnnotConstructor = true) + } + + def applyResolve(atp: Type, args: List[Tree])(implicit ctx: Context): Annotation = { + apply(resolveConstructor(atp, args)) + } + + def deferred(sym: Symbol, treeFn: Context => Tree)(implicit ctx: Context): Annotation = + new LazyAnnotation(sym) { + def complete(implicit ctx: Context) = treeFn(ctx) + } + + def deferred(atp: Type, args: List[Tree])(implicit ctx: Context): Annotation = + deferred(atp.classSymbol, implicit ctx => New(atp, args)) + + def deferredResolve(atp: Type, args: List[Tree])(implicit ctx: Context): Annotation = + deferred(atp.classSymbol, implicit ctx => resolveConstructor(atp, args)) + + def makeAlias(sym: TermSymbol)(implicit ctx: Context) = + apply(defn.AliasAnnot, List( + ref(TermRef.withSigAndDenot(sym.owner.thisType, sym.name, sym.signature, sym)))) + + def makeChild(sym: Symbol)(implicit ctx: Context) = + deferred(defn.ChildAnnot, + implicit ctx => New(defn.ChildAnnotType.appliedTo(sym.owner.thisType.select(sym.name, sym)), Nil)) + + def makeSourceFile(path: String)(implicit ctx: Context) = + apply(defn.SourceFileAnnot, Literal(Constant(path))) + } + + def ThrowsAnnotation(cls: ClassSymbol)(implicit ctx: Context) = { + val tref = cls.typeRef + Annotation(defn.ThrowsAnnotType.appliedTo(tref), Ident(tref)) + } + + /** A decorator that provides queries for specific annotations + * of a symbol. + */ + implicit class AnnotInfo(val sym: Symbol) extends AnyVal { + + def isDeprecated(implicit ctx: Context) = + sym.hasAnnotation(defn.DeprecatedAnnot) + + def deprecationMessage(implicit ctx: Context) = + for (annot <- sym.getAnnotation(defn.DeprecatedAnnot); + arg <- annot.argumentConstant(0)) + yield arg.stringValue + + def migrationVersion(implicit ctx: Context) = + for (annot <- sym.getAnnotation(defn.MigrationAnnot); + arg <- annot.argumentConstant(1)) + yield ScalaVersion.parse(arg.stringValue) + + def migrationMessage(implicit ctx: Context) = + for (annot <- sym.getAnnotation(defn.MigrationAnnot); + arg <- annot.argumentConstant(0)) + yield ScalaVersion.parse(arg.stringValue) + } +} diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala new file mode 100644 index 000000000..78ec685fc --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -0,0 +1,132 @@ +package dotty.tools +package dotc +package core + +import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._ +import SymDenotations._, Denotations.SingleDenotation +import util.Positions._ +import Decorators._ +import StdNames._ +import Annotations._ +import collection.mutable +import ast.tpd._ + +/** Realizability status */ +object CheckRealizable { + + abstract class Realizability(val msg: String) { + def andAlso(other: => Realizability) = + if (this == Realizable) other else this + def mapError(f: Realizability => Realizability) = + if (this == Realizable) this else f(this) + } + + object Realizable extends Realizability("") + + object NotConcrete extends Realizability(" is not a concrete type") + + object NotStable extends Realizability(" is not a stable reference") + + class NotFinal(sym: Symbol)(implicit ctx: Context) + extends Realizability(i" refers to nonfinal $sym") + + class HasProblemBounds(typ: SingleDenotation)(implicit ctx: Context) + extends Realizability(i" has a member $typ with possibly conflicting bounds ${typ.info.bounds.lo} <: ... <: ${typ.info.bounds.hi}") + + class HasProblemField(fld: SingleDenotation, problem: Realizability)(implicit ctx: Context) + extends Realizability(i" has a member $fld which is not a legal path\n since ${fld.symbol.name}: ${fld.info}${problem.msg}") + + class ProblemInUnderlying(tp: Type, problem: Realizability)(implicit ctx: Context) + extends Realizability(i"s underlying type ${tp}${problem.msg}") { + assert(problem != Realizable) + } + + def realizability(tp: Type)(implicit ctx: Context) = + new CheckRealizable().realizability(tp) + + def boundsRealizability(tp: Type)(implicit ctx: Context) = + new CheckRealizable().boundsRealizability(tp) +} + +/** Compute realizability status */ +class CheckRealizable(implicit ctx: Context) { + import CheckRealizable._ + + /** A set of all fields that have already been checked. Used + * to avoid infinite recursions when analyzing recursive types. + */ + private val checkedFields: mutable.Set[Symbol] = mutable.LinkedHashSet[Symbol]() + + /** Is symbol's definitition a lazy val? + * (note we exclude modules here, because their realizability is ensured separately) + */ + private def isLateInitialized(sym: Symbol) = sym.is(Lazy, butNot = Module) + + /** The realizability status of given type `tp`*/ + def realizability(tp: Type): Realizability = tp.dealias match { + case tp: TermRef => + val sym = tp.symbol + if (sym.is(Stable)) realizability(tp.prefix) + else { + val r = + if (!sym.isStable) NotStable + else if (!isLateInitialized(sym)) realizability(tp.prefix) + else if (!sym.isEffectivelyFinal) new NotFinal(sym) + else realizability(tp.info).mapError(r => new ProblemInUnderlying(tp.info, r)) + if (r == Realizable) sym.setFlag(Stable) + r + } + case _: SingletonType | NoPrefix => + Realizable + case tp => + def isConcrete(tp: Type): Boolean = tp.dealias match { + case tp: TypeRef => tp.symbol.isClass + case tp: TypeProxy => isConcrete(tp.underlying) + case tp: AndOrType => isConcrete(tp.tp1) && isConcrete(tp.tp2) + case _ => false + } + if (!isConcrete(tp)) NotConcrete + else boundsRealizability(tp).andAlso(memberRealizability(tp)) + } + + /** `Realizable` if `tp` has good bounds, a `HasProblemBounds` instance + * pointing to a bad bounds member otherwise. + */ + private def boundsRealizability(tp: Type) = { + def hasBadBounds(mbr: SingleDenotation) = { + val bounds = mbr.info.bounds + !(bounds.lo <:< bounds.hi) + } + tp.nonClassTypeMembers.find(hasBadBounds) match { + case Some(mbr) => new HasProblemBounds(mbr) + case _ => Realizable + } + } + + /** `Realizable` if all of `tp`'s non-struct fields have realizable types, + * a `HasProblemField` instance pointing to a bad field otherwise. + */ + private def memberRealizability(tp: Type) = { + def checkField(sofar: Realizability, fld: SingleDenotation): Realizability = + sofar andAlso { + if (checkedFields.contains(fld.symbol) || fld.symbol.is(Private | Mutable | Lazy)) + // if field is private it cannot be part of a visible path + // if field is mutable it cannot be part of a path + // if field is lazy it does not need to be initialized when the owning object is + // so in all cases the field does not influence realizability of the enclosing object. + Realizable + else { + checkedFields += fld.symbol + realizability(fld.info).mapError(r => new HasProblemField(fld, r)) + } + } + if (ctx.settings.strict.value) + // check fields only under strict mode for now. + // Reason: An embedded field could well be nullable, which means it + // should not be part of a path and need not be checked; but we cannot recognize + // this situation until we have a typesystem that tracks nullability. + ((Realizable: Realizability) /: tp.fields)(checkField) + else + Realizable + } +} diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala new file mode 100644 index 000000000..1e623db4d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/Comments.scala @@ -0,0 +1,459 @@ +package dotty.tools +package dotc +package core + +import ast.{ untpd, tpd } +import Decorators._, Symbols._, Contexts._, Flags.EmptyFlags +import util.SourceFile +import util.Positions._ +import util.CommentParsing._ +import util.Property.Key +import parsing.Parsers.Parser +import reporting.diagnostic.messages.ProperDefinitionNotFound + +object Comments { + val ContextDoc = new Key[ContextDocstrings] + + /** Decorator for getting docbase out of context */ + implicit class CommentsContext(val ctx: Context) extends AnyVal { + def docCtx: Option[ContextDocstrings] = ctx.property(ContextDoc) + } + + /** Context for Docstrings, contains basic functionality for getting + * docstrings via `Symbol` and expanding templates + */ + class ContextDocstrings { + import scala.collection.mutable + + private[this] val _docstrings: mutable.Map[Symbol, Comment] = + mutable.Map.empty + + val templateExpander = new CommentExpander + + def docstrings: Map[Symbol, Comment] = _docstrings.toMap + + def docstring(sym: Symbol): Option[Comment] = _docstrings.get(sym) + + def addDocstring(sym: Symbol, doc: Option[Comment]): Unit = + doc.map(d => _docstrings += (sym -> d)) + } + + /** A `Comment` contains the unformatted docstring as well as a position + * + * The `Comment` contains functionality to create versions of itself without + * `@usecase` sections as well as functionality to map the `raw` docstring + */ + abstract case class Comment(pos: Position, raw: String) { self => + def isExpanded: Boolean + + def usecases: List[UseCase] + + val isDocComment = raw.startsWith("/**") + + def expand(f: String => String): Comment = new Comment(pos, f(raw)) { + val isExpanded = true + val usecases = self.usecases + } + + def withUsecases(implicit ctx: Context): Comment = new Comment(pos, stripUsecases) { + val isExpanded = self.isExpanded + val usecases = parseUsecases + } + + private[this] lazy val stripUsecases: String = + removeSections(raw, "@usecase", "@define") + + private[this] def parseUsecases(implicit ctx: Context): List[UseCase] = + if (!raw.startsWith("/**")) + List.empty[UseCase] + else + tagIndex(raw) + .filter { startsWithTag(raw, _, "@usecase") } + .map { case (start, end) => decomposeUseCase(start, end) } + + /** Turns a usecase section into a UseCase, with code changed to: + * {{{ + * // From: + * def foo: A + * // To: + * def foo: A = ??? + * }}} + */ + private[this] def decomposeUseCase(start: Int, end: Int)(implicit ctx: Context): UseCase = { + def subPos(start: Int, end: Int) = + if (pos == NoPosition) NoPosition + else { + val start1 = pos.start + start + val end1 = pos.end + end + pos withStart start1 withPoint start1 withEnd end1 + } + + val codeStart = skipWhitespace(raw, start + "@usecase".length) + val codeEnd = skipToEol(raw, codeStart) + val code = raw.substring(codeStart, codeEnd) + " = ???" + val codePos = subPos(codeStart, codeEnd) + val commentStart = skipLineLead(raw, codeEnd + 1) min end + val commentStr = "/** " + raw.substring(commentStart, end) + "*/" + val commentPos = subPos(commentStart, end) + + UseCase(Comment(commentPos, commentStr), code, codePos) + } + } + + object Comment { + def apply(pos: Position, raw: String, expanded: Boolean = false, usc: List[UseCase] = Nil)(implicit ctx: Context): Comment = + new Comment(pos, raw) { + val isExpanded = expanded + val usecases = usc + } + } + + abstract case class UseCase(comment: Comment, code: String, codePos: Position) { + /** Set by typer */ + var tpdCode: tpd.DefDef = _ + + def untpdCode: untpd.Tree + } + + object UseCase { + def apply(comment: Comment, code: String, codePos: Position)(implicit ctx: Context) = + new UseCase(comment, code, codePos) { + val untpdCode = { + val tree = new Parser(new SourceFile("", code)).localDef(codePos.start, EmptyFlags) + + tree match { + case tree: untpd.DefDef => + val newName = (tree.name.show + "$" + codePos + "$doc").toTermName + untpd.DefDef(newName, tree.tparams, tree.vparamss, tree.tpt, tree.rhs) + case _ => + ctx.error(ProperDefinitionNotFound(), codePos) + tree + } + } + } + } + + /** + * Port of DocComment.scala from nsc + * @author Martin Odersky + * @author Felix Mulder + */ + class CommentExpander { + import dotc.config.Printers.dottydoc + import scala.collection.mutable + + def expand(sym: Symbol, site: Symbol)(implicit ctx: Context): String = { + val parent = if (site != NoSymbol) site else sym + defineVariables(parent) + expandedDocComment(sym, parent) + } + + /** The cooked doc comment of symbol `sym` after variable expansion, or "" if missing. + * + * @param sym The symbol for which doc comment is returned + * @param site The class for which doc comments are generated + * @throws ExpansionLimitExceeded when more than 10 successive expansions + * of the same string are done, which is + * interpreted as a recursive variable definition. + */ + def expandedDocComment(sym: Symbol, site: Symbol, docStr: String = "")(implicit ctx: Context): String = { + // when parsing a top level class or module, use the (module-)class itself to look up variable definitions + val parent = if ((sym.is(Flags.Module) || sym.isClass) && site.is(Flags.Package)) sym + else site + expandVariables(cookedDocComment(sym, docStr), sym, parent) + } + + private def template(raw: String): String = + removeSections(raw, "@define") + + private def defines(raw: String): List[String] = { + val sections = tagIndex(raw) + val defines = sections filter { startsWithTag(raw, _, "@define") } + val usecases = sections filter { startsWithTag(raw, _, "@usecase") } + val end = startTag(raw, (defines ::: usecases).sortBy(_._1)) + + defines map { case (start, end) => raw.substring(start, end) } + } + + private def replaceInheritDocToInheritdoc(docStr: String): String = + docStr.replaceAll("""\{@inheritDoc\p{Zs}*\}""", "@inheritdoc") + + /** The cooked doc comment of an overridden symbol */ + protected def superComment(sym: Symbol)(implicit ctx: Context): Option[String] = + allInheritedOverriddenSymbols(sym).iterator map (x => cookedDocComment(x)) find (_ != "") + + private val cookedDocComments = mutable.HashMap[Symbol, String]() + + /** The raw doc comment of symbol `sym`, minus usecase and define sections, augmented by + * missing sections of an inherited doc comment. + * If a symbol does not have a doc comment but some overridden version of it does, + * the doc comment of the overridden version is copied instead. + */ + def cookedDocComment(sym: Symbol, docStr: String = "")(implicit ctx: Context): String = cookedDocComments.getOrElseUpdate(sym, { + var ownComment = + if (docStr.length == 0) ctx.docCtx.flatMap(_.docstring(sym).map(c => template(c.raw))).getOrElse("") + else template(docStr) + ownComment = replaceInheritDocToInheritdoc(ownComment) + + superComment(sym) match { + case None => + // SI-8210 - The warning would be false negative when this symbol is a setter + if (ownComment.indexOf("@inheritdoc") != -1 && ! sym.isSetter) + dottydoc.println(s"${sym.pos}: the comment for ${sym} contains @inheritdoc, but no parent comment is available to inherit from.") + ownComment.replaceAllLiterally("@inheritdoc", "") + case Some(sc) => + if (ownComment == "") sc + else expandInheritdoc(sc, merge(sc, ownComment, sym), sym) + } + }) + + private def isMovable(str: String, sec: (Int, Int)): Boolean = + startsWithTag(str, sec, "@param") || + startsWithTag(str, sec, "@tparam") || + startsWithTag(str, sec, "@return") + + def merge(src: String, dst: String, sym: Symbol, copyFirstPara: Boolean = false): String = { + val srcSections = tagIndex(src) + val dstSections = tagIndex(dst) + val srcParams = paramDocs(src, "@param", srcSections) + val dstParams = paramDocs(dst, "@param", dstSections) + val srcTParams = paramDocs(src, "@tparam", srcSections) + val dstTParams = paramDocs(dst, "@tparam", dstSections) + val out = new StringBuilder + var copied = 0 + var tocopy = startTag(dst, dstSections dropWhile (!isMovable(dst, _))) + + if (copyFirstPara) { + val eop = // end of comment body (first para), which is delimited by blank line, or tag, or end of comment + (findNext(src, 0)(src.charAt(_) == '\n')) min startTag(src, srcSections) + out append src.substring(0, eop).trim + copied = 3 + tocopy = 3 + } + + def mergeSection(srcSec: Option[(Int, Int)], dstSec: Option[(Int, Int)]) = dstSec match { + case Some((start, end)) => + if (end > tocopy) tocopy = end + case None => + srcSec match { + case Some((start1, end1)) => { + out append dst.substring(copied, tocopy).trim + out append "\n" + copied = tocopy + out append src.substring(start1, end1).trim + } + case None => + } + } + + //TODO: enable this once you know how to get `sym.paramss` + /* + for (params <- sym.paramss; param <- params) + mergeSection(srcParams get param.name.toString, dstParams get param.name.toString) + for (tparam <- sym.typeParams) + mergeSection(srcTParams get tparam.name.toString, dstTParams get tparam.name.toString) + + mergeSection(returnDoc(src, srcSections), returnDoc(dst, dstSections)) + mergeSection(groupDoc(src, srcSections), groupDoc(dst, dstSections)) + */ + + if (out.length == 0) dst + else { + out append dst.substring(copied) + out.toString + } + } + + /** + * Expand inheritdoc tags + * - for the main comment we transform the inheritdoc into the super variable, + * and the variable expansion can expand it further + * - for the param, tparam and throws sections we must replace comments on the spot + * + * This is done separately, for two reasons: + * 1. It takes longer to run compared to merge + * 2. The inheritdoc annotation should not be used very often, as building the comment from pieces severely + * impacts performance + * + * @param parent The source (or parent) comment + * @param child The child (overriding member or usecase) comment + * @param sym The child symbol + * @return The child comment with the inheritdoc sections expanded + */ + def expandInheritdoc(parent: String, child: String, sym: Symbol): String = + if (child.indexOf("@inheritdoc") == -1) + child + else { + val parentSections = tagIndex(parent) + val childSections = tagIndex(child) + val parentTagMap = sectionTagMap(parent, parentSections) + val parentNamedParams = Map() + + ("@param" -> paramDocs(parent, "@param", parentSections)) + + ("@tparam" -> paramDocs(parent, "@tparam", parentSections)) + + ("@throws" -> paramDocs(parent, "@throws", parentSections)) + + val out = new StringBuilder + + def replaceInheritdoc(childSection: String, parentSection: => String) = + if (childSection.indexOf("@inheritdoc") == -1) + childSection + else + childSection.replaceAllLiterally("@inheritdoc", parentSection) + + def getParentSection(section: (Int, Int)): String = { + + def getSectionHeader = extractSectionTag(child, section) match { + case param@("@param"|"@tparam"|"@throws") => param + " " + extractSectionParam(child, section) + case other => other + } + + def sectionString(param: String, paramMap: Map[String, (Int, Int)]): String = + paramMap.get(param) match { + case Some(section) => + // Cleanup the section tag and parameter + val sectionTextBounds = extractSectionText(parent, section) + cleanupSectionText(parent.substring(sectionTextBounds._1, sectionTextBounds._2)) + case None => + dottydoc.println(s"""${sym.pos}: the """" + getSectionHeader + "\" annotation of the " + sym + + " comment contains @inheritdoc, but the corresponding section in the parent is not defined.") + "" + } + + child.substring(section._1, section._1 + 7) match { + case param@("@param "|"@tparam"|"@throws") => + sectionString(extractSectionParam(child, section), parentNamedParams(param.trim)) + case _ => + sectionString(extractSectionTag(child, section), parentTagMap) + } + } + + def mainComment(str: String, sections: List[(Int, Int)]): String = + if (str.trim.length > 3) + str.trim.substring(3, startTag(str, sections)) + else + "" + + // Append main comment + out.append("/**") + out.append(replaceInheritdoc(mainComment(child, childSections), mainComment(parent, parentSections))) + + // Append sections + for (section <- childSections) + out.append(replaceInheritdoc(child.substring(section._1, section._2), getParentSection(section))) + + out.append("*/") + out.toString + } + + protected def expandVariables(initialStr: String, sym: Symbol, site: Symbol)(implicit ctx: Context): String = { + val expandLimit = 10 + + def expandInternal(str: String, depth: Int): String = { + if (depth >= expandLimit) + throw new ExpansionLimitExceeded(str) + + val out = new StringBuilder + var copied, idx = 0 + // excluding variables written as \$foo so we can use them when + // necessary to document things like Symbol#decode + def isEscaped = idx > 0 && str.charAt(idx - 1) == '\\' + while (idx < str.length) { + if ((str charAt idx) != '$' || isEscaped) + idx += 1 + else { + val vstart = idx + idx = skipVariable(str, idx + 1) + def replaceWith(repl: String) = { + out append str.substring(copied, vstart) + out append repl + copied = idx + } + variableName(str.substring(vstart + 1, idx)) match { + case "super" => + superComment(sym) foreach { sc => + val superSections = tagIndex(sc) + replaceWith(sc.substring(3, startTag(sc, superSections))) + for (sec @ (start, end) <- superSections) + if (!isMovable(sc, sec)) out append sc.substring(start, end) + } + case "" => idx += 1 + case vname => + lookupVariable(vname, site) match { + case Some(replacement) => replaceWith(replacement) + case None => + dottydoc.println(s"Variable $vname undefined in comment for $sym in $site") + } + } + } + } + if (out.length == 0) str + else { + out append str.substring(copied) + expandInternal(out.toString, depth + 1) + } + } + + // We suppressed expanding \$ throughout the recursion, and now we + // need to replace \$ with $ so it looks as intended. + expandInternal(initialStr, 0).replaceAllLiterally("""\$""", "$") + } + + def defineVariables(sym: Symbol)(implicit ctx: Context) = { + val Trim = "(?s)^[\\s&&[^\n\r]]*(.*?)\\s*$".r + + val raw = ctx.docCtx.flatMap(_.docstring(sym).map(_.raw)).getOrElse("") + defs(sym) ++= defines(raw).map { + str => { + val start = skipWhitespace(str, "@define".length) + val (key, value) = str.splitAt(skipVariable(str, start)) + key.drop(start) -> value + } + } map { + case (key, Trim(value)) => + variableName(key) -> value.replaceAll("\\s+\\*+$", "") + } + } + + /** Maps symbols to the variable -> replacement maps that are defined + * in their doc comments + */ + private val defs = mutable.HashMap[Symbol, Map[String, String]]() withDefaultValue Map() + + /** Lookup definition of variable. + * + * @param vble The variable for which a definition is searched + * @param site The class for which doc comments are generated + */ + def lookupVariable(vble: String, site: Symbol)(implicit ctx: Context): Option[String] = site match { + case NoSymbol => None + case _ => + val searchList = + if (site.flags.is(Flags.Module)) site :: site.info.baseClasses + else site.info.baseClasses + + searchList collectFirst { case x if defs(x) contains vble => defs(x)(vble) } match { + case Some(str) if str startsWith "$" => lookupVariable(str.tail, site) + case res => res orElse lookupVariable(vble, site.owner) + } + } + + /** The position of the raw doc comment of symbol `sym`, or NoPosition if missing + * If a symbol does not have a doc comment but some overridden version of it does, + * the position of the doc comment of the overridden version is returned instead. + */ + def docCommentPos(sym: Symbol)(implicit ctx: Context): Position = + ctx.docCtx.flatMap(_.docstring(sym).map(_.pos)).getOrElse(NoPosition) + + /** A version which doesn't consider self types, as a temporary measure: + * an infinite loop has broken out between superComment and cookedDocComment + * since r23926. + */ + private def allInheritedOverriddenSymbols(sym: Symbol)(implicit ctx: Context): List[Symbol] = { + if (!sym.owner.isClass) Nil + else sym.allOverriddenSymbols.toList.filter(_ != NoSymbol) //TODO: could also be `sym.owner.allOverrid..` + //else sym.owner.ancestors map (sym overriddenSymbol _) filter (_ != NoSymbol) + } + + class ExpansionLimitExceeded(str: String) extends Exception + } +} diff --git a/compiler/src/dotty/tools/dotc/core/Constants.scala b/compiler/src/dotty/tools/dotc/core/Constants.scala new file mode 100644 index 000000000..1892e4bdc --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/Constants.scala @@ -0,0 +1,235 @@ +package dotty.tools.dotc +package core + +import Types._, Symbols._, Contexts._ +import printing.Printer + +object Constants { + + final val NoTag = 0 + final val UnitTag = 1 + final val BooleanTag = 2 + final val ByteTag = 3 + final val ShortTag = 4 + final val CharTag = 5 + final val IntTag = 6 + final val LongTag = 7 + final val FloatTag = 8 + final val DoubleTag = 9 + final val StringTag = 10 + final val NullTag = 11 + final val ClazzTag = 12 + // For supporting java enumerations inside java annotations (see ClassfileParser) + final val EnumTag = 13 + + case class Constant(value: Any) extends printing.Showable { + import java.lang.Double.doubleToRawLongBits + import java.lang.Float.floatToRawIntBits + + val tag: Int = value match { + case null => NullTag + case x: Unit => UnitTag + case x: Boolean => BooleanTag + case x: Byte => ByteTag + case x: Short => ShortTag + case x: Int => IntTag + case x: Long => LongTag + case x: Float => FloatTag + case x: Double => DoubleTag + case x: String => StringTag + case x: Char => CharTag + case x: Type => ClazzTag + case x: Symbol => EnumTag + case _ => throw new Error("bad constant value: " + value + " of class " + value.getClass) + } + + def isByteRange: Boolean = isIntRange && Byte.MinValue <= intValue && intValue <= Byte.MaxValue + def isShortRange: Boolean = isIntRange && Short.MinValue <= intValue && intValue <= Short.MaxValue + def isCharRange: Boolean = isIntRange && Char.MinValue <= intValue && intValue <= Char.MaxValue + def isIntRange: Boolean = ByteTag <= tag && tag <= IntTag + def isLongRange: Boolean = ByteTag <= tag && tag <= LongTag + def isFloatRange: Boolean = ByteTag <= tag && tag <= FloatTag + def isNumeric: Boolean = ByteTag <= tag && tag <= DoubleTag + def isNonUnitAnyVal = BooleanTag <= tag && tag <= DoubleTag + def isAnyVal = UnitTag <= tag && tag <= DoubleTag + + def tpe(implicit ctx: Context): Type = tag match { + case UnitTag => defn.UnitType + case BooleanTag => defn.BooleanType + case ByteTag => defn.ByteType + case ShortTag => defn.ShortType + case CharTag => defn.CharType + case IntTag => defn.IntType + case LongTag => defn.LongType + case FloatTag => defn.FloatType + case DoubleTag => defn.DoubleType + case StringTag => defn.StringType + case NullTag => defn.NullType + case ClazzTag => defn.ClassType(typeValue) + case EnumTag => defn.EnumType(symbolValue) + } + + /** We need the equals method to take account of tags as well as values. + */ + override def equals(other: Any): Boolean = other match { + case that: Constant => + this.tag == that.tag && equalHashValue == that.equalHashValue + case _ => false + } + + def isNaN = value match { + case f: Float => f.isNaN + case d: Double => d.isNaN + case _ => false + } + + def booleanValue: Boolean = + if (tag == BooleanTag) value.asInstanceOf[Boolean] + else throw new Error("value " + value + " is not a boolean") + + def byteValue: Byte = tag match { + case ByteTag => value.asInstanceOf[Byte] + case ShortTag => value.asInstanceOf[Short].toByte + case CharTag => value.asInstanceOf[Char].toByte + case IntTag => value.asInstanceOf[Int].toByte + case LongTag => value.asInstanceOf[Long].toByte + case FloatTag => value.asInstanceOf[Float].toByte + case DoubleTag => value.asInstanceOf[Double].toByte + case _ => throw new Error("value " + value + " is not a Byte") + } + + def shortValue: Short = tag match { + case ByteTag => value.asInstanceOf[Byte].toShort + case ShortTag => value.asInstanceOf[Short] + case CharTag => value.asInstanceOf[Char].toShort + case IntTag => value.asInstanceOf[Int].toShort + case LongTag => value.asInstanceOf[Long].toShort + case FloatTag => value.asInstanceOf[Float].toShort + case DoubleTag => value.asInstanceOf[Double].toShort + case _ => throw new Error("value " + value + " is not a Short") + } + + def charValue: Char = tag match { + case ByteTag => value.asInstanceOf[Byte].toChar + case ShortTag => value.asInstanceOf[Short].toChar + case CharTag => value.asInstanceOf[Char] + case IntTag => value.asInstanceOf[Int].toChar + case LongTag => value.asInstanceOf[Long].toChar + case FloatTag => value.asInstanceOf[Float].toChar + case DoubleTag => value.asInstanceOf[Double].toChar + case _ => throw new Error("value " + value + " is not a Char") + } + + def intValue: Int = tag match { + case ByteTag => value.asInstanceOf[Byte].toInt + case ShortTag => value.asInstanceOf[Short].toInt + case CharTag => value.asInstanceOf[Char].toInt + case IntTag => value.asInstanceOf[Int] + case LongTag => value.asInstanceOf[Long].toInt + case FloatTag => value.asInstanceOf[Float].toInt + case DoubleTag => value.asInstanceOf[Double].toInt + case _ => throw new Error("value " + value + " is not an Int") + } + + def longValue: Long = tag match { + case ByteTag => value.asInstanceOf[Byte].toLong + case ShortTag => value.asInstanceOf[Short].toLong + case CharTag => value.asInstanceOf[Char].toLong + case IntTag => value.asInstanceOf[Int].toLong + case LongTag => value.asInstanceOf[Long] + case FloatTag => value.asInstanceOf[Float].toLong + case DoubleTag => value.asInstanceOf[Double].toLong + case _ => throw new Error("value " + value + " is not a Long") + } + + def floatValue: Float = tag match { + case ByteTag => value.asInstanceOf[Byte].toFloat + case ShortTag => value.asInstanceOf[Short].toFloat + case CharTag => value.asInstanceOf[Char].toFloat + case IntTag => value.asInstanceOf[Int].toFloat + case LongTag => value.asInstanceOf[Long].toFloat + case FloatTag => value.asInstanceOf[Float] + case DoubleTag => value.asInstanceOf[Double].toFloat + case _ => throw new Error("value " + value + " is not a Float") + } + + def doubleValue: Double = tag match { + case ByteTag => value.asInstanceOf[Byte].toDouble + case ShortTag => value.asInstanceOf[Short].toDouble + case CharTag => value.asInstanceOf[Char].toDouble + case IntTag => value.asInstanceOf[Int].toDouble + case LongTag => value.asInstanceOf[Long].toDouble + case FloatTag => value.asInstanceOf[Float].toDouble + case DoubleTag => value.asInstanceOf[Double] + case _ => throw new Error("value " + value + " is not a Double") + } + + /** Convert constant value to conform to given type. + */ + def convertTo(pt: Type)(implicit ctx: Context): Constant = { + def classBound(pt: Type): Type = pt.dealias.stripTypeVar match { + case tref: TypeRef if !tref.symbol.isClass => classBound(tref.info.bounds.lo) + case param: PolyParam => + ctx.typerState.constraint.entry(param) match { + case TypeBounds(lo, hi) => + if (hi.classSymbol.isPrimitiveValueClass) hi //constrain further with high bound + else classBound(lo) + case NoType => classBound(param.binder.paramBounds(param.paramNum).lo) + case inst => classBound(inst) + } + case pt => pt + } + val target = classBound(pt).typeSymbol + if (target == tpe.typeSymbol) + this + else if ((target == defn.ByteClass) && isByteRange) + Constant(byteValue) + else if (target == defn.ShortClass && isShortRange) + Constant(shortValue) + else if (target == defn.CharClass && isCharRange) + Constant(charValue) + else if (target == defn.IntClass && isIntRange) + Constant(intValue) + else if (target == defn.LongClass && isLongRange) + Constant(longValue) + else if (target == defn.FloatClass && isFloatRange) + Constant(floatValue) + else if (target == defn.DoubleClass && isNumeric) + Constant(doubleValue) + else + null + } + + def stringValue: String = value.toString + + def toText(printer: Printer) = printer.toText(this) + + def typeValue: Type = value.asInstanceOf[Type] + def symbolValue: Symbol = value.asInstanceOf[Symbol] + + /** + * Consider two `NaN`s to be identical, despite non-equality + * Consider -0d to be distinct from 0d, despite equality + * + * We use the raw versions (i.e. `floatToRawIntBits` rather than `floatToIntBits`) + * to avoid treating different encodings of `NaN` as the same constant. + * You probably can't express different `NaN` varieties as compile time + * constants in regular Scala code, but it is conceivable that you could + * conjure them with a macro. + */ + private def equalHashValue: Any = value match { + case f: Float => floatToRawIntBits(f) + case d: Double => doubleToRawLongBits(d) + case v => v + } + + override def hashCode: Int = { + import scala.util.hashing.MurmurHash3._ + val seed = 17 + var h = seed + h = mix(h, tag.##) // include tag in the hash, otherwise 0, 0d, 0L, 0f collide. + h = mix(h, equalHashValue.##) + finalizeHash(h, length = 2) + } + } +} diff --git a/compiler/src/dotty/tools/dotc/core/Constraint.scala b/compiler/src/dotty/tools/dotc/core/Constraint.scala new file mode 100644 index 000000000..c99b748b7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/Constraint.scala @@ -0,0 +1,154 @@ +package dotty.tools +package dotc +package core + +import Types._, Contexts._, Symbols._ +import util.SimpleMap +import collection.mutable +import printing.{Printer, Showable} +import printing.Texts._ +import config.Config +import config.Printers.constr + +/** Constraint over undetermined type parameters. Constraints are built + * over values of the following types: + * + * - PolyType A constraint constrains the type parameters of a set of PolyTypes + * - PolyParam The parameters of the constrained polytypes + * - TypeVar Every constrained parameter might be associated with a TypeVar + * that has the PolyParam as origin. + */ +abstract class Constraint extends Showable { + + type This <: Constraint + + /** Does the constraint's domain contain the type parameters of `pt`? */ + def contains(pt: PolyType): Boolean + + /** Does the constraint's domain contain the type parameter `param`? */ + def contains(param: PolyParam): Boolean + + /** Does this constraint contain the type variable `tvar` and is it uninstantiated? */ + def contains(tvar: TypeVar): Boolean + + /** The constraint entry for given type parameter `param`, or NoType if `param` is not part of + * the constraint domain. Note: Low level, implementation dependent. + */ + def entry(param: PolyParam): Type + + /** The type variable corresponding to parameter `param`, or + * NoType, if `param` is not in constrained or is not paired with a type variable. + */ + def typeVarOfParam(param: PolyParam): Type + + /** Is it known that `param1 <:< param2`? */ + def isLess(param1: PolyParam, param2: PolyParam): Boolean + + /** The parameters that are known to be smaller wrt <: than `param` */ + def lower(param: PolyParam): List[PolyParam] + + /** The parameters that are known to be greater wrt <: than `param` */ + def upper(param: PolyParam): List[PolyParam] + + /** lower(param) \ lower(butNot) */ + def exclusiveLower(param: PolyParam, butNot: PolyParam): List[PolyParam] + + /** upper(param) \ upper(butNot) */ + def exclusiveUpper(param: PolyParam, butNot: PolyParam): List[PolyParam] + + /** The constraint bounds for given type parameter `param`. + * Poly params that are known to be smaller or greater than `param` + * are not contained in the return bounds. + * @pre `param` is not part of the constraint domain. + */ + def nonParamBounds(param: PolyParam): TypeBounds + + /** The lower bound of `param` including all known-to-be-smaller parameters */ + def fullLowerBound(param: PolyParam)(implicit ctx: Context): Type + + /** The upper bound of `param` including all known-to-be-greater parameters */ + def fullUpperBound(param: PolyParam)(implicit ctx: Context): Type + + /** The bounds of `param` including all known-to-be-smaller and -greater parameters */ + def fullBounds(param: PolyParam)(implicit ctx: Context): TypeBounds + + /** A new constraint which is derived from this constraint by adding + * entries for all type parameters of `poly`. + * @param tvars A list of type variables associated with the params, + * or Nil if the constraint will just be checked for + * satisfiability but will solved to give instances of + * type variables. + */ + def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This + + /** A new constraint which is derived from this constraint by updating + * the entry for parameter `param` to `tp`. + * `tp` can be one of the following: + * + * - A TypeBounds value, indicating new constraint bounds + * - Another type, indicating a solution for the parameter + * + * @pre `this contains param`. + */ + def updateEntry(param: PolyParam, tp: Type)(implicit ctx: Context): This + + /** A constraint that includes the relationship `p1 <: p2`. + * `<:` relationships between parameters ("edges") are propagated, but + * non-parameter bounds are left alone. + */ + def addLess(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This + + /** A constraint resulting from adding p2 = p1 to this constraint, and at the same + * time transferring all bounds of p2 to p1 + */ + def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This + + /** A new constraint which is derived from this constraint by removing + * the type parameter `param` from the domain and replacing all top-level occurrences + * of the parameter elsewhere in the constraint by type `tp`, or a conservative + * approximation of it if that is needed to avoid cycles. + * Occurrences nested inside a refinement or prefix are not affected. + */ + def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This + + /** Narrow one of the bounds of type parameter `param` + * If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure + * that `param >: bound`. + */ + def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This + + /** Is entry associated with `pt` removable? This is the case if + * all type parameters of the entry are associated with type variables + * which have their `inst` fields set. + */ + def isRemovable(pt: PolyType): Boolean + + /** A new constraint with all entries coming from `pt` removed. */ + def remove(pt: PolyType)(implicit ctx: Context): This + + /** The polytypes constrained by this constraint */ + def domainPolys: List[PolyType] + + /** The polytype parameters constrained by this constraint */ + def domainParams: List[PolyParam] + + /** Check whether predicate holds for all parameters in constraint */ + def forallParams(p: PolyParam => Boolean): Boolean + + /** Perform operation `op` on all typevars, or only on uninstantiated + * typevars, depending on whether `uninstOnly` is set or not. + */ + def foreachTypeVar(op: TypeVar => Unit): Unit + + /** The uninstantiated typevars of this constraint */ + def uninstVars: collection.Seq[TypeVar] + + /** The weakest constraint that subsumes both this constraint and `other` */ + def & (other: Constraint)(implicit ctx: Context): Constraint + + /** Check that no constrained parameter contains itself as a bound */ + def checkNonCyclic()(implicit ctx: Context): Unit + + /** Check that constraint only refers to PolyParams bound by itself */ + def checkClosed()(implicit ctx: Context): Unit +} diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala new file mode 100644 index 000000000..0e155b9e1 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -0,0 +1,458 @@ +package dotty.tools +package dotc +package core + +import Types._, Contexts._, Symbols._ +import Decorators._ +import config.Config +import config.Printers.{constr, typr} +import TypeApplications.EtaExpansion +import collection.mutable + +/** Methods for adding constraints and solving them. + * + * What goes into a Constraint as opposed to a ConstrainHandler? + * + * Constraint code is purely functional: Operations get constraints and produce new ones. + * Constraint code does not have access to a type-comparer. Anything regarding lubs and glbs has to be done + * elsewhere. + * + * By comparison: Constraint handlers are parts of type comparers and can use their functionality. + * Constraint handlers update the current constraint as a side effect. + */ +trait ConstraintHandling { + + implicit val ctx: Context + + protected def isSubType(tp1: Type, tp2: Type): Boolean + protected def isSameType(tp1: Type, tp2: Type): Boolean + + val state: TyperState + import state.constraint + + private var addConstraintInvocations = 0 + + /** If the constraint is frozen we cannot add new bounds to the constraint. */ + protected var frozenConstraint = false + + protected var alwaysFluid = false + + /** Perform `op` in a mode where all attempts to set `frozen` to true are ignored */ + def fluidly[T](op: => T): T = { + val saved = alwaysFluid + alwaysFluid = true + try op finally alwaysFluid = saved + } + + /** We are currently comparing polytypes. Used as a flag for + * optimization: when `false`, no need to do an expensive `pruneLambdaParams` + */ + protected var comparedPolyTypes: Set[PolyType] = Set.empty + + private def addOneBound(param: PolyParam, bound: Type, isUpper: Boolean): Boolean = + !constraint.contains(param) || { + def occursIn(bound: Type): Boolean = { + val b = bound.dealias + (b eq param) || { + b match { + case b: AndOrType => occursIn(b.tp1) || occursIn(b.tp2) + case b: TypeVar => occursIn(b.origin) + case _ => false + } + } + } + if (Config.checkConstraintsSeparated) + assert(!occursIn(bound), s"$param occurs in $bound") + val c1 = constraint.narrowBound(param, bound, isUpper) + (c1 eq constraint) || { + constraint = c1 + val TypeBounds(lo, hi) = constraint.entry(param) + isSubType(lo, hi) + } + } + + protected def addUpperBound(param: PolyParam, bound: Type): Boolean = { + def description = i"constraint $param <: $bound to\n$constraint" + if (bound.isRef(defn.NothingClass) && ctx.typerState.isGlobalCommittable) { + def msg = s"!!! instantiated to Nothing: $param, constraint = ${constraint.show}" + if (Config.failOnInstantiationToNothing) assert(false, msg) + else ctx.log(msg) + } + constr.println(i"adding $description") + val lower = constraint.lower(param) + val res = + addOneBound(param, bound, isUpper = true) && + lower.forall(addOneBound(_, bound, isUpper = true)) + constr.println(i"added $description = $res") + res + } + + protected def addLowerBound(param: PolyParam, bound: Type): Boolean = { + def description = i"constraint $param >: $bound to\n$constraint" + constr.println(i"adding $description") + val upper = constraint.upper(param) + val res = + addOneBound(param, bound, isUpper = false) && + upper.forall(addOneBound(_, bound, isUpper = false)) + constr.println(i"added $description = $res") + res + } + + protected def addLess(p1: PolyParam, p2: PolyParam): Boolean = { + def description = i"ordering $p1 <: $p2 to\n$constraint" + val res = + if (constraint.isLess(p2, p1)) unify(p2, p1) + else { + val down1 = p1 :: constraint.exclusiveLower(p1, p2) + val up2 = p2 :: constraint.exclusiveUpper(p2, p1) + val lo1 = constraint.nonParamBounds(p1).lo + val hi2 = constraint.nonParamBounds(p2).hi + constr.println(i"adding $description down1 = $down1, up2 = $up2") + constraint = constraint.addLess(p1, p2) + down1.forall(addOneBound(_, hi2, isUpper = true)) && + up2.forall(addOneBound(_, lo1, isUpper = false)) + } + constr.println(i"added $description = $res") + res + } + + /** Make p2 = p1, transfer all bounds of p2 to p1 + * @pre less(p1)(p2) + */ + private def unify(p1: PolyParam, p2: PolyParam): Boolean = { + constr.println(s"unifying $p1 $p2") + assert(constraint.isLess(p1, p2)) + val down = constraint.exclusiveLower(p2, p1) + val up = constraint.exclusiveUpper(p1, p2) + constraint = constraint.unify(p1, p2) + val bounds = constraint.nonParamBounds(p1) + val lo = bounds.lo + val hi = bounds.hi + isSubType(lo, hi) && + down.forall(addOneBound(_, hi, isUpper = true)) && + up.forall(addOneBound(_, lo, isUpper = false)) + } + + final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { + val saved = frozenConstraint + frozenConstraint = !alwaysFluid + try isSubType(tp1, tp2) + finally frozenConstraint = saved + } + + final def isSameTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { + val saved = frozenConstraint + frozenConstraint = !alwaysFluid + try isSameType(tp1, tp2) + finally frozenConstraint = saved + } + + /** Test whether the lower bounds of all parameters in this + * constraint are a solution to the constraint. + */ + protected final def isSatisfiable: Boolean = + constraint.forallParams { param => + val TypeBounds(lo, hi) = constraint.entry(param) + isSubType(lo, hi) || { + ctx.log(i"sub fail $lo <:< $hi") + false + } + } + + /** Solve constraint set for given type parameter `param`. + * If `fromBelow` is true the parameter is approximated by its lower bound, + * otherwise it is approximated by its upper bound. However, any occurrences + * of the parameter in a refinement somewhere in the bound are removed. Also + * wildcard types in bounds are approximated by their upper or lower bounds. + * (Such occurrences can arise for F-bounded types). + * The constraint is left unchanged. + * @return the instantiating type + * @pre `param` is in the constraint's domain. + */ + final def approximation(param: PolyParam, fromBelow: Boolean): Type = { + val avoidParam = new TypeMap { + override def stopAtStatic = true + def apply(tp: Type) = mapOver { + tp match { + case tp: RefinedType if param occursIn tp.refinedInfo => tp.parent + case tp: WildcardType => + val bounds = tp.optBounds.orElse(TypeBounds.empty).bounds + // Try to instantiate the wildcard to a type that is known to conform to it. + // This means: + // If fromBelow is true, we minimize the type overall + // Hence, if variance < 0, pick the maximal safe type: bounds.lo + // (i.e. the whole bounds range is over the type) + // if variance > 0, pick the minimal safe type: bounds.hi + // (i.e. the whole bounds range is under the type) + // if variance == 0, pick bounds.lo anyway (this is arbitrary but in line with + // the principle that we pick the smaller type when in doubt). + // If fromBelow is false, we maximize the type overall and reverse the bounds + // if variance != 0. For variance == 0, we still minimize. + // In summary we pick the bound given by this table: + // + // variance | -1 0 1 + // ------------------------ + // from below | lo lo hi + // from above | hi lo lo + // + if (variance == 0 || fromBelow == (variance < 0)) bounds.lo else bounds.hi + case _ => tp + } + } + } + assert(constraint.contains(param)) + val bound = if (fromBelow) constraint.fullLowerBound(param) else constraint.fullUpperBound(param) + val inst = avoidParam(bound) + typr.println(s"approx ${param.show}, from below = $fromBelow, bound = ${bound.show}, inst = ${inst.show}") + inst + } + + /** The instance type of `param` in the current constraint (which contains `param`). + * If `fromBelow` is true, the instance type is the lub of the parameter's + * lower bounds; otherwise it is the glb of its upper bounds. However, + * a lower bound instantiation can be a singleton type only if the upper bound + * is also a singleton type. + */ + def instanceType(param: PolyParam, fromBelow: Boolean): Type = { + def upperBound = constraint.fullUpperBound(param) + def isSingleton(tp: Type): Boolean = tp match { + case tp: SingletonType => true + case AndType(tp1, tp2) => isSingleton(tp1) | isSingleton(tp2) + case OrType(tp1, tp2) => isSingleton(tp1) & isSingleton(tp2) + case _ => false + } + def isFullyDefined(tp: Type): Boolean = tp match { + case tp: TypeVar => tp.isInstantiated && isFullyDefined(tp.instanceOpt) + case tp: TypeProxy => isFullyDefined(tp.underlying) + case tp: AndOrType => isFullyDefined(tp.tp1) && isFullyDefined(tp.tp2) + case _ => true + } + def isOrType(tp: Type): Boolean = tp.stripTypeVar.dealias match { + case tp: OrType => true + case tp: RefinedOrRecType => isOrType(tp.parent) + case AndType(tp1, tp2) => isOrType(tp1) | isOrType(tp2) + case WildcardType(bounds: TypeBounds) => isOrType(bounds.hi) + case _ => false + } + + // First, solve the constraint. + var inst = approximation(param, fromBelow) + + // Then, approximate by (1.) - (3.) and simplify as follows. + // 1. If instance is from below and is a singleton type, yet + // upper bound is not a singleton type, widen the instance. + if (fromBelow && isSingleton(inst) && !isSingleton(upperBound)) + inst = inst.widen + + inst = inst.simplified + + // 2. If instance is from below and is a fully-defined union type, yet upper bound + // is not a union type, approximate the union type from above by an intersection + // of all common base types. + if (fromBelow && isOrType(inst) && isFullyDefined(inst) && !isOrType(upperBound)) + inst = ctx.harmonizeUnion(inst) + + // 3. If instance is from below, and upper bound has open named parameters + // make sure the instance has all named parameters of the bound. + if (fromBelow) inst = inst.widenToNamedTypeParams(param.namedTypeParams) + inst + } + + /** Constraint `c1` subsumes constraint `c2`, if under `c2` as constraint we have + * for all poly params `p` defined in `c2` as `p >: L2 <: U2`: + * + * c1 defines p with bounds p >: L1 <: U1, and + * L2 <: L1, and + * U1 <: U2 + * + * Both `c1` and `c2` are required to derive from constraint `pre`, possibly + * narrowing it with further bounds. + */ + protected final def subsumes(c1: Constraint, c2: Constraint, pre: Constraint): Boolean = + if (c2 eq pre) true + else if (c1 eq pre) false + else { + val saved = constraint + try + c2.forallParams(p => + c1.contains(p) && + c2.upper(p).forall(c1.isLess(p, _)) && + isSubTypeWhenFrozen(c1.nonParamBounds(p), c2.nonParamBounds(p))) + finally constraint = saved + } + + /** The current bounds of type parameter `param` */ + final def bounds(param: PolyParam): TypeBounds = { + val e = constraint.entry(param) + if (e.exists) e.bounds else param.binder.paramBounds(param.paramNum) + } + + /** Add polytype `pt`, possibly with type variables `tvars`, to current constraint + * and propagate all bounds. + * @param tvars See Constraint#add + */ + def addToConstraint(pt: PolyType, tvars: List[TypeVar]): Unit = + assert { + checkPropagated(i"initialized $pt") { + constraint = constraint.add(pt, tvars) + pt.paramNames.indices.forall { i => + val param = PolyParam(pt, i) + val bounds = constraint.nonParamBounds(param) + val lower = constraint.lower(param) + val upper = constraint.upper(param) + if (lower.nonEmpty && !bounds.lo.isRef(defn.NothingClass) || + upper.nonEmpty && !bounds.hi.isRef(defn.AnyClass)) constr.println(i"INIT*** $pt") + lower.forall(addOneBound(_, bounds.hi, isUpper = true)) && + upper.forall(addOneBound(_, bounds.lo, isUpper = false)) + } + } + } + + /** Can `param` be constrained with new bounds? */ + final def canConstrain(param: PolyParam): Boolean = + !frozenConstraint && (constraint contains param) + + /** Add constraint `param <: bound` if `fromBelow` is false, `param >: bound` otherwise. + * `bound` is assumed to be in normalized form, as specified in `firstTry` and + * `secondTry` of `TypeComparer`. In particular, it should not be an alias type, + * lazy ref, typevar, wildcard type, error type. In addition, upper bounds may + * not be AndTypes and lower bounds may not be OrTypes. This is assured by the + * way isSubType is organized. + */ + protected def addConstraint(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = { + def description = i"constr $param ${if (fromBelow) ">:" else "<:"} $bound:\n$constraint" + //checkPropagated(s"adding $description")(true) // DEBUG in case following fails + checkPropagated(s"added $description") { + addConstraintInvocations += 1 + + /** When comparing lambdas we might get constraints such as + * `A <: X0` or `A = List[X0]` where `A` is a constrained parameter + * and `X0` is a lambda parameter. The constraint for `A` is not allowed + * to refer to such a lambda parameter because the lambda parameter is + * not visible where `A` is defined. Consequently, we need to + * approximate the bound so that the lambda parameter does not appear in it. + * If `tp` is an upper bound, we need to approximate with something smaller, + * otherwise something larger. + * Test case in pos/i94-nada.scala. This test crashes with an illegal instance + * error in Test2 when the rest of the SI-2712 fix is applied but `pruneLambdaParams` is + * missing. + */ + def pruneLambdaParams(tp: Type) = + if (comparedPolyTypes.nonEmpty) { + val approx = new ApproximatingTypeMap { + def apply(t: Type): Type = t match { + case t @ PolyParam(pt: PolyType, n) if comparedPolyTypes contains pt => + val effectiveVariance = if (fromBelow) -variance else variance + val bounds = pt.paramBounds(n) + if (effectiveVariance > 0) bounds.lo + else if (effectiveVariance < 0) bounds.hi + else NoType + case _ => + mapOver(t) + } + } + approx(tp) + } + else tp + + def addParamBound(bound: PolyParam) = + if (fromBelow) addLess(bound, param) else addLess(param, bound) + + /** Drop all constrained parameters that occur at the toplevel in `bound` and + * handle them by `addLess` calls. + * The preconditions make sure that such parameters occur only + * in one of two ways: + * + * 1. + * + * P <: Ts1 | ... | Tsm (m > 0) + * Tsi = T1 & ... Tn (n >= 0) + * Some of the Ti are constrained parameters + * + * 2. + * + * Ts1 & ... & Tsm <: P (m > 0) + * Tsi = T1 | ... | Tn (n >= 0) + * Some of the Ti are constrained parameters + * + * In each case we cannot leave the parameter in place, + * because that would risk making a parameter later a subtype or supertype + * of a bound where the parameter occurs again at toplevel, which leads to cycles + * in the subtyping test. So we intentionally narrow the constraint by + * recording an isLess relationship instead (even though this is not implied + * by the bound). + * + * Narrowing a constraint is better than widening it, because narrowing leads + * to incompleteness (which we face anyway, see for instance eitherIsSubType) + * but widening leads to unsoundness. + * + * A test case that demonstrates the problem is i864.scala. + * Turn Config.checkConstraintsSeparated on to get an accurate diagnostic + * of the cycle when it is created. + * + * @return The pruned type if all `addLess` calls succeed, `NoType` otherwise. + */ + def prune(bound: Type): Type = bound match { + case bound: AndOrType => + val p1 = prune(bound.tp1) + val p2 = prune(bound.tp2) + if (p1.exists && p2.exists) bound.derivedAndOrType(p1, p2) + else NoType + case bound: TypeVar if constraint contains bound.origin => + prune(bound.underlying) + case bound: PolyParam => + constraint.entry(bound) match { + case NoType => pruneLambdaParams(bound) + case _: TypeBounds => + if (!addParamBound(bound)) NoType + else if (fromBelow) defn.NothingType + else defn.AnyType + case inst => + prune(inst) + } + case _ => + pruneLambdaParams(bound) + } + + try bound match { + case bound: PolyParam if constraint contains bound => + addParamBound(bound) + case _ => + val pbound = prune(bound) + pbound.exists && ( + if (fromBelow) addLowerBound(param, pbound) else addUpperBound(param, pbound)) + } + finally addConstraintInvocations -= 1 + } + } + + /** Instantiate `param` to `tp` if the constraint stays satisfiable */ + protected def tryInstantiate(param: PolyParam, tp: Type): Boolean = { + val saved = constraint + constraint = + if (addConstraint(param, tp, fromBelow = true) && + addConstraint(param, tp, fromBelow = false)) constraint.replace(param, tp) + else saved + constraint ne saved + } + + /** Check that constraint is fully propagated. See comment in Config.checkConstraintsPropagated */ + def checkPropagated(msg: => String)(result: Boolean): Boolean = { + if (Config.checkConstraintsPropagated && result && addConstraintInvocations == 0) { + val saved = frozenConstraint + frozenConstraint = true + for (p <- constraint.domainParams) { + def check(cond: => Boolean, q: PolyParam, ordering: String, explanation: String): Unit = + assert(cond, i"propagation failure for $p $ordering $q: $explanation\n$msg") + for (u <- constraint.upper(p)) + check(bounds(p).hi <:< bounds(u).hi, u, "<:", "upper bound not propagated") + for (l <- constraint.lower(p)) { + check(bounds(l).lo <:< bounds(p).hi, l, ">:", "lower bound not propagated") + check(constraint.isLess(l, p), l, ">:", "reverse ordering (<:) missing") + } + } + frozenConstraint = saved + } + result + } +} diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintRunInfo.scala b/compiler/src/dotty/tools/dotc/core/ConstraintRunInfo.scala new file mode 100644 index 000000000..e0f659cc6 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/ConstraintRunInfo.scala @@ -0,0 +1,17 @@ +package dotty.tools.dotc +package core + +import Contexts._ +import config.Printers.typr + +trait ConstraintRunInfo { self: RunInfo => + private var maxSize = 0 + private var maxConstraint: Constraint = _ + def recordConstraintSize(c: Constraint, size: Int) = + if (size > maxSize) { + maxSize = size + maxConstraint = c + } + def printMaxConstraint()(implicit ctx: Context) = + if (maxSize > 0) typr.println(s"max constraint = ${maxConstraint.show}") +} diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala new file mode 100644 index 000000000..639c4d111 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -0,0 +1,709 @@ +package dotty.tools +package dotc +package core + +import interfaces.CompilerCallback +import Decorators._ +import Periods._ +import Names._ +import Phases._ +import Types._ +import Symbols._ +import Scopes._ +import NameOps._ +import Uniques._ +import SymDenotations._ +import Comments._ +import Flags.ParamAccessor +import util.Positions._ +import ast.Trees._ +import ast.untpd +import util.{FreshNameCreator, SimpleMap, SourceFile, NoSource} +import typer.{Implicits, ImplicitRunInfo, ImportInfo, Inliner, NamerContextOps, SearchHistory, TypeAssigner, Typer} +import Implicits.ContextualImplicits +import config.Settings._ +import config.Config +import reporting._ +import collection.mutable +import collection.immutable.BitSet +import printing._ +import config.{Settings, ScalaSettings, Platform, JavaPlatform} +import language.implicitConversions +import DenotTransformers.DenotTransformer +import util.Property.Key +import xsbti.AnalysisCallback + +object Contexts { + + /** A context is passed basically everywhere in dotc. + * This is convenient but carries the risk of captured contexts in + * objects that turn into space leaks. To combat this risk, here are some + * conventions to follow: + * + * - Never let an implicit context be an argument of a class whose instances + * live longer than the context. + * - Classes that need contexts for their initialization take an explicit parameter + * named `initctx`. They pass initctx to all positions where it is needed + * (and these positions should all be part of the intialization sequence of the class). + * - Classes that need contexts that survive initialization are instead passed + * a "condensed context", typically named `cctx` (or they create one). Condensed contexts + * just add some basic information to the context base without the + * risk of capturing complete trees. + * - To make sure these rules are kept, it would be good to do a sanity + * check using bytecode inspection with javap or scalap: Keep track + * of all class fields of type context; allow them only in whitelisted + * classes (which should be short-lived). + */ + abstract class Context extends Periods + with Substituters + with TypeOps + with Phases + with Printers + with Symbols + with SymDenotations + with Reporting + with NamerContextOps + with Cloneable { thiscontext => + implicit def ctx: Context = this + + /** The context base at the root */ + val base: ContextBase + + /** All outer contexts, ending in `base.initialCtx` and then `NoContext` */ + def outersIterator = new Iterator[Context] { + var current = thiscontext + def hasNext = current != NoContext + def next = { val c = current; current = current.outer; c } + } + + /** The outer context */ + private[this] var _outer: Context = _ + protected def outer_=(outer: Context) = _outer = outer + def outer: Context = _outer + + /** The compiler callback implementation, or null if no callback will be called. */ + private[this] var _compilerCallback: CompilerCallback = _ + protected def compilerCallback_=(callback: CompilerCallback) = + _compilerCallback = callback + def compilerCallback: CompilerCallback = _compilerCallback + + /** The sbt callback implementation if we are run from sbt, null otherwise */ + private[this] var _sbtCallback: AnalysisCallback = _ + protected def sbtCallback_=(callback: AnalysisCallback) = + _sbtCallback = callback + def sbtCallback: AnalysisCallback = _sbtCallback + + /** The current context */ + private[this] var _period: Period = _ + protected def period_=(period: Period) = { + assert(period.firstPhaseId == period.lastPhaseId, period) + _period = period + } + def period: Period = _period + + /** The scope nesting level */ + private[this] var _mode: Mode = _ + protected def mode_=(mode: Mode) = _mode = mode + def mode: Mode = _mode + + /** The current type comparer */ + private[this] var _typerState: TyperState = _ + protected def typerState_=(typerState: TyperState) = _typerState = typerState + def typerState: TyperState = _typerState + + /** The current plain printer */ + private[this] var _printerFn: Context => Printer = _ + protected def printerFn_=(printerFn: Context => Printer) = _printerFn = printerFn + def printerFn: Context => Printer = _printerFn + + /** The current owner symbol */ + private[this] var _owner: Symbol = _ + protected def owner_=(owner: Symbol) = _owner = owner + def owner: Symbol = _owner + + /** The current settings values */ + private[this] var _sstate: SettingsState = _ + protected def sstate_=(sstate: SettingsState) = _sstate = sstate + def sstate: SettingsState = _sstate + + /** The current tree */ + private[this] var _compilationUnit: CompilationUnit = _ + protected def compilationUnit_=(compilationUnit: CompilationUnit) = _compilationUnit = compilationUnit + def compilationUnit: CompilationUnit = _compilationUnit + + /** The current tree */ + private[this] var _tree: Tree[_ >: Untyped]= _ + protected def tree_=(tree: Tree[_ >: Untyped]) = _tree = tree + def tree: Tree[_ >: Untyped] = _tree + + /** The current scope */ + private[this] var _scope: Scope = _ + protected def scope_=(scope: Scope) = _scope = scope + def scope: Scope = _scope + + /** The current type assigner or typer */ + private[this] var _typeAssigner: TypeAssigner = _ + protected def typeAssigner_=(typeAssigner: TypeAssigner) = _typeAssigner = typeAssigner + def typeAssigner: TypeAssigner = _typeAssigner + def typer: Typer = _typeAssigner.asInstanceOf[Typer] + + /** The currently active import info */ + private[this] var _importInfo: ImportInfo = _ + protected def importInfo_=(importInfo: ImportInfo) = _importInfo = importInfo + def importInfo: ImportInfo = _importInfo + + /** The current compiler-run specific Info */ + private[this] var _runInfo: RunInfo = _ + protected def runInfo_=(runInfo: RunInfo) = _runInfo = runInfo + def runInfo: RunInfo = _runInfo + + /** An optional diagostics buffer than is used by some checking code + * to provide more information in the buffer if it exists. + */ + private var _diagnostics: Option[StringBuilder] = _ + protected def diagnostics_=(diagnostics: Option[StringBuilder]) = _diagnostics = diagnostics + def diagnostics: Option[StringBuilder] = _diagnostics + + /** The current bounds in force for type parameters appearing in a GADT */ + private var _gadt: GADTMap = _ + protected def gadt_=(gadt: GADTMap) = _gadt = gadt + def gadt: GADTMap = _gadt + + /**The current fresh name creator */ + private[this] var _freshNames: FreshNameCreator = _ + protected def freshNames_=(freshNames: FreshNameCreator) = _freshNames = freshNames + def freshNames: FreshNameCreator = _freshNames + + def freshName(prefix: String = ""): String = freshNames.newName(prefix) + def freshName(prefix: Name): String = freshName(prefix.toString) + + /** A map in which more contextual properties can be stored */ + private var _moreProperties: Map[Key[Any], Any] = _ + protected def moreProperties_=(moreProperties: Map[Key[Any], Any]) = _moreProperties = moreProperties + def moreProperties: Map[Key[Any], Any] = _moreProperties + + def property[T](key: Key[T]): Option[T] = + moreProperties.get(key).asInstanceOf[Option[T]] + + private var _typeComparer: TypeComparer = _ + protected def typeComparer_=(typeComparer: TypeComparer) = _typeComparer = typeComparer + def typeComparer: TypeComparer = { + if (_typeComparer.ctx ne this) + _typeComparer = _typeComparer.copyIn(this) + _typeComparer + } + + /** Number of findMember calls on stack */ + private[core] var findMemberCount: Int = 0 + + /** List of names which have a findMemberCall on stack, + * after Config.LogPendingFindMemberThreshold is reached. + */ + private[core] var pendingMemberSearches: List[Name] = Nil + + /** The new implicit references that are introduced by this scope */ + private var implicitsCache: ContextualImplicits = null + def implicits: ContextualImplicits = { + if (implicitsCache == null ) + implicitsCache = { + val implicitRefs: List[TermRef] = + if (isClassDefContext) + try owner.thisType.implicitMembers + catch { + case ex: CyclicReference => Nil + } + else if (isImportContext) importInfo.importedImplicits + else if (isNonEmptyScopeContext) scope.implicitDecls + else Nil + val outerImplicits = + if (isImportContext && importInfo.hiddenRoot.exists) + outer.implicits exclude importInfo.hiddenRoot + else + outer.implicits + if (implicitRefs.isEmpty) outerImplicits + else new ContextualImplicits(implicitRefs, outerImplicits)(this) + } + implicitsCache + } + + /** The history of implicit searches that are currently active */ + private var _searchHistory: SearchHistory = null + protected def searchHistory_= (searchHistory: SearchHistory) = _searchHistory = searchHistory + def searchHistory: SearchHistory = _searchHistory + + /** Those fields are used to cache phases created in withPhase. + * phasedCtx is first phase with altered phase ever requested. + * phasedCtxs is array that uses phaseId's as indexes, + * contexts are created only on request and cached in this array + */ + private var phasedCtx: Context = _ + private var phasedCtxs: Array[Context] = _ + + /** This context at given phase. + * This method will always return a phase period equal to phaseId, thus will never return squashed phases + */ + final def withPhase(phaseId: PhaseId): Context = + if (this.phaseId == phaseId) this + else if (phasedCtx.phaseId == phaseId) phasedCtx + else if (phasedCtxs != null && phasedCtxs(phaseId) != null) phasedCtxs(phaseId) + else { + val ctx1 = fresh.setPhase(phaseId) + if (phasedCtx eq this) phasedCtx = ctx1 + else { + if (phasedCtxs == null) phasedCtxs = new Array[Context](base.phases.length) + phasedCtxs(phaseId) = ctx1 + } + ctx1 + } + + final def withPhase(phase: Phase): Context = + withPhase(phase.id) + + final def withPhaseNoLater(phase: Phase) = + if (phase.exists && ctx.phase.id > phase.id) withPhase(phase) else ctx + + /** If -Ydebug is on, the top of the stack trace where this context + * was created, otherwise `null`. + */ + private var creationTrace: Array[StackTraceElement] = _ + + private def setCreationTrace() = + if (this.settings.YtraceContextCreation.value) + creationTrace = (new Throwable).getStackTrace().take(20) + + /** Print all enclosing context's creation stacktraces */ + def printCreationTraces() = { + println("=== context creation trace =======") + for (ctx <- outersIterator) { + println(s">>>>>>>>> $ctx") + if (ctx.creationTrace != null) println(ctx.creationTrace.mkString("\n")) + } + println("=== end context creation trace ===") + } + + /** The current reporter */ + def reporter: Reporter = typerState.reporter + + /** Is this a context for the members of a class definition? */ + def isClassDefContext: Boolean = + owner.isClass && (owner ne outer.owner) + + /** Is this a context that introduces an import clause? */ + def isImportContext: Boolean = + (this ne NoContext) && (this.importInfo ne outer.importInfo) + + /** Is this a context that introduces a non-empty scope? */ + def isNonEmptyScopeContext: Boolean = + (this.scope ne outer.scope) && this.scope.nonEmpty + + /** Leave message in diagnostics buffer if it exists */ + def diagnose(str: => String) = + for (sb <- diagnostics) { + sb.setLength(0) + sb.append(str) + } + + /** The next outer context whose tree is a template or package definition */ + def enclTemplate: Context = { + var c = this + while (c != NoContext && !c.tree.isInstanceOf[Template[_]] && !c.tree.isInstanceOf[PackageDef[_]]) + c = c.outer + c + } + + /** The context for a supercall. This context is used for elaborating + * the parents of a class and their arguments. + * The context is computed from the current class context. It has + * + * - as owner: The primary constructor of the class + * - as outer context: The context enclosing the class context + * - as scope: The parameter accessors in the class context + * - with additional mode: InSuperCall + * + * The reasons for this peculiar choice of attributes are as follows: + * + * - The constructor must be the owner, because that's where any local methods or closures + * should go. + * - The context may not see any class members (inherited or defined), and should + * instead see definitions defined in the outer context which might be shadowed by + * such class members. That's why the outer context must be the outer context of the class. + * - At the same time the context should see the parameter accessors of the current class, + * that's why they get added to the local scope. An alternative would have been to have the + * context see the constructor parameters instead, but then we'd need a final substitution step + * from constructor parameters to class parameter accessors. + */ + def superCallContext: Context = { + val locals = newScopeWith(owner.asClass.paramAccessors: _*) + superOrThisCallContext(owner.primaryConstructor, locals) + } + + /** The context for the arguments of a this(...) constructor call. + * The context is computed from the local auxiliary constructor context. + * It has + * + * - as owner: The auxiliary constructor + * - as outer context: The context enclosing the enclosing class context + * - as scope: The parameters of the auxiliary constructor. + */ + def thisCallArgContext: Context = { + assert(owner.isClassConstructor) + val constrCtx = outersIterator.dropWhile(_.outer.owner == owner).next + superOrThisCallContext(owner, constrCtx.scope) + .setTyperState(typerState) + .setGadt(gadt) + } + + /** The super- or this-call context with given owner and locals. */ + private def superOrThisCallContext(owner: Symbol, locals: Scope): FreshContext = { + var classCtx = outersIterator.dropWhile(!_.isClassDefContext).next + classCtx.outer.fresh.setOwner(owner) + .setScope(locals) + .setMode(classCtx.mode | Mode.InSuperCall) + } + + /** The context of expression `expr` seen as a member of a statement sequence */ + def exprContext(stat: Tree[_ >: Untyped], exprOwner: Symbol) = + if (exprOwner == this.owner) this + else if (untpd.isSuperConstrCall(stat) && this.owner.isClass) superCallContext + else ctx.fresh.setOwner(exprOwner) + + /** The current source file; will be derived from current + * compilation unit. + */ + def source: SourceFile = + if (compilationUnit == null) NoSource else compilationUnit.source + + /** Does current phase use an erased types interpretation? */ + def erasedTypes: Boolean = phase.erasedTypes + + /** Is the debug option set? */ + def debug: Boolean = base.settings.debug.value + + /** Is the verbose option set? */ + def verbose: Boolean = base.settings.verbose.value + + /** Should use colors when printing? */ + def useColors: Boolean = + base.settings.color.value == "always" + + /** A condensed context containing essential information of this but + * no outer contexts except the initial context. + private var _condensed: CondensedContext = null + def condensed: CondensedContext = { + if (_condensed eq outer.condensed) + _condensed = base.initialCtx.fresh + .withPeriod(period) + .withNewMode(mode) + // typerState and its constraint is not preserved in condensed + // reporter is always ThrowingReporter + .withPrinterFn(printerFn) + .withOwner(owner) + .withSettings(sstate) + // tree is not preserved in condensed + .withRunInfo(runInfo) + .withDiagnostics(diagnostics) + .withMoreProperties(moreProperties) + _condensed + } + */ + + protected def init(outer: Context): this.type = { + this.outer = outer + this.implicitsCache = null + this.phasedCtx = this + this.phasedCtxs = null + setCreationTrace() + this + } + + /** A fresh clone of this context. */ + def fresh: FreshContext = clone.asInstanceOf[FreshContext].init(this) + + final def withOwner(owner: Symbol): Context = + if (owner ne this.owner) fresh.setOwner(owner) else this + + override def toString = + "Context(\n" + + (outersIterator map ( ctx => s" owner = ${ctx.owner}, scope = ${ctx.scope}") mkString "\n") + } + + /** A condensed context provides only a small memory footprint over + * a Context base, and therefore can be stored without problems in + * long-lived objects. + abstract class CondensedContext extends Context { + override def condensed = this + } + */ + + /** A fresh context allows selective modification + * of its attributes using the with... methods. + */ + abstract class FreshContext extends Context { + def setPeriod(period: Period): this.type = { this.period = period; this } + def setMode(mode: Mode): this.type = { this.mode = mode; this } + def setCompilerCallback(callback: CompilerCallback): this.type = { this.compilerCallback = callback; this } + def setSbtCallback(callback: AnalysisCallback): this.type = { this.sbtCallback = callback; this } + def setTyperState(typerState: TyperState): this.type = { this.typerState = typerState; this } + def setReporter(reporter: Reporter): this.type = setTyperState(typerState.withReporter(reporter)) + def setNewTyperState: this.type = setTyperState(typerState.fresh(isCommittable = true)) + def setExploreTyperState: this.type = setTyperState(typerState.fresh(isCommittable = false)) + def setPrinterFn(printer: Context => Printer): this.type = { this.printerFn = printer; this } + def setOwner(owner: Symbol): this.type = { assert(owner != NoSymbol); this.owner = owner; this } + def setSettings(sstate: SettingsState): this.type = { this.sstate = sstate; this } + def setCompilationUnit(compilationUnit: CompilationUnit): this.type = { this.compilationUnit = compilationUnit; this } + def setTree(tree: Tree[_ >: Untyped]): this.type = { this.tree = tree; this } + def setScope(scope: Scope): this.type = { this.scope = scope; this } + def setNewScope: this.type = { this.scope = newScope; this } + def setTypeAssigner(typeAssigner: TypeAssigner): this.type = { this.typeAssigner = typeAssigner; this } + def setTyper(typer: Typer): this.type = { this.scope = typer.scope; setTypeAssigner(typer) } + def setImportInfo(importInfo: ImportInfo): this.type = { this.importInfo = importInfo; this } + def setRunInfo(runInfo: RunInfo): this.type = { this.runInfo = runInfo; this } + def setDiagnostics(diagnostics: Option[StringBuilder]): this.type = { this.diagnostics = diagnostics; this } + def setGadt(gadt: GADTMap): this.type = { this.gadt = gadt; this } + def setTypeComparerFn(tcfn: Context => TypeComparer): this.type = { this.typeComparer = tcfn(this); this } + def setSearchHistory(searchHistory: SearchHistory): this.type = { this.searchHistory = searchHistory; this } + def setFreshNames(freshNames: FreshNameCreator): this.type = { this.freshNames = freshNames; this } + def setMoreProperties(moreProperties: Map[Key[Any], Any]): this.type = { this.moreProperties = moreProperties; this } + + def setProperty[T](key: Key[T], value: T): this.type = + setMoreProperties(moreProperties.updated(key, value)) + + def setPhase(pid: PhaseId): this.type = setPeriod(Period(runId, pid)) + def setPhase(phase: Phase): this.type = setPeriod(Period(runId, phase.start, phase.end)) + + def setSetting[T](setting: Setting[T], value: T): this.type = + setSettings(setting.updateIn(sstate, value)) + + def setFreshGADTBounds: this.type = { this.gadt = new GADTMap(gadt.bounds); this } + + def setDebug = setSetting(base.settings.debug, true) + } + + implicit class ModeChanges(val c: Context) extends AnyVal { + final def withModeBits(mode: Mode): Context = + if (mode != c.mode) c.fresh.setMode(mode) else c + + final def addMode(mode: Mode): Context = withModeBits(c.mode | mode) + final def maskMode(mode: Mode): Context = withModeBits(c.mode & mode) + final def retractMode(mode: Mode): Context = withModeBits(c.mode &~ mode) + } + + implicit class FreshModeChanges(val c: FreshContext) extends AnyVal { + final def addMode(mode: Mode): c.type = c.setMode(c.mode | mode) + final def maskMode(mode: Mode): c.type = c.setMode(c.mode & mode) + final def retractMode(mode: Mode): c.type = c.setMode(c.mode &~ mode) + } + + /** A class defining the initial context with given context base + * and set of possible settings. + */ + private class InitialContext(val base: ContextBase, settings: SettingGroup) extends FreshContext { + outer = NoContext + period = InitialPeriod + mode = Mode.None + typerState = new TyperState(new ConsoleReporter()) + printerFn = new RefinedPrinter(_) + owner = NoSymbol + sstate = settings.defaultState + tree = untpd.EmptyTree + typeAssigner = TypeAssigner + runInfo = new RunInfo(this) + diagnostics = None + freshNames = new FreshNameCreator.Default + moreProperties = Map.empty + typeComparer = new TypeComparer(this) + searchHistory = new SearchHistory(0, Map()) + gadt = new GADTMap(SimpleMap.Empty) + } + + @sharable object NoContext extends Context { + val base = null + override val implicits: ContextualImplicits = new ContextualImplicits(Nil, null)(this) + } + + /** A context base defines state and associated methods that exist once per + * compiler run. + */ + class ContextBase extends ContextState + with Denotations.DenotationsBase + with Phases.PhasesBase { + + /** The applicable settings */ + val settings = new ScalaSettings + + /** The initial context */ + val initialCtx: Context = new InitialContext(this, settings) + + /** The symbol loaders */ + val loaders = new SymbolLoaders + + /** The platform, initialized by `initPlatform()`. */ + private var _platform: Platform = _ + + /** The platform */ + def platform: Platform = { + if (_platform == null) { + throw new IllegalStateException( + "initialize() must be called before accessing platform") + } + _platform + } + + protected def newPlatform(implicit ctx: Context): Platform = + new JavaPlatform + + /** The loader that loads the members of _root_ */ + def rootLoader(root: TermSymbol)(implicit ctx: Context): SymbolLoader = platform.rootLoader(root) + + // Set up some phases to get started */ + usePhases(List(SomePhase)) + + /** The standard definitions */ + val definitions = new Definitions + + /** Initializes the `ContextBase` with a starting context. + * This initializes the `platform` and the `definitions`. + */ + def initialize()(implicit ctx: Context): Unit = { + _platform = newPlatform + definitions.init() + } + + def squashed(p: Phase): Phase = { + allPhases.find(_.period.containsPhaseId(p.id)).getOrElse(NoPhase) + } + } + + /** The essential mutable state of a context base, collected into a common class */ + class ContextState { + // Symbols state + + /** A counter for unique ids */ + private[core] var _nextId = 0 + + def nextId = { _nextId += 1; _nextId } + + /** A map from a superclass id to the typeref of the class that has it */ + private[core] var classOfId = new Array[ClassSymbol](Config.InitialSuperIdsSize) + + /** A map from a the typeref of a class to its superclass id */ + private[core] val superIdOfClass = new mutable.AnyRefMap[ClassSymbol, Int] + + /** The last allocated superclass id */ + private[core] var lastSuperId = -1 + + /** Allocate and return next free superclass id */ + private[core] def nextSuperId: Int = { + lastSuperId += 1 + if (lastSuperId >= classOfId.length) { + val tmp = new Array[ClassSymbol](classOfId.length * 2) + classOfId.copyToArray(tmp) + classOfId = tmp + } + lastSuperId + } + + // Types state + /** A table for hash consing unique types */ + private[core] val uniques = new util.HashSet[Type](Config.initialUniquesCapacity) { + override def hash(x: Type): Int = x.hash + } + + /** A table for hash consing unique refined types */ + private[dotc] val uniqueRefinedTypes = new RefinedUniques + + /** A table for hash consing unique named types */ + private[core] val uniqueNamedTypes = new NamedTypeUniques + + /** A table for hash consing unique type bounds */ + private[core] val uniqueTypeAliases = new TypeAliasUniques + + private def uniqueSets = Map( + "uniques" -> uniques, + "uniqueRefinedTypes" -> uniqueRefinedTypes, + "uniqueNamedTypes" -> uniqueNamedTypes, + "uniqueTypeAliases" -> uniqueTypeAliases) + + /** A map that associates label and size of all uniques sets */ + def uniquesSizes: Map[String, Int] = uniqueSets.mapValues(_.size) + + /** The number of recursive invocation of underlying on a NamedType + * during a controlled operation. + */ + private[core] var underlyingRecursions: Int = 0 + + /** The set of named types on which a currently active invocation + * of underlying during a controlled operation exists. */ + private[core] val pendingUnderlying = new mutable.HashSet[Type] + + /** A flag that some unsafe nonvariant instantiation was encountered + * in this run. Used as a shortcut to a avoid scans of types in + * Typer.typedSelect. + */ + private[dotty] var unsafeNonvariant: RunId = NoRunId + + // Phases state + + private[core] var phasesPlan: List[List[Phase]] = _ + + /** Phases by id */ + private[core] var phases: Array[Phase] = _ + + /** Phases with consecutive Transforms grouped into a single phase, Empty array if squashing is disabled */ + private[core] var squashedPhases: Array[Phase] = Array.empty[Phase] + + /** Next denotation transformer id */ + private[core] var nextDenotTransformerId: Array[Int] = _ + + private[core] var denotTransformers: Array[DenotTransformer] = _ + + // Printers state + /** Number of recursive invocations of a show method on current stack */ + private[dotc] var toTextRecursions = 0 + + // Reporters state + private[dotc] var indent = 0 + + protected[dotc] val indentTab = " " + + def reset() = { + for ((_, set) <- uniqueSets) set.clear() + for (i <- 0 until classOfId.length) classOfId(i) = null + superIdOfClass.clear() + lastSuperId = -1 + } + + // Test that access is single threaded + + /** The thread on which `checkSingleThreaded was invoked last */ + @sharable private var thread: Thread = null + + /** Check that we are on the same thread as before */ + def checkSingleThreaded() = + if (thread == null) thread = Thread.currentThread() + else assert(thread == Thread.currentThread(), "illegal multithreaded access to ContextBase") + } + + object Context { + + /** Implicit conversion that injects all printer operations into a context */ + implicit def toPrinter(ctx: Context): Printer = ctx.printer + + /** implicit conversion that injects all ContextBase members into a context */ + implicit def toBase(ctx: Context): ContextBase = ctx.base + + // @sharable val theBase = new ContextBase // !!! DEBUG, so that we can use a minimal context for reporting even in code that normally cannot access a context + } + + /** Info that changes on each compiler run */ + class RunInfo(initctx: Context) extends ImplicitRunInfo with ConstraintRunInfo { + implicit val ctx: Context = initctx + } + + class GADTMap(initBounds: SimpleMap[Symbol, TypeBounds]) { + private var myBounds = initBounds + def setBounds(sym: Symbol, b: TypeBounds): Unit = + myBounds = myBounds.updated(sym, b) + def bounds = myBounds + } +} diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala new file mode 100644 index 000000000..a105741f5 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -0,0 +1,185 @@ +package dotty.tools.dotc +package core + +import annotation.tailrec +import Symbols._ +import Contexts._, Names._, Phases._, printing.Texts._, printing.Printer, printing.Showable +import util.Positions.Position, util.SourcePosition +import collection.mutable.ListBuffer +import dotty.tools.dotc.transform.TreeTransforms._ +import ast.tpd._ +import scala.language.implicitConversions +import printing.Formatting._ + +/** This object provides useful implicit decorators for types defined elsewhere */ +object Decorators { + + /** Turns Strings into PreNames, adding toType/TermName methods */ + implicit class StringDecorator(val s: String) extends AnyVal with PreName { + def toTypeName: TypeName = typeName(s) + def toTermName: TermName = termName(s) + def toText(printer: Printer): Text = Str(s) + } + + /** Implements a findSymbol method on iterators of Symbols that + * works like find but avoids Option, replacing None with NoSymbol. + */ + implicit class SymbolIteratorDecorator(val it: Iterator[Symbol]) extends AnyVal { + final def findSymbol(p: Symbol => Boolean): Symbol = { + while (it.hasNext) { + val sym = it.next + if (p(sym)) return sym + } + NoSymbol + } + } + + final val MaxFilterRecursions = 1000 + + /** Implements filterConserve, zipWithConserve methods + * on lists that avoid duplication of list nodes where feasible. + */ + implicit class ListDecorator[T](val xs: List[T]) extends AnyVal { + + final def mapconserve[U](f: T => U): List[U] = { + @tailrec + def loop(mapped: ListBuffer[U], unchanged: List[U], pending: List[T]): List[U] = + if (pending.isEmpty) { + if (mapped eq null) unchanged + else mapped.prependToList(unchanged) + } else { + val head0 = pending.head + val head1 = f(head0) + + if (head1.asInstanceOf[AnyRef] eq head0.asInstanceOf[AnyRef]) + loop(mapped, unchanged, pending.tail) + else { + val b = if (mapped eq null) new ListBuffer[U] else mapped + var xc = unchanged + while (xc ne pending) { + b += xc.head + xc = xc.tail + } + b += head1 + val tail0 = pending.tail + loop(b, tail0.asInstanceOf[List[U]], tail0) + } + } + loop(null, xs.asInstanceOf[List[U]], xs) + } + + /** Like `xs filter p` but returns list `xs` itself - instead of a copy - + * if `p` is true for all elements and `xs` is not longer + * than `MaxFilterRecursions`. + */ + def filterConserve(p: T => Boolean): List[T] = { + def loop(xs: List[T], nrec: Int): List[T] = xs match { + case Nil => xs + case x :: xs1 => + if (nrec < MaxFilterRecursions) { + val ys1 = loop(xs1, nrec + 1) + if (p(x)) + if (ys1 eq xs1) xs else x :: ys1 + else + ys1 + } else xs filter p + } + loop(xs, 0) + } + + /** Like `(xs, ys).zipped.map(f)`, but returns list `xs` itself + * - instead of a copy - if function `f` maps all elements of + * `xs` to themselves. Also, it is required that `ys` is at least + * as long as `xs`. + */ + def zipWithConserve[U](ys: List[U])(f: (T, U) => T): List[T] = + if (xs.isEmpty) xs + else { + val x1 = f(xs.head, ys.head) + val xs1 = xs.tail.zipWithConserve(ys.tail)(f) + if ((x1.asInstanceOf[AnyRef] eq xs.head.asInstanceOf[AnyRef]) && + (xs1 eq xs.tail)) xs + else x1 :: xs1 + } + + def foldRightBN[U](z: => U)(op: (T, => U) => U): U = xs match { + case Nil => z + case x :: xs1 => op(x, xs1.foldRightBN(z)(op)) + } + + final def hasSameLengthAs[U](ys: List[U]): Boolean = { + @tailrec def loop(xs: List[T], ys: List[U]): Boolean = + if (xs.isEmpty) ys.isEmpty + else ys.nonEmpty && loop(xs.tail, ys.tail) + loop(xs, ys) + } + + /** Union on lists seen as sets */ + def | (ys: List[T]): List[T] = xs ++ (ys filterNot (xs contains _)) + + /** Intersection on lists seen as sets */ + def & (ys: List[T]): List[T] = xs filter (ys contains _) + } + + implicit class ListOfListDecorator[T](val xss: List[List[T]]) extends AnyVal { + def nestedMap[U](f: T => U): List[List[U]] = xss map (_ map f) + def nestedMapconserve[U](f: T => U): List[List[U]] = xss mapconserve (_ mapconserve f) + } + + implicit class TextToString(val text: Text) extends AnyVal { + def show(implicit ctx: Context) = text.mkString(ctx.settings.pageWidth.value) + } + + /** Test whether a list of strings representing phases contains + * a given phase. See [[config.CompilerCommand#explainAdvanced]] for the + * exact meaning of "contains" here. + */ + implicit class PhaseListDecorator(val names: List[String]) extends AnyVal { + def containsPhase(phase: Phase): Boolean = phase match { + case phase: TreeTransformer => phase.miniPhases.exists(containsPhase) + case _ => + names exists { name => + name == "all" || { + val strippedName = name.stripSuffix("+") + val logNextPhase = name ne strippedName + phase.phaseName.startsWith(strippedName) || + (logNextPhase && phase.prev.phaseName.startsWith(strippedName)) + } + } + } + } + + implicit def sourcePos(pos: Position)(implicit ctx: Context): SourcePosition = { + def recur(inlinedCalls: List[Tree], pos: Position): SourcePosition = inlinedCalls match { + case inlinedCall :: rest => + sourceFile(inlinedCall).atPos(pos).withOuter(recur(rest, inlinedCall.pos)) + case empty => + ctx.source.atPos(pos) + } + recur(enclosingInlineds, pos) + } + + implicit class StringInterpolators(val sc: StringContext) extends AnyVal { + + /** General purpose string formatting */ + def i(args: Any*)(implicit ctx: Context): String = + new StringFormatter(sc).assemble(args) + + /** Formatting for error messages: Like `i` but suppress follow-on + * error messages after the first one if some of their arguments are "non-sensical". + */ + def em(args: Any*)(implicit ctx: Context): String = + new ErrorMessageFormatter(sc).assemble(args) + + /** Formatting with added explanations: Like `em`, but add explanations to + * give more info about type variables and to disambiguate where needed. + */ + def ex(args: Any*)(implicit ctx: Context): String = + explained2(implicit ctx => em(args: _*)) + + /** Formatter that adds syntax highlighting to all interpolated values */ + def hl(args: Any*)(implicit ctx: Context): String = + new SyntaxFormatter(sc).assemble(args).stripMargin + } +} + diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala new file mode 100644 index 000000000..4b090d9b1 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -0,0 +1,807 @@ +package dotty.tools +package dotc +package core + +import Types._, Contexts._, Symbols._, Denotations._, SymDenotations._, StdNames._, Names._ +import Flags._, Scopes._, Decorators._, NameOps._, util.Positions._, Periods._ +import unpickleScala2.Scala2Unpickler.ensureConstructor +import scala.annotation.{ switch, meta } +import scala.collection.{ mutable, immutable } +import PartialFunction._ +import collection.mutable +import scala.reflect.api.{ Universe => ApiUniverse } + +object Definitions { + val MaxTupleArity, MaxAbstractFunctionArity = 22 + val MaxFunctionArity = 30 + // Awaiting a definite solution that drops the limit altogether, 30 gives a safety + // margin over the previous 22, so that treecopiers in miniphases are allowed to + // temporarily create larger closures. This is needed in lambda lift where large closures + // are first formed by treecopiers before they are split apart into parameters and + // environment in the lambdalift transform itself. +} + +/** A class defining symbols and types of standard definitions + * + * Note: There's a much nicer design possible once we have implicit functions. + * The idea is explored to some degree in branch wip-definitions (#929): Instead of a type + * and a separate symbol definition, we produce in one line an implicit function from + * Context to Symbol, and possibly also the corresponding type. This cuts down on all + * the duplication encountered here. + * + * wip-definitions tries to do the same with an implicit conversion from a SymbolPerRun + * type to a symbol type. The problem with that is universal equality. Comparisons will + * not trigger the conversion and will therefore likely return false results. + * + * So the branch is put on hold, until we have implicit functions, which will always + * automatically be dereferenced. + */ +class Definitions { + import Definitions._ + + private implicit var ctx: Context = _ + + private def newSymbol[N <: Name](owner: Symbol, name: N, flags: FlagSet, info: Type) = + ctx.newSymbol(owner, name, flags | Permanent, info) + + private def newClassSymbol(owner: Symbol, name: TypeName, flags: FlagSet, infoFn: ClassSymbol => Type) = + ctx.newClassSymbol(owner, name, flags | Permanent, infoFn).entered + + private def newCompleteClassSymbol(owner: Symbol, name: TypeName, flags: FlagSet, parents: List[TypeRef], decls: Scope = newScope) = + ctx.newCompleteClassSymbol(owner, name, flags | Permanent, parents, decls).entered + + private def newTopClassSymbol(name: TypeName, flags: FlagSet, parents: List[TypeRef]) = + completeClass(newCompleteClassSymbol(ScalaPackageClass, name, flags, parents)) + + private def newTypeField(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope) = + scope.enter(newSymbol(cls, name, flags, TypeBounds.empty)) + + private def newTypeParam(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope) = + newTypeField(cls, name, flags | ClassTypeParamCreationFlags, scope) + + private def newSyntheticTypeParam(cls: ClassSymbol, scope: MutableScope, paramFlags: FlagSet, suffix: String = "T0") = + newTypeParam(cls, suffix.toTypeName.expandedName(cls), ExpandedName | paramFlags, scope) + + // NOTE: Ideally we would write `parentConstrs: => Type*` but SIP-24 is only + // implemented in Dotty and not in Scala 2. + // See . + private def specialPolyClass(name: TypeName, paramFlags: FlagSet, parentConstrs: => Seq[Type]): ClassSymbol = { + val completer = new LazyType { + def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { + val cls = denot.asClass.classSymbol + val paramDecls = newScope + val typeParam = newSyntheticTypeParam(cls, paramDecls, paramFlags) + def instantiate(tpe: Type) = + if (tpe.typeParams.nonEmpty) tpe.appliedTo(typeParam.typeRef) + else tpe + val parents = parentConstrs.toList map instantiate + val parentRefs: List[TypeRef] = ctx.normalizeToClassRefs(parents, cls, paramDecls) + denot.info = ClassInfo(ScalaPackageClass.thisType, cls, parentRefs, paramDecls) + } + } + newClassSymbol(ScalaPackageClass, name, EmptyFlags, completer) + } + + private def newMethod(cls: ClassSymbol, name: TermName, info: Type, flags: FlagSet = EmptyFlags): TermSymbol = + newSymbol(cls, name.encode, flags | Method, info).entered.asTerm + + private def newAliasType(name: TypeName, tpe: Type, flags: FlagSet = EmptyFlags): TypeSymbol = { + val sym = newSymbol(ScalaPackageClass, name, flags, TypeAlias(tpe)) + ScalaPackageClass.currentPackageDecls.enter(sym) + sym + } + + private def newPolyMethod(cls: ClassSymbol, name: TermName, typeParamCount: Int, + resultTypeFn: PolyType => Type, flags: FlagSet = EmptyFlags) = { + val tparamNames = tpnme.syntheticTypeParamNames(typeParamCount) + val tparamBounds = tparamNames map (_ => TypeBounds.empty) + val ptype = PolyType(tparamNames)(_ => tparamBounds, resultTypeFn) + newMethod(cls, name, ptype, flags) + } + + private def newT1ParameterlessMethod(cls: ClassSymbol, name: TermName, resultTypeFn: PolyType => Type, flags: FlagSet) = + newPolyMethod(cls, name, 1, resultTypeFn, flags) + + private def newT1EmptyParamsMethod(cls: ClassSymbol, name: TermName, resultTypeFn: PolyType => Type, flags: FlagSet) = + newPolyMethod(cls, name, 1, pt => MethodType(Nil, resultTypeFn(pt)), flags) + + private def mkArityArray(name: String, arity: Int, countFrom: Int): Array[TypeRef] = { + val arr = new Array[TypeRef](arity + 1) + for (i <- countFrom to arity) arr(i) = ctx.requiredClassRef(name + i) + arr + } + + private def completeClass(cls: ClassSymbol): ClassSymbol = { + ensureConstructor(cls, EmptyScope) + if (cls.linkedClass.exists) cls.linkedClass.info = NoType + cls + } + + lazy val RootClass: ClassSymbol = ctx.newPackageSymbol( + NoSymbol, nme.ROOT, (root, rootcls) => ctx.rootLoader(root)).moduleClass.asClass + lazy val RootPackage: TermSymbol = ctx.newSymbol( + NoSymbol, nme.ROOTPKG, PackageCreationFlags, TypeRef(NoPrefix, RootClass)) + + lazy val EmptyPackageVal = ctx.newPackageSymbol( + RootClass, nme.EMPTY_PACKAGE, (emptypkg, emptycls) => ctx.rootLoader(emptypkg)).entered + lazy val EmptyPackageClass = EmptyPackageVal.moduleClass.asClass + + /** A package in which we can place all methods that are interpreted specially by the compiler */ + lazy val OpsPackageVal = ctx.newCompletePackageSymbol(RootClass, nme.OPS_PACKAGE).entered + lazy val OpsPackageClass = OpsPackageVal.moduleClass.asClass + + lazy val ScalaPackageVal = ctx.requiredPackage("scala") + lazy val ScalaMathPackageVal = ctx.requiredPackage("scala.math") + lazy val ScalaPackageClass = ScalaPackageVal.moduleClass.asClass + lazy val JavaPackageVal = ctx.requiredPackage("java") + lazy val JavaLangPackageVal = ctx.requiredPackage("java.lang") + // fundamental modules + lazy val SysPackage = ctx.requiredModule("scala.sys.package") + lazy val Sys_errorR = SysPackage.moduleClass.requiredMethodRef(nme.error) + def Sys_error(implicit ctx: Context) = Sys_errorR.symbol + + /** The `scalaShadowing` package is used to safely modify classes and + * objects in scala so that they can be used from dotty. They will + * be visible as members of the `scala` package, replacing any objects + * or classes with the same name. But their binary artifacts are + * in `scalaShadowing` so they don't clash with the same-named `scala` + * members at runtime. + */ + lazy val ScalaShadowingPackageVal = ctx.requiredPackage("scalaShadowing") + lazy val ScalaShadowingPackageClass = ScalaShadowingPackageVal.moduleClass.asClass + + /** Note: We cannot have same named methods defined in Object and Any (and AnyVal, for that matter) + * because after erasure the Any and AnyVal references get remapped to the Object methods + * which would result in a double binding assertion failure. + * Instead we do the following: + * + * - Have some methods exist only in Any, and remap them with the Erasure denotation + * transformer to be owned by Object. + * - Have other methods exist only in Object. + * To achieve this, we synthesize all Any and Object methods; Object methods no longer get + * loaded from a classfile. + * + * There's a remaining question about `getClass`. In Scala2.x `getClass` was handled by compiler magic. + * This is deemed too cumersome for Dotty and therefore right now `getClass` gets no special treatment; + * it's just a method on `Any` which returns the raw type `java.lang.Class`. An alternative + * way to get better `getClass` typing would be to treat `getClass` as a method of a generic + * decorator which gets remapped in a later phase to Object#getClass. Then we could give it + * the right type without changing the typechecker: + * + * implicit class AnyGetClass[T](val x: T) extends AnyVal { + * def getClass: java.lang.Class[T] = ??? + * } + */ + lazy val AnyClass: ClassSymbol = completeClass(newCompleteClassSymbol(ScalaPackageClass, tpnme.Any, Abstract, Nil)) + def AnyType = AnyClass.typeRef + lazy val AnyValClass: ClassSymbol = completeClass(newCompleteClassSymbol(ScalaPackageClass, tpnme.AnyVal, Abstract, List(AnyClass.typeRef))) + def AnyValType = AnyValClass.typeRef + + lazy val Any_== = newMethod(AnyClass, nme.EQ, methOfAny(BooleanType), Final) + lazy val Any_!= = newMethod(AnyClass, nme.NE, methOfAny(BooleanType), Final) + lazy val Any_equals = newMethod(AnyClass, nme.equals_, methOfAny(BooleanType)) + lazy val Any_hashCode = newMethod(AnyClass, nme.hashCode_, MethodType(Nil, IntType)) + lazy val Any_toString = newMethod(AnyClass, nme.toString_, MethodType(Nil, StringType)) + lazy val Any_## = newMethod(AnyClass, nme.HASHHASH, ExprType(IntType), Final) + lazy val Any_getClass = newMethod(AnyClass, nme.getClass_, MethodType(Nil, ClassClass.typeRef.appliedTo(TypeBounds.empty)), Final) + lazy val Any_isInstanceOf = newT1ParameterlessMethod(AnyClass, nme.isInstanceOf_, _ => BooleanType, Final) + lazy val Any_asInstanceOf = newT1ParameterlessMethod(AnyClass, nme.asInstanceOf_, PolyParam(_, 0), Final) + + def AnyMethods = List(Any_==, Any_!=, Any_equals, Any_hashCode, + Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_asInstanceOf) + + lazy val ObjectClass: ClassSymbol = { + val cls = ctx.requiredClass("java.lang.Object") + assert(!cls.isCompleted, "race for completing java.lang.Object") + cls.info = ClassInfo(cls.owner.thisType, cls, AnyClass.typeRef :: Nil, newScope) + + // The companion object doesn't really exist, `NoType` is the general + // technique to do that. Here we need to set it before completing + // attempt to load Object's classfile, which causes issue #1648. + val companion = JavaLangPackageVal.info.decl(nme.Object).symbol + companion.info = NoType // to indicate that it does not really exist + + completeClass(cls) + } + def ObjectType = ObjectClass.typeRef + + lazy val AnyRefAlias: TypeSymbol = newAliasType(tpnme.AnyRef, ObjectType) + def AnyRefType = AnyRefAlias.typeRef + + lazy val Object_eq = newMethod(ObjectClass, nme.eq, methOfAnyRef(BooleanType), Final) + lazy val Object_ne = newMethod(ObjectClass, nme.ne, methOfAnyRef(BooleanType), Final) + lazy val Object_synchronized = newPolyMethod(ObjectClass, nme.synchronized_, 1, + pt => MethodType(List(PolyParam(pt, 0)), PolyParam(pt, 0)), Final) + lazy val Object_clone = newMethod(ObjectClass, nme.clone_, MethodType(Nil, ObjectType), Protected) + lazy val Object_finalize = newMethod(ObjectClass, nme.finalize_, MethodType(Nil, UnitType), Protected) + lazy val Object_notify = newMethod(ObjectClass, nme.notify_, MethodType(Nil, UnitType)) + lazy val Object_notifyAll = newMethod(ObjectClass, nme.notifyAll_, MethodType(Nil, UnitType)) + lazy val Object_wait = newMethod(ObjectClass, nme.wait_, MethodType(Nil, UnitType)) + lazy val Object_waitL = newMethod(ObjectClass, nme.wait_, MethodType(LongType :: Nil, UnitType)) + lazy val Object_waitLI = newMethod(ObjectClass, nme.wait_, MethodType(LongType :: IntType :: Nil, UnitType)) + + def ObjectMethods = List(Object_eq, Object_ne, Object_synchronized, Object_clone, + Object_finalize, Object_notify, Object_notifyAll, Object_wait, Object_waitL, Object_waitLI) + + /** Dummy method needed by elimByName */ + lazy val dummyApply = newPolyMethod( + OpsPackageClass, nme.dummyApply, 1, + pt => MethodType(List(FunctionOf(Nil, PolyParam(pt, 0))), PolyParam(pt, 0))) + + /** Method representing a throw */ + lazy val throwMethod = newMethod(OpsPackageClass, nme.THROWkw, + MethodType(List(ThrowableType), NothingType)) + + lazy val NothingClass: ClassSymbol = newCompleteClassSymbol( + ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyClass.typeRef)) + def NothingType = NothingClass.typeRef + lazy val NullClass: ClassSymbol = newCompleteClassSymbol( + ScalaPackageClass, tpnme.Null, AbstractFinal, List(ObjectClass.typeRef)) + def NullType = NullClass.typeRef + + lazy val ScalaPredefModuleRef = ctx.requiredModuleRef("scala.Predef") + def ScalaPredefModule(implicit ctx: Context) = ScalaPredefModuleRef.symbol + + lazy val Predef_conformsR = ScalaPredefModule.requiredMethodRef("$conforms") + def Predef_conforms(implicit ctx: Context) = Predef_conformsR.symbol + lazy val Predef_classOfR = ScalaPredefModule.requiredMethodRef("classOf") + def Predef_classOf(implicit ctx: Context) = Predef_classOfR.symbol + + lazy val ScalaRuntimeModuleRef = ctx.requiredModuleRef("scala.runtime.ScalaRunTime") + def ScalaRuntimeModule(implicit ctx: Context) = ScalaRuntimeModuleRef.symbol + def ScalaRuntimeClass(implicit ctx: Context) = ScalaRuntimeModule.moduleClass.asClass + + def runtimeMethodRef(name: PreName) = ScalaRuntimeModule.requiredMethodRef(name) + def ScalaRuntime_dropR(implicit ctx: Context) = runtimeMethodRef(nme.drop) + def ScalaRuntime_drop(implicit ctx: Context) = ScalaRuntime_dropR.symbol + + lazy val BoxesRunTimeModuleRef = ctx.requiredModuleRef("scala.runtime.BoxesRunTime") + def BoxesRunTimeModule(implicit ctx: Context) = BoxesRunTimeModuleRef.symbol + def BoxesRunTimeClass(implicit ctx: Context) = BoxesRunTimeModule.moduleClass.asClass + lazy val ScalaStaticsModuleRef = ctx.requiredModuleRef("scala.runtime.Statics") + def ScalaStaticsModule(implicit ctx: Context) = ScalaStaticsModuleRef.symbol + def ScalaStaticsClass(implicit ctx: Context) = ScalaStaticsModule.moduleClass.asClass + + def staticsMethodRef(name: PreName) = ScalaStaticsModule.requiredMethodRef(name) + def staticsMethod(name: PreName) = ScalaStaticsModule.requiredMethod(name) + + lazy val DottyPredefModuleRef = ctx.requiredModuleRef("dotty.DottyPredef") + def DottyPredefModule(implicit ctx: Context) = DottyPredefModuleRef.symbol + + def Predef_eqAny(implicit ctx: Context) = DottyPredefModule.requiredMethod(nme.eqAny) + + lazy val DottyArraysModuleRef = ctx.requiredModuleRef("dotty.runtime.Arrays") + def DottyArraysModule(implicit ctx: Context) = DottyArraysModuleRef.symbol + def newGenericArrayMethod(implicit ctx: Context) = DottyArraysModule.requiredMethod("newGenericArray") + def newArrayMethod(implicit ctx: Context) = DottyArraysModule.requiredMethod("newArray") + + lazy val NilModuleRef = ctx.requiredModuleRef("scala.collection.immutable.Nil") + def NilModule(implicit ctx: Context) = NilModuleRef.symbol + + lazy val SingletonClass: ClassSymbol = + // needed as a synthetic class because Scala 2.x refers to it in classfiles + // but does not define it as an explicit class. + newCompleteClassSymbol( + ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final, + List(AnyClass.typeRef), EmptyScope) + + lazy val SeqType: TypeRef = ctx.requiredClassRef("scala.collection.Seq") + def SeqClass(implicit ctx: Context) = SeqType.symbol.asClass + + lazy val Seq_applyR = SeqClass.requiredMethodRef(nme.apply) + def Seq_apply(implicit ctx: Context) = Seq_applyR.symbol + lazy val Seq_headR = SeqClass.requiredMethodRef(nme.head) + def Seq_head(implicit ctx: Context) = Seq_headR.symbol + + lazy val ArrayType: TypeRef = ctx.requiredClassRef("scala.Array") + def ArrayClass(implicit ctx: Context) = ArrayType.symbol.asClass + lazy val Array_applyR = ArrayClass.requiredMethodRef(nme.apply) + def Array_apply(implicit ctx: Context) = Array_applyR.symbol + lazy val Array_updateR = ArrayClass.requiredMethodRef(nme.update) + def Array_update(implicit ctx: Context) = Array_updateR.symbol + lazy val Array_lengthR = ArrayClass.requiredMethodRef(nme.length) + def Array_length(implicit ctx: Context) = Array_lengthR.symbol + lazy val Array_cloneR = ArrayClass.requiredMethodRef(nme.clone_) + def Array_clone(implicit ctx: Context) = Array_cloneR.symbol + lazy val ArrayConstructorR = ArrayClass.requiredMethodRef(nme.CONSTRUCTOR) + def ArrayConstructor(implicit ctx: Context) = ArrayConstructorR.symbol + lazy val ArrayModuleType = ctx.requiredModuleRef("scala.Array") + def ArrayModule(implicit ctx: Context) = ArrayModuleType.symbol.moduleClass.asClass + + + lazy val UnitType: TypeRef = valueTypeRef("scala.Unit", BoxedUnitType, java.lang.Void.TYPE, UnitEnc) + def UnitClass(implicit ctx: Context) = UnitType.symbol.asClass + lazy val BooleanType = valueTypeRef("scala.Boolean", BoxedBooleanType, java.lang.Boolean.TYPE, BooleanEnc) + def BooleanClass(implicit ctx: Context) = BooleanType.symbol.asClass + lazy val Boolean_notR = BooleanClass.requiredMethodRef(nme.UNARY_!) + def Boolean_! = Boolean_notR.symbol + lazy val Boolean_andR = BooleanClass.requiredMethodRef(nme.ZAND) // ### harmonize required... calls + def Boolean_&& = Boolean_andR.symbol + lazy val Boolean_orR = BooleanClass.requiredMethodRef(nme.ZOR) + def Boolean_|| = Boolean_orR.symbol + + lazy val ByteType: TypeRef = valueTypeRef("scala.Byte", BoxedByteType, java.lang.Byte.TYPE, ByteEnc) + def ByteClass(implicit ctx: Context) = ByteType.symbol.asClass + lazy val ShortType: TypeRef = valueTypeRef("scala.Short", BoxedShortType, java.lang.Short.TYPE, ShortEnc) + def ShortClass(implicit ctx: Context) = ShortType.symbol.asClass + lazy val CharType: TypeRef = valueTypeRef("scala.Char", BoxedCharType, java.lang.Character.TYPE, CharEnc) + def CharClass(implicit ctx: Context) = CharType.symbol.asClass + lazy val IntType: TypeRef = valueTypeRef("scala.Int", BoxedIntType, java.lang.Integer.TYPE, IntEnc) + def IntClass(implicit ctx: Context) = IntType.symbol.asClass + lazy val Int_minusR = IntClass.requiredMethodRef(nme.MINUS, List(IntType)) + def Int_- = Int_minusR.symbol + lazy val Int_plusR = IntClass.requiredMethodRef(nme.PLUS, List(IntType)) + def Int_+ = Int_plusR.symbol + lazy val Int_divR = IntClass.requiredMethodRef(nme.DIV, List(IntType)) + def Int_/ = Int_divR.symbol + lazy val Int_mulR = IntClass.requiredMethodRef(nme.MUL, List(IntType)) + def Int_* = Int_mulR.symbol + lazy val Int_eqR = IntClass.requiredMethodRef(nme.EQ, List(IntType)) + def Int_== = Int_eqR.symbol + lazy val Int_geR = IntClass.requiredMethodRef(nme.GE, List(IntType)) + def Int_>= = Int_geR.symbol + lazy val Int_leR = IntClass.requiredMethodRef(nme.LE, List(IntType)) + def Int_<= = Int_leR.symbol + lazy val LongType: TypeRef = valueTypeRef("scala.Long", BoxedLongType, java.lang.Long.TYPE, LongEnc) + def LongClass(implicit ctx: Context) = LongType.symbol.asClass + lazy val Long_XOR_Long = LongType.member(nme.XOR).requiredSymbol( + x => (x is Method) && (x.info.firstParamTypes.head isRef defn.LongClass) + ) + lazy val Long_LSR_Int = LongType.member(nme.LSR).requiredSymbol( + x => (x is Method) && (x.info.firstParamTypes.head isRef defn.IntClass) + ) + lazy val FloatType: TypeRef = valueTypeRef("scala.Float", BoxedFloatType, java.lang.Float.TYPE, FloatEnc) + def FloatClass(implicit ctx: Context) = FloatType.symbol.asClass + lazy val DoubleType: TypeRef = valueTypeRef("scala.Double", BoxedDoubleType, java.lang.Double.TYPE, DoubleEnc) + def DoubleClass(implicit ctx: Context) = DoubleType.symbol.asClass + + lazy val BoxedUnitType: TypeRef = ctx.requiredClassRef("scala.runtime.BoxedUnit") + def BoxedUnitClass(implicit ctx: Context) = BoxedUnitType.symbol.asClass + + def BoxedUnit_UNIT(implicit ctx: Context) = BoxedUnitClass.linkedClass.requiredValue("UNIT") + + lazy val BoxedBooleanType: TypeRef = ctx.requiredClassRef("java.lang.Boolean") + def BoxedBooleanClass(implicit ctx: Context) = BoxedBooleanType.symbol.asClass + lazy val BoxedByteType: TypeRef = ctx.requiredClassRef("java.lang.Byte") + def BoxedByteClass(implicit ctx: Context) = BoxedByteType.symbol.asClass + lazy val BoxedShortType: TypeRef = ctx.requiredClassRef("java.lang.Short") + def BoxedShortClass(implicit ctx: Context) = BoxedShortType.symbol.asClass + lazy val BoxedCharType: TypeRef = ctx.requiredClassRef("java.lang.Character") + def BoxedCharClass(implicit ctx: Context) = BoxedCharType.symbol.asClass + lazy val BoxedIntType: TypeRef = ctx.requiredClassRef("java.lang.Integer") + def BoxedIntClass(implicit ctx: Context) = BoxedIntType.symbol.asClass + lazy val BoxedLongType: TypeRef = ctx.requiredClassRef("java.lang.Long") + def BoxedLongClass(implicit ctx: Context) = BoxedLongType.symbol.asClass + lazy val BoxedFloatType: TypeRef = ctx.requiredClassRef("java.lang.Float") + def BoxedFloatClass(implicit ctx: Context) = BoxedFloatType.symbol.asClass + lazy val BoxedDoubleType: TypeRef = ctx.requiredClassRef("java.lang.Double") + def BoxedDoubleClass(implicit ctx: Context) = BoxedDoubleType.symbol.asClass + + lazy val BoxedBooleanModule = ctx.requiredModule("java.lang.Boolean") + lazy val BoxedByteModule = ctx.requiredModule("java.lang.Byte") + lazy val BoxedShortModule = ctx.requiredModule("java.lang.Short") + lazy val BoxedCharModule = ctx.requiredModule("java.lang.Character") + lazy val BoxedIntModule = ctx.requiredModule("java.lang.Integer") + lazy val BoxedLongModule = ctx.requiredModule("java.lang.Long") + lazy val BoxedFloatModule = ctx.requiredModule("java.lang.Float") + lazy val BoxedDoubleModule = ctx.requiredModule("java.lang.Double") + lazy val BoxedUnitModule = ctx.requiredModule("java.lang.Void") + + lazy val ByNameParamClass2x = specialPolyClass(tpnme.BYNAME_PARAM_CLASS, Covariant, Seq(AnyType)) + lazy val EqualsPatternClass = specialPolyClass(tpnme.EQUALS_PATTERN, EmptyFlags, Seq(AnyType)) + + lazy val RepeatedParamClass = specialPolyClass(tpnme.REPEATED_PARAM_CLASS, Covariant, Seq(ObjectType, SeqType)) + + // fundamental classes + lazy val StringClass = ctx.requiredClass("java.lang.String") + def StringType: Type = StringClass.typeRef + lazy val StringModule = StringClass.linkedClass + + lazy val String_+ = newMethod(StringClass, nme.raw.PLUS, methOfAny(StringType), Final) + lazy val String_valueOf_Object = StringModule.info.member(nme.valueOf).suchThat(_.info.firstParamTypes match { + case List(pt) => (pt isRef AnyClass) || (pt isRef ObjectClass) + case _ => false + }).symbol + + lazy val JavaCloneableClass = ctx.requiredClass("java.lang.Cloneable") + lazy val NullPointerExceptionClass = ctx.requiredClass("java.lang.NullPointerException") + lazy val ClassClass = ctx.requiredClass("java.lang.Class") + lazy val BoxedNumberClass = ctx.requiredClass("java.lang.Number") + lazy val ThrowableClass = ctx.requiredClass("java.lang.Throwable") + lazy val ClassCastExceptionClass = ctx.requiredClass("java.lang.ClassCastException") + lazy val JavaSerializableClass = ctx.requiredClass("java.lang.Serializable") + lazy val ComparableClass = ctx.requiredClass("java.lang.Comparable") + + // in scalac modified to have Any as parent + + lazy val SerializableType: TypeRef = ctx.requiredClassRef("scala.Serializable") + def SerializableClass(implicit ctx: Context) = SerializableType.symbol.asClass + lazy val StringBuilderType: TypeRef = ctx.requiredClassRef("scala.collection.mutable.StringBuilder") + def StringBuilderClass(implicit ctx: Context) = StringBuilderType.symbol.asClass + lazy val MatchErrorType: TypeRef = ctx.requiredClassRef("scala.MatchError") + def MatchErrorClass(implicit ctx: Context) = MatchErrorType.symbol.asClass + + lazy val StringAddType: TypeRef = ctx.requiredClassRef("scala.runtime.StringAdd") + def StringAddClass(implicit ctx: Context) = StringAddType.symbol.asClass + + lazy val StringAdd_plusR = StringAddClass.requiredMethodRef(nme.raw.PLUS) + def StringAdd_+(implicit ctx: Context) = StringAdd_plusR.symbol + + lazy val PartialFunctionType: TypeRef = ctx.requiredClassRef("scala.PartialFunction") + def PartialFunctionClass(implicit ctx: Context) = PartialFunctionType.symbol.asClass + lazy val AbstractPartialFunctionType: TypeRef = ctx.requiredClassRef("scala.runtime.AbstractPartialFunction") + def AbstractPartialFunctionClass(implicit ctx: Context) = AbstractPartialFunctionType.symbol.asClass + lazy val SymbolType: TypeRef = ctx.requiredClassRef("scala.Symbol") + def SymbolClass(implicit ctx: Context) = SymbolType.symbol.asClass + lazy val DynamicType: TypeRef = ctx.requiredClassRef("scala.Dynamic") + def DynamicClass(implicit ctx: Context) = DynamicType.symbol.asClass + lazy val OptionType: TypeRef = ctx.requiredClassRef("scala.Option") + def OptionClass(implicit ctx: Context) = OptionType.symbol.asClass + lazy val ProductType: TypeRef = ctx.requiredClassRef("scala.Product") + def ProductClass(implicit ctx: Context) = ProductType.symbol.asClass + lazy val Product_canEqualR = ProductClass.requiredMethodRef(nme.canEqual_) + def Product_canEqual(implicit ctx: Context) = Product_canEqualR.symbol + lazy val Product_productArityR = ProductClass.requiredMethodRef(nme.productArity) + def Product_productArity(implicit ctx: Context) = Product_productArityR.symbol + lazy val Product_productPrefixR = ProductClass.requiredMethodRef(nme.productPrefix) + def Product_productPrefix(implicit ctx: Context) = Product_productPrefixR.symbol + lazy val LanguageModuleRef = ctx.requiredModule("scala.language") + def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass + lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl") + + lazy val ClassTagType = ctx.requiredClassRef("scala.reflect.ClassTag") + def ClassTagClass(implicit ctx: Context) = ClassTagType.symbol.asClass + def ClassTagModule(implicit ctx: Context) = ClassTagClass.companionModule + + lazy val EqType = ctx.requiredClassRef("scala.Eq") + def EqClass(implicit ctx: Context) = EqType.symbol.asClass + + // Annotation base classes + lazy val AnnotationType = ctx.requiredClassRef("scala.annotation.Annotation") + def AnnotationClass(implicit ctx: Context) = AnnotationType.symbol.asClass + lazy val ClassfileAnnotationType = ctx.requiredClassRef("scala.annotation.ClassfileAnnotation") + def ClassfileAnnotationClass(implicit ctx: Context) = ClassfileAnnotationType.symbol.asClass + lazy val StaticAnnotationType = ctx.requiredClassRef("scala.annotation.StaticAnnotation") + def StaticAnnotationClass(implicit ctx: Context) = StaticAnnotationType.symbol.asClass + + // Annotation classes + lazy val AliasAnnotType = ctx.requiredClassRef("scala.annotation.internal.Alias") + def AliasAnnot(implicit ctx: Context) = AliasAnnotType.symbol.asClass + lazy val AnnotationDefaultAnnotType = ctx.requiredClassRef("scala.annotation.internal.AnnotationDefault") + def AnnotationDefaultAnnot(implicit ctx: Context) = AnnotationDefaultAnnotType.symbol.asClass + lazy val BodyAnnotType = ctx.requiredClassRef("scala.annotation.internal.Body") + def BodyAnnot(implicit ctx: Context) = BodyAnnotType.symbol.asClass + lazy val ChildAnnotType = ctx.requiredClassRef("scala.annotation.internal.Child") + def ChildAnnot(implicit ctx: Context) = ChildAnnotType.symbol.asClass + lazy val CovariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.CovariantBetween") + def CovariantBetweenAnnot(implicit ctx: Context) = CovariantBetweenAnnotType.symbol.asClass + lazy val ContravariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.ContravariantBetween") + def ContravariantBetweenAnnot(implicit ctx: Context) = ContravariantBetweenAnnotType.symbol.asClass + lazy val DeprecatedAnnotType = ctx.requiredClassRef("scala.deprecated") + def DeprecatedAnnot(implicit ctx: Context) = DeprecatedAnnotType.symbol.asClass + lazy val ImplicitNotFoundAnnotType = ctx.requiredClassRef("scala.annotation.implicitNotFound") + def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass + lazy val InlineAnnotType = ctx.requiredClassRef("scala.inline") + def InlineAnnot(implicit ctx: Context) = InlineAnnotType.symbol.asClass + lazy val InlineParamAnnotType = ctx.requiredClassRef("scala.annotation.internal.InlineParam") + def InlineParamAnnot(implicit ctx: Context) = InlineParamAnnotType.symbol.asClass + lazy val InvariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.InvariantBetween") + def InvariantBetweenAnnot(implicit ctx: Context) = InvariantBetweenAnnotType.symbol.asClass + lazy val MigrationAnnotType = ctx.requiredClassRef("scala.annotation.migration") + def MigrationAnnot(implicit ctx: Context) = MigrationAnnotType.symbol.asClass + lazy val NativeAnnotType = ctx.requiredClassRef("scala.native") + def NativeAnnot(implicit ctx: Context) = NativeAnnotType.symbol.asClass + lazy val RemoteAnnotType = ctx.requiredClassRef("scala.remote") + def RemoteAnnot(implicit ctx: Context) = RemoteAnnotType.symbol.asClass + lazy val RepeatedAnnotType = ctx.requiredClassRef("scala.annotation.internal.Repeated") + def RepeatedAnnot(implicit ctx: Context) = RepeatedAnnotType.symbol.asClass + lazy val SourceFileAnnotType = ctx.requiredClassRef("scala.annotation.internal.SourceFile") + def SourceFileAnnot(implicit ctx: Context) = SourceFileAnnotType.symbol.asClass + lazy val ScalaSignatureAnnotType = ctx.requiredClassRef("scala.reflect.ScalaSignature") + def ScalaSignatureAnnot(implicit ctx: Context) = ScalaSignatureAnnotType.symbol.asClass + lazy val ScalaLongSignatureAnnotType = ctx.requiredClassRef("scala.reflect.ScalaLongSignature") + def ScalaLongSignatureAnnot(implicit ctx: Context) = ScalaLongSignatureAnnotType.symbol.asClass + lazy val ScalaStrictFPAnnotType = ctx.requiredClassRef("scala.annotation.strictfp") + def ScalaStrictFPAnnot(implicit ctx: Context) = ScalaStrictFPAnnotType.symbol.asClass + lazy val ScalaStaticAnnotType = ctx.requiredClassRef("scala.annotation.static") + def ScalaStaticAnnot(implicit ctx: Context) = ScalaStaticAnnotType.symbol.asClass + lazy val SerialVersionUIDAnnotType = ctx.requiredClassRef("scala.SerialVersionUID") + def SerialVersionUIDAnnot(implicit ctx: Context) = SerialVersionUIDAnnotType.symbol.asClass + lazy val TASTYSignatureAnnotType = ctx.requiredClassRef("scala.annotation.internal.TASTYSignature") + def TASTYSignatureAnnot(implicit ctx: Context) = TASTYSignatureAnnotType.symbol.asClass + lazy val TASTYLongSignatureAnnotType = ctx.requiredClassRef("scala.annotation.internal.TASTYLongSignature") + def TASTYLongSignatureAnnot(implicit ctx: Context) = TASTYLongSignatureAnnotType.symbol.asClass + lazy val TailrecAnnotType = ctx.requiredClassRef("scala.annotation.tailrec") + def TailrecAnnot(implicit ctx: Context) = TailrecAnnotType.symbol.asClass + lazy val SwitchAnnotType = ctx.requiredClassRef("scala.annotation.switch") + def SwitchAnnot(implicit ctx: Context) = SwitchAnnotType.symbol.asClass + lazy val ThrowsAnnotType = ctx.requiredClassRef("scala.throws") + def ThrowsAnnot(implicit ctx: Context) = ThrowsAnnotType.symbol.asClass + lazy val TransientAnnotType = ctx.requiredClassRef("scala.transient") + def TransientAnnot(implicit ctx: Context) = TransientAnnotType.symbol.asClass + lazy val UncheckedAnnotType = ctx.requiredClassRef("scala.unchecked") + def UncheckedAnnot(implicit ctx: Context) = UncheckedAnnotType.symbol.asClass + lazy val UncheckedStableAnnotType = ctx.requiredClassRef("scala.annotation.unchecked.uncheckedStable") + def UncheckedStableAnnot(implicit ctx: Context) = UncheckedStableAnnotType.symbol.asClass + lazy val UncheckedVarianceAnnotType = ctx.requiredClassRef("scala.annotation.unchecked.uncheckedVariance") + def UncheckedVarianceAnnot(implicit ctx: Context) = UncheckedVarianceAnnotType.symbol.asClass + lazy val UnsafeNonvariantAnnotType = ctx.requiredClassRef("scala.annotation.internal.UnsafeNonvariant") + def UnsafeNonvariantAnnot(implicit ctx: Context) = UnsafeNonvariantAnnotType.symbol.asClass + lazy val VolatileAnnotType = ctx.requiredClassRef("scala.volatile") + def VolatileAnnot(implicit ctx: Context) = VolatileAnnotType.symbol.asClass + lazy val FieldMetaAnnotType = ctx.requiredClassRef("scala.annotation.meta.field") + def FieldMetaAnnot(implicit ctx: Context) = FieldMetaAnnotType.symbol.asClass + lazy val GetterMetaAnnotType = ctx.requiredClassRef("scala.annotation.meta.getter") + def GetterMetaAnnot(implicit ctx: Context) = GetterMetaAnnotType.symbol.asClass + lazy val SetterMetaAnnotType = ctx.requiredClassRef("scala.annotation.meta.setter") + def SetterMetaAnnot(implicit ctx: Context) = SetterMetaAnnotType.symbol.asClass + + // convenient one-parameter method types + def methOfAny(tp: Type) = MethodType(List(AnyType), tp) + def methOfAnyVal(tp: Type) = MethodType(List(AnyValType), tp) + def methOfAnyRef(tp: Type) = MethodType(List(ObjectType), tp) + + // Derived types + + def RepeatedParamType = RepeatedParamClass.typeRef + def ThrowableType = ThrowableClass.typeRef + + def ClassType(arg: Type)(implicit ctx: Context) = { + val ctype = ClassClass.typeRef + if (ctx.phase.erasedTypes) ctype else ctype.appliedTo(arg) + } + + /** The enumeration type, goven a value of the enumeration */ + def EnumType(sym: Symbol)(implicit ctx: Context) = + // given (in java): "class A { enum E { VAL1 } }" + // - sym: the symbol of the actual enumeration value (VAL1) + // - .owner: the ModuleClassSymbol of the enumeration (object E) + // - .linkedClass: the ClassSymbol of the enumeration (class E) + sym.owner.linkedClass.typeRef + + object FunctionOf { + def apply(args: List[Type], resultType: Type)(implicit ctx: Context) = + FunctionType(args.length).appliedTo(args ::: resultType :: Nil) + def unapply(ft: Type)(implicit ctx: Context)/*: Option[(List[Type], Type)]*/ = { + // -language:keepUnions difference: unapply needs result type because inferred type + // is Some[(List[Type], Type)] | None, which is not a legal unapply type. + val tsym = ft.typeSymbol + lazy val targs = ft.argInfos + val numArgs = targs.length - 1 + if (numArgs >= 0 && numArgs <= MaxFunctionArity && + (FunctionType(numArgs).symbol == tsym)) Some(targs.init, targs.last) + else None + } + } + + object ArrayOf { + def apply(elem: Type)(implicit ctx: Context) = + 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 _ => None + } + } + + /** An extractor for multi-dimensional arrays. + * Note that this will also extract the high bound if an + * element type is a wildcard. E.g. + * + * Array[_ <: Array[_ <: Number]] + * + * would match + * + * MultiArrayOf(, 2) + */ + object MultiArrayOf { + def apply(elem: Type, ndims: Int)(implicit ctx: Context): Type = + if (ndims == 0) elem else ArrayOf(apply(elem, ndims - 1)) + def unapply(tp: Type)(implicit ctx: Context): Option[(Type, Int)] = tp match { + case ArrayOf(elemtp) => + def recur(elemtp: Type): Option[(Type, Int)] = elemtp.dealias match { + case TypeBounds(lo, hi) => recur(hi) + case MultiArrayOf(finalElemTp, n) => Some(finalElemTp, n + 1) + case _ => Some(elemtp, 1) + } + recur(elemtp) + case _ => + None + } + } + + // ----- Symbol sets --------------------------------------------------- + + lazy val AbstractFunctionType = mkArityArray("scala.runtime.AbstractFunction", MaxAbstractFunctionArity, 0) + val AbstractFunctionClassPerRun = new PerRun[Array[Symbol]](implicit ctx => AbstractFunctionType.map(_.symbol.asClass)) + def AbstractFunctionClass(n: Int)(implicit ctx: Context) = AbstractFunctionClassPerRun()(ctx)(n) + lazy val FunctionType = mkArityArray("scala.Function", MaxFunctionArity, 0) + def FunctionClassPerRun = new PerRun[Array[Symbol]](implicit ctx => FunctionType.map(_.symbol.asClass)) + def FunctionClass(n: Int)(implicit ctx: Context) = FunctionClassPerRun()(ctx)(n) + lazy val Function0_applyR = FunctionType(0).symbol.requiredMethodRef(nme.apply) + def Function0_apply(implicit ctx: Context) = Function0_applyR.symbol + + lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2) + lazy val ProductNType = mkArityArray("scala.Product", MaxTupleArity, 0) + + private lazy val FunctionTypes: Set[TypeRef] = FunctionType.toSet + private lazy val TupleTypes: Set[TypeRef] = TupleType.toSet + private lazy val ProductTypes: Set[TypeRef] = ProductNType.toSet + + /** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */ + def scalaClassName(cls: Symbol)(implicit ctx: Context): TypeName = + if (cls.isClass && cls.owner == ScalaPackageClass) cls.asClass.name else EmptyTypeName + + /** If type `ref` refers to a class in the scala package, its name, otherwise EmptyTypeName */ + def scalaClassName(ref: Type)(implicit ctx: Context): TypeName = scalaClassName(ref.classSymbol) + + private def isVarArityClass(cls: Symbol, prefix: Name) = { + val name = scalaClassName(cls) + name.startsWith(prefix) && name.drop(prefix.length).forall(_.isDigit) + } + + def isBottomClass(cls: Symbol) = + cls == NothingClass || cls == NullClass + def isBottomType(tp: Type) = + tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass) + + def isFunctionClass(cls: Symbol) = isVarArityClass(cls, tpnme.Function) + def isAbstractFunctionClass(cls: Symbol) = isVarArityClass(cls, tpnme.AbstractFunction) + def isTupleClass(cls: Symbol) = isVarArityClass(cls, tpnme.Tuple) + def isProductClass(cls: Symbol) = isVarArityClass(cls, tpnme.Product) + + val StaticRootImportFns = List[() => TermRef]( + () => JavaLangPackageVal.termRef, + () => ScalaPackageVal.termRef + ) + + val PredefImportFns = List[() => TermRef]( + () => ScalaPredefModuleRef, + () => DottyPredefModuleRef + ) + + lazy val RootImportFns = + if (ctx.settings.YnoImports.value) List.empty[() => TermRef] + else if (ctx.settings.YnoPredef.value) StaticRootImportFns + else StaticRootImportFns ++ PredefImportFns + + lazy val RootImportTypes = RootImportFns.map(_()) + + /** Modules whose members are in the default namespace and their module classes */ + lazy val UnqualifiedOwnerTypes: Set[NamedType] = + RootImportTypes.toSet[NamedType] ++ RootImportTypes.map(_.symbol.moduleClass.typeRef) + + lazy val PhantomClasses = Set[Symbol](AnyClass, AnyValClass, NullClass, NothingClass) + + def isPolymorphicAfterErasure(sym: Symbol) = + (sym eq Any_isInstanceOf) || (sym eq Any_asInstanceOf) + + def isTupleType(tp: Type)(implicit ctx: Context) = { + val arity = tp.dealias.argInfos.length + arity <= MaxTupleArity && TupleType(arity) != null && (tp isRef TupleType(arity).symbol) + } + + def tupleType(elems: List[Type]) = { + TupleType(elems.size).appliedTo(elems) + } + + def isProductSubType(tp: Type)(implicit ctx: Context) = + (tp derivesFrom ProductType.symbol) && tp.baseClasses.exists(isProductClass) + + def isFunctionType(tp: Type)(implicit ctx: Context) = { + val arity = functionArity(tp) + 0 <= arity && arity <= MaxFunctionArity && (tp isRef FunctionType(arity).symbol) + } + + def functionArity(tp: Type)(implicit ctx: Context) = tp.dealias.argInfos.length - 1 + + // ----- primitive value class machinery ------------------------------------------ + + /** This class would also be obviated by the implicit function type design */ + class PerRun[T](generate: Context => T) { + private var current: RunId = NoRunId + private var cached: T = _ + def apply()(implicit ctx: Context): T = { + if (current != ctx.runId) { + cached = generate(ctx) + current = ctx.runId + } + cached + } + } + + lazy val ScalaNumericValueTypeList = List( + ByteType, ShortType, CharType, IntType, LongType, FloatType, DoubleType) + + private lazy val ScalaNumericValueTypes: collection.Set[TypeRef] = ScalaNumericValueTypeList.toSet + private lazy val ScalaValueTypes: collection.Set[TypeRef] = ScalaNumericValueTypes + UnitType + BooleanType + private lazy val ScalaBoxedTypes = ScalaValueTypes map (t => boxedTypes(t.name)) + + val ScalaNumericValueClasses = new PerRun[collection.Set[Symbol]](implicit ctx => ScalaNumericValueTypes.map(_.symbol)) + val ScalaValueClasses = new PerRun[collection.Set[Symbol]](implicit ctx => ScalaValueTypes.map(_.symbol)) + val ScalaBoxedClasses = new PerRun[collection.Set[Symbol]](implicit ctx => ScalaBoxedTypes.map(_.symbol)) + + private val boxedTypes = mutable.Map[TypeName, TypeRef]() + private val valueTypeEnc = mutable.Map[TypeName, PrimitiveClassEnc]() + +// private val unboxedTypeRef = mutable.Map[TypeName, TypeRef]() +// private val javaTypeToValueTypeRef = mutable.Map[Class[_], TypeRef]() +// private val valueTypeNameToJavaType = mutable.Map[TypeName, Class[_]]() + + private def valueTypeRef(name: String, boxed: TypeRef, jtype: Class[_], enc: Int): TypeRef = { + val vcls = ctx.requiredClassRef(name) + boxedTypes(vcls.name) = boxed + valueTypeEnc(vcls.name) = enc +// unboxedTypeRef(boxed.name) = vcls +// javaTypeToValueTypeRef(jtype) = vcls +// valueTypeNameToJavaType(vcls.name) = jtype + vcls + } + + /** The type of the boxed class corresponding to primitive value type `tp`. */ + def boxedType(tp: Type)(implicit ctx: Context): TypeRef = boxedTypes(scalaClassName(tp)) + + def wrapArrayMethodName(elemtp: Type): TermName = { + val cls = elemtp.classSymbol + if (cls.isPrimitiveValueClass) nme.wrapXArray(cls.name) + else if (cls.derivesFrom(ObjectClass) && !cls.isPhantomClass) nme.wrapRefArray + else nme.genericWrapArray + } + + type PrimitiveClassEnc = Int + + val ByteEnc = 2 + val ShortEnc = ByteEnc * 3 + val CharEnc = 5 + val IntEnc = ShortEnc * CharEnc + val LongEnc = IntEnc * 7 + val FloatEnc = LongEnc * 11 + val DoubleEnc = FloatEnc * 13 + val BooleanEnc = 17 + val UnitEnc = 19 + + def isValueSubType(tref1: TypeRef, tref2: TypeRef)(implicit ctx: Context) = + valueTypeEnc(tref2.name) % valueTypeEnc(tref1.name) == 0 + def isValueSubClass(sym1: Symbol, sym2: Symbol) = + valueTypeEnc(sym2.asClass.name) % valueTypeEnc(sym1.asClass.name) == 0 + + // ----- Initialization --------------------------------------------------- + + /** Lists core classes that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */ + lazy val syntheticScalaClasses = List( + AnyClass, + AnyRefAlias, + RepeatedParamClass, + ByNameParamClass2x, + AnyValClass, + NullClass, + NothingClass, + SingletonClass, + EqualsPatternClass) + + lazy val syntheticCoreClasses = syntheticScalaClasses ++ List( + EmptyPackageVal, + OpsPackageClass) + + /** Lists core methods that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */ + lazy val syntheticCoreMethods = AnyMethods ++ ObjectMethods ++ List(String_+, throwMethod) + + lazy val reservedScalaClassNames: Set[Name] = syntheticScalaClasses.map(_.name).toSet + + private[this] var _isInitialized = false + private def isInitialized = _isInitialized + + def init()(implicit ctx: Context) = { + this.ctx = ctx + if (!_isInitialized) { + // force initialization of every symbol that is synthesized or hijacked by the compiler + val forced = syntheticCoreClasses ++ syntheticCoreMethods ++ ScalaValueClasses() + + // Enter all symbols from the scalaShadowing package in the scala package + for (m <- ScalaShadowingPackageClass.info.decls) + ScalaPackageClass.enter(m) + + _isInitialized = true + } + } +} diff --git a/compiler/src/dotty/tools/dotc/core/DenotTransformers.scala b/compiler/src/dotty/tools/dotc/core/DenotTransformers.scala new file mode 100644 index 000000000..02d27ea33 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/DenotTransformers.scala @@ -0,0 +1,78 @@ +package dotty.tools.dotc +package core + +import Periods._ +import SymDenotations._ +import Contexts._ +import Types._ +import Symbols._ +import Denotations._ +import Phases._ +import java.lang.AssertionError +import dotty.tools.dotc.util.DotClass + +object DenotTransformers { + + /** A transformer group contains a sequence of transformers, + * ordered by the phase where they apply. Transformers are added + * to a group via `install`. + */ + + /** A transformer transforms denotations at a given phase */ + trait DenotTransformer extends Phase { + + /** The last phase during which the transformed denotations are valid */ + def lastPhaseId(implicit ctx: Context) = ctx.nextDenotTransformerId(id + 1) + + /** The validity period of the transformer in the given context */ + def validFor(implicit ctx: Context): Period = + Period(ctx.runId, id, lastPhaseId) + + /** The transformation method */ + def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation + } + + /** A transformer that only transforms the info field of denotations */ + trait InfoTransformer extends DenotTransformer { + + def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type + + def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = { + val sym = ref.symbol + if (sym.exists && !mayChange(sym)) ref + else { + val info1 = transformInfo(ref.info, ref.symbol) + if (info1 eq ref.info) ref + else ref match { + case ref: SymDenotation => ref.copySymDenotation(info = info1) + case _ => ref.derivedSingleDenotation(ref.symbol, info1) + } + } + } + + /** Denotations with a symbol where `mayChange` is false are guaranteed to be + * unaffected by this transform, so `transformInfo` need not be run. This + * can save time, and more importantly, can help avoid forcing symbol completers. + */ + protected def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = true + } + + /** A transformer that only transforms SymDenotations */ + trait SymTransformer extends DenotTransformer { + + def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation + + def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match { + case ref: SymDenotation => transformSym(ref) + case _ => ref + } + } + + /** A `DenotTransformer` trait that has the identity as its `transform` method. + * You might want to inherit from this trait so that new denotations can be + * installed using `installAfter` and `enteredAfter` at the end of the phase. + */ + trait IdentityDenotTransformer extends DenotTransformer { + def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref + } +} diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala new file mode 100644 index 000000000..6a39c5787 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -0,0 +1,1217 @@ +package dotty.tools +package dotc +package core + +import SymDenotations.{ SymDenotation, ClassDenotation, NoDenotation } +import Contexts.{Context, ContextBase} +import Names.{Name, PreName} +import Names.TypeName +import StdNames._ +import Symbols.NoSymbol +import Symbols._ +import Types._ +import Periods._ +import Flags._ +import DenotTransformers._ +import Decorators._ +import dotc.transform.Erasure +import printing.Texts._ +import printing.Printer +import io.AbstractFile +import config.Config +import util.common._ +import collection.mutable.ListBuffer +import Decorators.SymbolIteratorDecorator + +/** Denotations represent the meaning of symbols and named types. + * The following diagram shows how the principal types of denotations + * and their denoting entities relate to each other. Lines ending in + * a down-arrow `v` are member methods. The two methods shown in the diagram are + * "symbol" and "deref". Both methods are parameterized by the current context, + * and are effectively indexed by current period. + * + * Lines ending in a horizontal line mean subtying (right is a subtype of left). + * + * NamedType------TermRefWithSignature + * | | Symbol---------ClassSymbol + * | | | | + * | denot | denot | denot | denot + * v v v v + * Denotation-+-----SingleDenotation-+------SymDenotation-+----ClassDenotation + * | | + * +-----MultiDenotation | + * | + * +--UniqueRefDenotation + * +--JointRefDenotation + * + * Here's a short summary of the classes in this diagram. + * + * NamedType A type consisting of a prefix type and a name, with fields + * prefix: Type + * name: Name + * It has two subtypes: TermRef and TypeRef + * TermRefWithSignature A TermRef that has in addition a signature to select an overloaded variant, with new field + * sig: Signature + * Symbol A label for a definition or declaration in one compiler run + * ClassSymbol A symbol representing a class + * Denotation The meaning of a named type or symbol during a period + * MultiDenotation A denotation representing several overloaded members + * SingleDenotation A denotation representing a non-overloaded member or definition, with main fields + * symbol: Symbol + * info: Type + * UniqueRefDenotation A denotation referring to a single definition with some member type + * JointRefDenotation A denotation referring to a member that could resolve to several definitions + * SymDenotation A denotation representing a single definition with its original type, with main fields + * name: Name + * owner: Symbol + * flags: Flags + * privateWithin: Symbol + * annotations: List[Annotation] + * ClassDenotation A denotation representing a single class definition. + */ +object Denotations { + + implicit def eqDenotation: Eq[Denotation, Denotation] = Eq + + /** A denotation is the result of resolving + * a name (either simple identifier or select) during a given period. + * + * Denotations can be combined with `&` and `|`. + * & is conjunction, | is disjunction. + * + * `&` will create an overloaded denotation from two + * non-overloaded denotations if their signatures differ. + * Analogously `|` of two denotations with different signatures will give + * an empty denotation `NoDenotation`. + * + * A denotation might refer to `NoSymbol`. This is the case if the denotation + * was produced from a disjunction of two denotations with different symbols + * and there was no common symbol in a superclass that could substitute for + * both symbols. Here is an example: + * + * Say, we have: + * + * class A { def f: A } + * class B { def f: B } + * val x: A | B = if (test) new A else new B + * val y = x.f + * + * Then the denotation of `y` is `SingleDenotation(NoSymbol, A | B)`. + * + * @param symbol The referencing symbol, or NoSymbol is none exists + */ + abstract class Denotation(val symbol: Symbol) extends util.DotClass with printing.Showable { + + /** The type info of the denotation, exists only for non-overloaded denotations */ + def info(implicit ctx: Context): Type + + /** The type info, or, if this is a SymDenotation where the symbol + * is not yet completed, the completer + */ + def infoOrCompleter: Type + + /** The period during which this denotation is valid. */ + def validFor: Period + + /** Is this a reference to a type symbol? */ + def isType: Boolean + + /** Is this a reference to a term symbol? */ + def isTerm: Boolean = !isType + + /** Is this denotation overloaded? */ + final def isOverloaded = isInstanceOf[MultiDenotation] + + /** The signature of the denotation. */ + def signature(implicit ctx: Context): Signature + + /** Resolve overloaded denotation to pick the ones with the given signature + * when seen from prefix `site`. + * @param relaxed When true, consider only parameter signatures for a match. + */ + def atSignature(sig: Signature, site: Type = NoPrefix, relaxed: Boolean = false)(implicit ctx: Context): Denotation + + /** The variant of this denotation that's current in the given context. + * If no such denotation exists, returns the denotation with each alternative + * at its first point of definition. + */ + def current(implicit ctx: Context): Denotation + + /** Is this denotation different from NoDenotation or an ErrorDenotation? */ + def exists: Boolean = true + + /** A denotation with the info of this denotation transformed using `f` */ + def mapInfo(f: Type => Type)(implicit ctx: Context): Denotation + + /** If this denotation does not exist, fallback to alternative */ + final def orElse(that: => Denotation) = if (this.exists) this else that + + /** The set of alternative single-denotations making up this denotation */ + final def alternatives: List[SingleDenotation] = altsWith(alwaysTrue) + + /** The alternatives of this denotation that satisfy the predicate `p`. */ + def altsWith(p: Symbol => Boolean): List[SingleDenotation] + + /** The unique alternative of this denotation that satisfies the predicate `p`, + * or NoDenotation if no satisfying alternative exists. + * @throws TypeError if there is at more than one alternative that satisfies `p`. + */ + def suchThat(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation + + /** If this is a SingleDenotation, return it, otherwise throw a TypeError */ + def checkUnique(implicit ctx: Context): SingleDenotation = suchThat(alwaysTrue) + + /** Does this denotation have an alternative that satisfies the predicate `p`? */ + def hasAltWith(p: SingleDenotation => Boolean): Boolean + + /** The denotation made up from the alternatives of this denotation that + * are accessible from prefix `pre`, or NoDenotation if no accessible alternative exists. + */ + def accessibleFrom(pre: Type, superAccess: Boolean = false)(implicit ctx: Context): Denotation + + /** Find member of this denotation with given name and + * produce a denotation that contains the type of the member + * as seen from given prefix `pre`. Exclude all members that have + * flags in `excluded` from consideration. + */ + def findMember(name: Name, pre: Type, excluded: FlagSet)(implicit ctx: Context): Denotation = + info.findMember(name, pre, excluded) + + /** If this denotation is overloaded, filter with given predicate. + * If result is still overloaded throw a TypeError. + * Note: disambiguate is slightly different from suchThat in that + * single-denotations that do not satisfy the predicate are left alone + * (whereas suchThat would map them to NoDenotation). + */ + def disambiguate(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation = this match { + case sdenot: SingleDenotation => sdenot + case mdenot => suchThat(p) orElse NoQualifyingRef(alternatives) + } + + /** Return symbol in this denotation that satisfies the given predicate. + * if generateStubs is specified, return a stubsymbol if denotation is a missing ref. + * Throw a `TypeError` if predicate fails to disambiguate symbol or no alternative matches. + */ + def requiredSymbol(p: Symbol => Boolean, source: AbstractFile = null, generateStubs: Boolean = true)(implicit ctx: Context): Symbol = + disambiguate(p) match { + case m @ MissingRef(ownerd, name) => + if (generateStubs) { + m.ex.printStackTrace() + ctx.newStubSymbol(ownerd.symbol, name, source) + } + else NoSymbol + case NoDenotation | _: NoQualifyingRef => + throw new TypeError(s"None of the alternatives of $this satisfies required predicate") + case denot => + denot.symbol + } + + def requiredMethod(name: PreName)(implicit ctx: Context): TermSymbol = + info.member(name.toTermName).requiredSymbol(_ is Method).asTerm + def requiredMethodRef(name: PreName)(implicit ctx: Context): TermRef = + requiredMethod(name).termRef + + def requiredMethod(name: PreName, argTypes: List[Type])(implicit ctx: Context): TermSymbol = + info.member(name.toTermName).requiredSymbol(x=> + (x is Method) && x.info.paramTypess == List(argTypes) + ).asTerm + def requiredMethodRef(name: PreName, argTypes: List[Type])(implicit ctx: Context): TermRef = + requiredMethod(name, argTypes).termRef + + def requiredValue(name: PreName)(implicit ctx: Context): TermSymbol = + info.member(name.toTermName).requiredSymbol(_.info.isParameterless).asTerm + def requiredValueRef(name: PreName)(implicit ctx: Context): TermRef = + requiredValue(name).termRef + + def requiredClass(name: PreName)(implicit ctx: Context): ClassSymbol = + info.member(name.toTypeName).requiredSymbol(_.isClass).asClass + + /** The alternative of this denotation that has a type matching `targetType` when seen + * as a member of type `site`, `NoDenotation` if none exists. + */ + def matchingDenotation(site: Type, targetType: Type)(implicit ctx: Context): SingleDenotation = { + def qualifies(sym: Symbol) = site.memberInfo(sym).matchesLoosely(targetType) + if (isOverloaded) { + atSignature(targetType.signature, site, relaxed = true) match { + case sd: SingleDenotation => sd.matchingDenotation(site, targetType) + case md => md.suchThat(qualifies(_)) + } + } + else if (exists && !qualifies(symbol)) NoDenotation + else asSingleDenotation + } + + /** Handle merge conflict by throwing a `MergeError` exception */ + private def mergeConflict(tp1: Type, tp2: Type)(implicit ctx: Context): Type = { + def showType(tp: Type) = tp match { + case ClassInfo(_, cls, _, _, _) => cls.showLocated + case bounds: TypeBounds => i"type bounds $bounds" + case _ => tp.show + } + if (true) throw new MergeError(s"cannot merge ${showType(tp1)} with ${showType(tp2)}", tp1, tp2) + else throw new Error(s"cannot merge ${showType(tp1)} with ${showType(tp2)}") // flip condition for debugging + } + + /** Merge two lists of names. If names in corresponding positions match, keep them, + * otherwise generate new synthetic names. + */ + def mergeNames[N <: Name](names1: List[N], names2: List[N], syntheticName: Int => N): List[N] = { + for ((name1, name2, idx) <- (names1, names2, 0 until names1.length).zipped) + yield if (name1 == name2) name1 else syntheticName(idx) + }.toList + + /** Form a denotation by conjoining with denotation `that`. + * + * NoDenotations are dropped. MultiDenotations are handled by merging + * parts with same signatures. SingleDenotations with equal signatures + * are joined as follows: + * + * In a first step, consider only those denotations which have symbols + * that are accessible from prefix `pre`. + * + * If there are several such denotations, try to pick one by applying the following + * three precedence rules in decreasing order of priority: + * + * 1. Prefer denotations with more specific infos. + * 2. If infos are equally specific, prefer denotations with concrete symbols over denotations + * with abstract symbols. + * 3. If infos are equally specific and symbols are equally concrete, + * prefer denotations with symbols defined in subclasses + * over denotations with symbols defined in proper superclasses. + * + * If there is exactly one (preferred) accessible denotation, return it. + * + * If there is no preferred accessible denotation, return a JointRefDenotation + * with one of the operand symbols (unspecified which one), and an info which + * is the intersection (using `&` or `safe_&` if `safeIntersection` is true) + * of the infos of the operand denotations. + * + * If SingleDenotations with different signatures are joined, return NoDenotation. + */ + def & (that: Denotation, pre: Type, safeIntersection: Boolean = false)(implicit ctx: Context): Denotation = { + + /** Normally, `tp1 & tp2`. Special cases for matching methods and classes, with + * the possibility of raising a merge error. + */ + def infoMeet(tp1: Type, tp2: Type): Type = { + if (tp1 eq tp2) tp1 + else tp1 match { + case tp1: TypeBounds => + tp2 match { + case tp2: TypeBounds => if (safeIntersection) tp1 safe_& tp2 else tp1 & tp2 + case tp2: ClassInfo if tp1 contains tp2 => tp2 + case _ => mergeConflict(tp1, tp2) + } + case tp1: ClassInfo => + tp2 match { + case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix & tp2.prefix) + case tp2: TypeBounds if tp2 contains tp1 => tp1 + case _ => mergeConflict(tp1, tp2) + } + case tp1 @ MethodType(names1, formals1) if isTerm => + tp2 match { + case tp2 @ MethodType(names2, formals2) if ctx.typeComparer.matchingParams(formals1, formals2, tp1.isJava, tp2.isJava) && + tp1.isImplicit == tp2.isImplicit => + tp1.derivedMethodType( + mergeNames(names1, names2, nme.syntheticParamName), + formals1, + infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1))) + case _ => + mergeConflict(tp1, tp2) + } + case tp1: PolyType if isTerm => + tp2 match { + case tp2: PolyType if ctx.typeComparer.matchingTypeParams(tp1, tp2) => + tp1.derivedPolyType( + mergeNames(tp1.paramNames, tp2.paramNames, tpnme.syntheticTypeParamName), + tp1.paramBounds, + infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1))) + case _: MethodicType => + mergeConflict(tp1, tp2) + } + case _ => + tp1 & tp2 + } + } + + /** Try to merge denot1 and denot2 without adding a new signature. */ + def mergeDenot(denot1: Denotation, denot2: SingleDenotation): Denotation = denot1 match { + case denot1 @ MultiDenotation(denot11, denot12) => + val d1 = mergeDenot(denot11, denot2) + if (d1.exists) denot1.derivedMultiDenotation(d1, denot12) + else { + val d2 = mergeDenot(denot12, denot2) + if (d2.exists) denot1.derivedMultiDenotation(denot11, d2) + else NoDenotation + } + case denot1: SingleDenotation => + if (denot1 eq denot2) denot1 + else if (denot1.matches(denot2)) mergeSingleDenot(denot1, denot2) + else NoDenotation + } + + /** Try to merge single-denotations. */ + def mergeSingleDenot(denot1: SingleDenotation, denot2: SingleDenotation): SingleDenotation = { + val info1 = denot1.info + val info2 = denot2.info + val sym1 = denot1.symbol + val sym2 = denot2.symbol + + val sym2Accessible = sym2.isAccessibleFrom(pre) + + /** Does `sym1` come before `sym2` in the linearization of `pre`? */ + def precedes(sym1: Symbol, sym2: Symbol) = { + def precedesIn(bcs: List[ClassSymbol]): Boolean = bcs match { + case bc :: bcs1 => (sym1 eq bc) || !(sym2 eq bc) && precedesIn(bcs1) + case Nil => true + } + (sym1 ne sym2) && + (sym1.derivesFrom(sym2) || + !sym2.derivesFrom(sym1) && precedesIn(pre.baseClasses)) + } + + /** Similar to SymDenotation#accessBoundary, but without the special cases. */ + def accessBoundary(sym: Symbol) = + if (sym.is(Private)) sym.owner + else sym.privateWithin.orElse( + if (sym.is(Protected)) sym.owner.enclosingPackageClass + else defn.RootClass) + + /** Establish a partial order "preference" order between symbols. + * Give preference to `sym1` over `sym2` if one of the following + * conditions holds, in decreasing order of weight: + * 1. sym1 is concrete and sym2 is abstract + * 2. The owner of sym1 comes before the owner of sym2 in the linearization + * of the type of the prefix `pre`. + * 3. The access boundary of sym2 is properly contained in the access + * boundary of sym1. For protected access, we count the enclosing + * package as access boundary. + * 4. sym1 a method but sym2 is not. + * The aim of these criteria is to give some disambiguation on access which + * - does not depend on textual order or other arbitrary choices + * - minimizes raising of doubleDef errors + */ + def preferSym(sym1: Symbol, sym2: Symbol) = + sym1.eq(sym2) || + sym1.isAsConcrete(sym2) && + (!sym2.isAsConcrete(sym1) || + precedes(sym1.owner, sym2.owner) || + accessBoundary(sym2).isProperlyContainedIn(accessBoundary(sym1)) || + sym1.is(Method) && !sym2.is(Method)) || + sym1.info.isErroneous + + /** Sym preference provided types also override */ + def prefer(sym1: Symbol, sym2: Symbol, info1: Type, info2: Type) = + preferSym(sym1, sym2) && info1.overrides(info2) + + def handleDoubleDef = + if (preferSym(sym1, sym2)) denot1 + else if (preferSym(sym2, sym1)) denot2 + else doubleDefError(denot1, denot2, pre) + + if (sym2Accessible && prefer(sym2, sym1, info2, info1)) denot2 + else { + val sym1Accessible = sym1.isAccessibleFrom(pre) + if (sym1Accessible && prefer(sym1, sym2, info1, info2)) denot1 + else if (sym1Accessible && sym2.exists && !sym2Accessible) denot1 + else if (sym2Accessible && sym1.exists && !sym1Accessible) denot2 + else if (isDoubleDef(sym1, sym2)) handleDoubleDef + else { + val sym = + if (!sym1.exists) sym2 + else if (!sym2.exists) sym1 + else if (preferSym(sym2, sym1)) sym2 + else sym1 + val jointInfo = + try infoMeet(info1, info2) + catch { + case ex: MergeError => + if (pre.widen.classSymbol.is(Scala2x) || ctx.scala2Mode) + info1 // follow Scala2 linearization - + // compare with way merge is performed in SymDenotation#computeMembersNamed + else + throw new MergeError(s"${ex.getMessage} as members of type ${pre.show}", ex.tp1, ex.tp2) + } + new JointRefDenotation(sym, jointInfo, denot1.validFor & denot2.validFor) + } + } + } + + if (this eq that) this + else if (!this.exists) that + else if (!that.exists) this + else that match { + case that: SingleDenotation => + val r = mergeDenot(this, that) + if (r.exists) r else MultiDenotation(this, that) + case that @ MultiDenotation(denot1, denot2) => + this & (denot1, pre) & (denot2, pre) + } + } + + /** Form a choice between this denotation and that one. + * @param pre The prefix type of the members of the denotation, used + * to determine an accessible symbol if it exists. + */ + def | (that: Denotation, pre: Type)(implicit ctx: Context): Denotation = { + + /** Normally, `tp1 | tp2`. Special cases for matching methods and classes, with + * the possibility of raising a merge error. + */ + def infoJoin(tp1: Type, tp2: Type): Type = tp1 match { + case tp1: TypeBounds => + tp2 match { + case tp2: TypeBounds => tp1 | tp2 + case tp2: ClassInfo if tp1 contains tp2 => tp1 + case _ => mergeConflict(tp1, tp2) + } + case tp1: ClassInfo => + tp2 match { + case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix | tp2.prefix) + case tp2: TypeBounds if tp2 contains tp1 => tp2 + case _ => mergeConflict(tp1, tp2) + } + case tp1 @ MethodType(names1, formals1) => + tp2 match { + case tp2 @ MethodType(names2, formals2) + if ctx.typeComparer.matchingParams(formals1, formals2, tp1.isJava, tp2.isJava) && + tp1.isImplicit == tp2.isImplicit => + tp1.derivedMethodType( + mergeNames(names1, names2, nme.syntheticParamName), + formals1, tp1.resultType | tp2.resultType.subst(tp2, tp1)) + case _ => + mergeConflict(tp1, tp2) + } + case tp1: PolyType => + tp2 match { + case tp2: PolyType if ctx.typeComparer.matchingTypeParams(tp1, tp2) => + tp1.derivedPolyType( + mergeNames(tp1.paramNames, tp2.paramNames, tpnme.syntheticTypeParamName), + tp1.paramBounds, tp1.resultType | tp2.resultType.subst(tp2, tp1)) + case _ => + mergeConflict(tp1, tp2) + } + case _ => + tp1 | tp2 + } + + def unionDenot(denot1: SingleDenotation, denot2: SingleDenotation): Denotation = + if (denot1.matches(denot2)) { + val sym1 = denot1.symbol + val sym2 = denot2.symbol + val info1 = denot1.info + val info2 = denot2.info + val sameSym = sym1 eq sym2 + if (sameSym && (info1 frozen_<:< info2)) denot2 + else if (sameSym && (info2 frozen_<:< info1)) denot1 + else { + val jointSym = + if (sameSym) sym1 + else { + val owner2 = if (sym2 ne NoSymbol) sym2.owner else NoSymbol + /** Determine a symbol which is overridden by both sym1 and sym2. + * Preference is given to accessible symbols. + */ + def lubSym(overrides: Iterator[Symbol], previous: Symbol): Symbol = + if (!overrides.hasNext) previous + else { + val candidate = overrides.next + if (owner2 derivesFrom candidate.owner) + if (candidate isAccessibleFrom pre) candidate + else lubSym(overrides, previous orElse candidate) + else + lubSym(overrides, previous) + } + lubSym(sym1.allOverriddenSymbols, NoSymbol) + } + new JointRefDenotation( + jointSym, infoJoin(info1, info2), denot1.validFor & denot2.validFor) + } + } + else NoDenotation + + if (this eq that) this + else if (!this.exists) this + else if (!that.exists) that + else this match { + case denot1 @ MultiDenotation(denot11, denot12) => + denot1.derivedMultiDenotation(denot11 | (that, pre), denot12 | (that, pre)) + case denot1: SingleDenotation => + that match { + case denot2 @ MultiDenotation(denot21, denot22) => + denot2.derivedMultiDenotation(this | (denot21, pre), this | (denot22, pre)) + case denot2: SingleDenotation => + unionDenot(denot1, denot2) + } + } + } + + final def asSingleDenotation = asInstanceOf[SingleDenotation] + final def asSymDenotation = asInstanceOf[SymDenotation] + + def toText(printer: Printer): Text = printer.toText(this) + } + + /** An overloaded denotation consisting of the alternatives of both given denotations. + */ + case class MultiDenotation(denot1: Denotation, denot2: Denotation) extends Denotation(NoSymbol) { + final def infoOrCompleter = multiHasNot("info") + final def info(implicit ctx: Context) = infoOrCompleter + final def validFor = denot1.validFor & denot2.validFor + final def isType = false + final def signature(implicit ctx: Context) = Signature.OverloadedSignature + def atSignature(sig: Signature, site: Type, relaxed: Boolean)(implicit ctx: Context): Denotation = + derivedMultiDenotation(denot1.atSignature(sig, site, relaxed), denot2.atSignature(sig, site, relaxed)) + def current(implicit ctx: Context): Denotation = + derivedMultiDenotation(denot1.current, denot2.current) + def altsWith(p: Symbol => Boolean): List[SingleDenotation] = + denot1.altsWith(p) ++ denot2.altsWith(p) + def suchThat(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation = { + val sd1 = denot1.suchThat(p) + val sd2 = denot2.suchThat(p) + if (sd1.exists) + if (sd2.exists) + if (isDoubleDef(denot1.symbol, denot2.symbol)) doubleDefError(denot1, denot2) + else throw new TypeError(s"failure to disambiguate overloaded reference $this") + else sd1 + else sd2 + } + def hasAltWith(p: SingleDenotation => Boolean): Boolean = + denot1.hasAltWith(p) || denot2.hasAltWith(p) + def accessibleFrom(pre: Type, superAccess: Boolean)(implicit ctx: Context): Denotation = { + val d1 = denot1 accessibleFrom (pre, superAccess) + val d2 = denot2 accessibleFrom (pre, superAccess) + if (!d1.exists) d2 + else if (!d2.exists) d1 + else derivedMultiDenotation(d1, d2) + } + def mapInfo(f: Type => Type)(implicit ctx: Context): Denotation = + derivedMultiDenotation(denot1.mapInfo(f), denot2.mapInfo(f)) + def derivedMultiDenotation(d1: Denotation, d2: Denotation) = + if ((d1 eq denot1) && (d2 eq denot2)) this else MultiDenotation(d1, d2) + override def toString = alternatives.mkString(" ") + + private def multiHasNot(op: String): Nothing = + throw new UnsupportedOperationException( + s"multi-denotation with alternatives $alternatives does not implement operation $op") + } + + /** A non-overloaded denotation */ + abstract class SingleDenotation(symbol: Symbol) extends Denotation(symbol) with PreDenotation { + def hasUniqueSym: Boolean + protected def newLikeThis(symbol: Symbol, info: Type): SingleDenotation + + final def signature(implicit ctx: Context): Signature = { + if (isType) Signature.NotAMethod // don't force info if this is a type SymDenotation + else info match { + case info: MethodicType => + try info.signature + catch { // !!! DEBUG + case scala.util.control.NonFatal(ex) => + ctx.echo(s"cannot take signature of ${info.show}") + throw ex + } + case _ => Signature.NotAMethod + } + } + + def derivedSingleDenotation(symbol: Symbol, info: Type)(implicit ctx: Context): SingleDenotation = + if ((symbol eq this.symbol) && (info eq this.info)) this + else newLikeThis(symbol, info) + + def mapInfo(f: Type => Type)(implicit ctx: Context): SingleDenotation = + derivedSingleDenotation(symbol, f(info)) + + def orElse(that: => SingleDenotation) = if (this.exists) this else that + + def altsWith(p: Symbol => Boolean): List[SingleDenotation] = + if (exists && p(symbol)) this :: Nil else Nil + + def suchThat(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation = + if (exists && p(symbol)) this else NoDenotation + + def hasAltWith(p: SingleDenotation => Boolean): Boolean = + exists && p(this) + + def accessibleFrom(pre: Type, superAccess: Boolean)(implicit ctx: Context): Denotation = + if (!symbol.exists || symbol.isAccessibleFrom(pre, superAccess)) this else NoDenotation + + def atSignature(sig: Signature, site: Type, relaxed: Boolean)(implicit ctx: Context): SingleDenotation = { + val situated = if (site == NoPrefix) this else asSeenFrom(site) + val matches = sig.matchDegree(situated.signature) >= + (if (relaxed) Signature.ParamMatch else Signature.FullMatch) + if (matches) this else NoDenotation + } + + // ------ Forming types ------------------------------------------- + + /** The TypeRef representing this type denotation at its original location. */ + def typeRef(implicit ctx: Context): TypeRef = + TypeRef(symbol.owner.thisType, symbol.name.asTypeName, this) + + /** The TermRef representing this term denotation at its original location. */ + def termRef(implicit ctx: Context): TermRef = + TermRef(symbol.owner.thisType, symbol.name.asTermName, this) + + /** The TermRef representing this term denotation at its original location + * and at signature `NotAMethod`. + */ + def valRef(implicit ctx: Context): TermRef = + TermRef.withSigAndDenot(symbol.owner.thisType, symbol.name.asTermName, Signature.NotAMethod, this) + + /** The TermRef representing this term denotation at its original location + * at the denotation's signature. + * @note Unlike `valRef` and `termRef`, this will force the completion of the + * denotation via a call to `info`. + */ + def termRefWithSig(implicit ctx: Context): TermRef = + TermRef.withSigAndDenot(symbol.owner.thisType, symbol.name.asTermName, signature, this) + + /** The NamedType representing this denotation at its original location. + * Same as either `typeRef` or `termRefWithSig` depending whether this denotes a type or not. + */ + def namedType(implicit ctx: Context): NamedType = + if (isType) typeRef else termRefWithSig + + // ------ Transformations ----------------------------------------- + + private[this] var myValidFor: Period = Nowhere + + def validFor = myValidFor + def validFor_=(p: Period) = + myValidFor = p + + /** The next SingleDenotation in this run, with wrap-around from last to first. + * + * There may be several `SingleDenotation`s with different validity + * representing the same underlying definition at different phases. + * These are called a "flock". Flock members are generated by + * @See current. Flock members are connected in a ring + * with their `nextInRun` fields. + * + * There are the following invariants concerning flock members + * + * 1) validity periods are non-overlapping + * 2) the union of all validity periods is a contiguous + * interval. + */ + protected var nextInRun: SingleDenotation = this + + /** The version of this SingleDenotation that was valid in the first phase + * of this run. + */ + def initial: SingleDenotation = + if (validFor == Nowhere) this + else { + var current = nextInRun + while (current.validFor.code > this.myValidFor.code) current = current.nextInRun + current + } + + def history: List[SingleDenotation] = { + val b = new ListBuffer[SingleDenotation] + var current = initial + do { + b += (current) + current = current.nextInRun + } + while (current ne initial) + b.toList + } + + /** Invalidate all caches and fields that depend on base classes and their contents */ + def invalidateInheritedInfo(): Unit = () + + /** Move validity period of this denotation to a new run. Throw a StaleSymbol error + * if denotation is no longer valid. + */ + private def bringForward()(implicit ctx: Context): SingleDenotation = this match { + case denot: SymDenotation if ctx.stillValid(denot) => + assert(ctx.runId > validFor.runId || ctx.settings.YtestPickler.value, // mixing test pickler with debug printing can travel back in time + s"denotation $denot invalid in run ${ctx.runId}. ValidFor: $validFor") + var d: SingleDenotation = denot + do { + d.validFor = Period(ctx.period.runId, d.validFor.firstPhaseId, d.validFor.lastPhaseId) + d.invalidateInheritedInfo() + d = d.nextInRun + } while (d ne denot) + this + case _ => + if (coveredInterval.containsPhaseId(ctx.phaseId)) { + if (ctx.debug) ctx.traceInvalid(this) + staleSymbolError + } + else NoDenotation + } + + /** Produce a denotation that is valid for the given context. + * Usually called when !(validFor contains ctx.period) + * (even though this is not a precondition). + * If the runId of the context is the same as runId of this denotation, + * the right flock member is located, or, if it does not exist yet, + * created by invoking a transformer (@See Transformers). + * If the runId's differ, but this denotation is a SymDenotation + * and its toplevel owner class or module + * is still a member of its enclosing package, then the whole flock + * is brought forward to be valid in the new runId. Otherwise + * the symbol is stale, which constitutes an internal error. + */ + def current(implicit ctx: Context): SingleDenotation = { + val currentPeriod = ctx.period + val valid = myValidFor + if (valid.code <= 0) { + // can happen if we sit on a stale denotation which has been replaced + // wholesale by an installAfter; in this case, proceed to the next + // denotation and try again. + if (validFor == Nowhere && nextInRun.validFor != Nowhere) return nextInRun.current + assert(false) + } + + if (valid.runId != currentPeriod.runId) + if (exists) initial.bringForward.current + else this + else { + var cur = this + if (currentPeriod.code > valid.code) { + // search for containing period as long as nextInRun increases. + var next = nextInRun + while (next.validFor.code > valid.code && !(next.validFor contains currentPeriod)) { + cur = next + next = next.nextInRun + } + if (next.validFor.code > valid.code) { + // in this case, next.validFor contains currentPeriod + cur = next + cur + } else { + //println(s"might need new denot for $cur, valid for ${cur.validFor} at $currentPeriod") + // not found, cur points to highest existing variant + val nextTransformerId = ctx.nextDenotTransformerId(cur.validFor.lastPhaseId) + if (currentPeriod.lastPhaseId <= nextTransformerId) + cur.validFor = Period(currentPeriod.runId, cur.validFor.firstPhaseId, nextTransformerId) + else { + var startPid = nextTransformerId + 1 + val transformer = ctx.denotTransformers(nextTransformerId) + //println(s"transforming $this with $transformer") + try { + next = transformer.transform(cur)(ctx.withPhase(transformer)).syncWithParents + } catch { + case ex: CyclicReference => + println(s"error while transforming $this") // DEBUG + throw ex + } + if (next eq cur) + startPid = cur.validFor.firstPhaseId + else { + next match { + case next: ClassDenotation => + assert(!next.is(Package), s"illegal transformation of package denotation by transformer ${ctx.withPhase(transformer).phase}") + next.resetFlag(Frozen) + case _ => + } + next.insertAfter(cur) + cur = next + } + cur.validFor = Period(currentPeriod.runId, startPid, transformer.lastPhaseId) + //printPeriods(cur) + //println(s"new denot: $cur, valid for ${cur.validFor}") + } + cur.current // multiple transformations could be required + } + } else { + // currentPeriod < end of valid; in this case a version must exist + // but to be defensive we check for infinite loop anyway + var cnt = 0 + while (!(cur.validFor contains currentPeriod)) { + //println(s"searching: $cur at $currentPeriod, valid for ${cur.validFor}") + cur = cur.nextInRun + // Note: One might be tempted to add a `prev` field to get to the new denotation + // more directly here. I tried that, but it degrades rather than improves + // performance: Test setup: Compile everything in dotc and immediate subdirectories + // 10 times. Best out of 10: 18154ms with `prev` field, 17777ms without. + cnt += 1 + if (cnt > MaxPossiblePhaseId) + return current(ctx.withPhase(coveredInterval.firstPhaseId)) + } + cur + } + } + } + + private def demandOutsideDefinedMsg(implicit ctx: Context): String = + s"demanding denotation of $this at phase ${ctx.phase}(${ctx.phaseId}) outside defined interval: defined periods are${definedPeriodsString}" + + /** Install this denotation to be the result of the given denotation transformer. + * This is the implementation of the same-named method in SymDenotations. + * It's placed here because it needs access to private fields of SingleDenotation. + * @pre Can only be called in `phase.next`. + */ + protected def installAfter(phase: DenotTransformer)(implicit ctx: Context): Unit = { + val targetId = phase.next.id + if (ctx.phaseId != targetId) installAfter(phase)(ctx.withPhase(phase.next)) + else { + val current = symbol.current + // println(s"installing $this after $phase/${phase.id}, valid = ${current.validFor}") + // printPeriods(current) + this.validFor = Period(ctx.runId, targetId, current.validFor.lastPhaseId) + if (current.validFor.firstPhaseId >= targetId) + insertInsteadOf(current) + else { + current.validFor = Period(ctx.runId, current.validFor.firstPhaseId, targetId - 1) + insertAfter(current) + } + // printPeriods(this) + } + } + + /** Apply a transformation `f` to all denotations in this group that start at or after + * given phase. Denotations are replaced while keeping the same validity periods. + */ + protected def transformAfter(phase: DenotTransformer, f: SymDenotation => SymDenotation)(implicit ctx: Context): Unit = { + var current = symbol.current + while (current.validFor.firstPhaseId < phase.id && (current.nextInRun.validFor.code > current.validFor.code)) + current = current.nextInRun + var hasNext = true + while ((current.validFor.firstPhaseId >= phase.id) && hasNext) { + val current1: SingleDenotation = f(current.asSymDenotation) + if (current1 ne current) { + current1.validFor = current.validFor + current1.insertInsteadOf(current) + } + hasNext = current1.nextInRun.validFor.code > current1.validFor.code + current = current1.nextInRun + } + } + + /** Insert this denotation so that it follows `prev`. */ + private def insertAfter(prev: SingleDenotation) = { + this.nextInRun = prev.nextInRun + prev.nextInRun = this + } + + /** Insert this denotation instead of `old`. + * Also ensure that `old` refers with `nextInRun` to this denotation + * and set its `validFor` field to `NoWhere`. This is necessary so that + * references to the old denotation can be brought forward via `current` + * to a valid denotation. + * + * The code to achieve this is subtle in that it works correctly + * whether the replaced denotation is the only one in its cycle or not. + */ + private def insertInsteadOf(old: SingleDenotation): Unit = { + var prev = old + while (prev.nextInRun ne old) prev = prev.nextInRun + // order of next two assignments is important! + prev.nextInRun = this + this.nextInRun = old.nextInRun + old.validFor = Nowhere + } + + def staleSymbolError(implicit ctx: Context) = { + def ownerMsg = this match { + case denot: SymDenotation => s"in ${denot.owner}" + case _ => "" + } + def msg = s"stale symbol; $this#${symbol.id} $ownerMsg, defined in ${myValidFor}, is referred to in run ${ctx.period}" + throw new StaleSymbol(msg) + } + + /** The period (interval of phases) for which there exists + * a valid denotation in this flock. + */ + def coveredInterval(implicit ctx: Context): Period = { + var cur = this + var cnt = 0 + var interval = validFor + do { + cur = cur.nextInRun + cnt += 1 + assert(cnt <= MaxPossiblePhaseId, demandOutsideDefinedMsg) + interval |= cur.validFor + } while (cur ne this) + interval + } + + /** For ClassDenotations only: + * If caches influenced by parent classes are still valid, the denotation + * itself, otherwise a freshly initialized copy. + */ + def syncWithParents(implicit ctx: Context): SingleDenotation = this + + /** Show declaration string; useful for showing declarations + * as seen from subclasses. + */ + def showDcl(implicit ctx: Context): String = ctx.dclText(this).show + + override def toString = + if (symbol == NoSymbol) symbol.toString + else s"" + + def definedPeriodsString: String = { + var sb = new StringBuilder() + var cur = this + var cnt = 0 + do { + sb.append(" " + cur.validFor) + cur = cur.nextInRun + cnt += 1 + if (cnt > MaxPossiblePhaseId) { sb.append(" ..."); cur = this } + } while (cur ne this) + sb.toString + } + + // ------ PreDenotation ops ---------------------------------------------- + + final def first = this + final def last = this + final def toDenot(pre: Type)(implicit ctx: Context): Denotation = this + final def containsSym(sym: Symbol): Boolean = hasUniqueSym && (symbol eq sym) + final def matches(other: SingleDenotation)(implicit ctx: Context): Boolean = { + val d = signature.matchDegree(other.signature) + d == Signature.FullMatch || + d >= Signature.ParamMatch && info.matches(other.info) + } + final def filterWithPredicate(p: SingleDenotation => Boolean): SingleDenotation = + if (p(this)) this else NoDenotation + final def filterDisjoint(denots: PreDenotation)(implicit ctx: Context): SingleDenotation = + if (denots.exists && denots.matches(this)) NoDenotation else this + def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(implicit ctx: Context): SingleDenotation = + if (hasUniqueSym && prevDenots.containsSym(symbol)) NoDenotation + else if (isType) filterDisjoint(ownDenots).asSeenFrom(pre) + else asSeenFrom(pre).filterDisjoint(ownDenots) + final def filterExcluded(excluded: FlagSet)(implicit ctx: Context): SingleDenotation = + if (excluded.isEmpty || !(this overlaps excluded)) this else NoDenotation + + type AsSeenFromResult = SingleDenotation + protected def computeAsSeenFrom(pre: Type)(implicit ctx: Context): SingleDenotation = { + val symbol = this.symbol + val owner = this match { + case thisd: SymDenotation => thisd.owner + case _ => if (symbol.exists) symbol.owner else NoSymbol + } + if (!owner.membersNeedAsSeenFrom(pre)) this + else derivedSingleDenotation(symbol, info.asSeenFrom(pre, owner)) + } + + private def overlaps(fs: FlagSet)(implicit ctx: Context): Boolean = this match { + case sd: SymDenotation => sd is fs + case _ => symbol is fs + } + } + + abstract class NonSymSingleDenotation(symbol: Symbol) extends SingleDenotation(symbol) { + def infoOrCompleter: Type + def info(implicit ctx: Context) = infoOrCompleter + def isType = infoOrCompleter.isInstanceOf[TypeType] + } + + class UniqueRefDenotation( + symbol: Symbol, + val infoOrCompleter: Type, + initValidFor: Period) extends NonSymSingleDenotation(symbol) { + validFor = initValidFor + override def hasUniqueSym: Boolean = true + protected def newLikeThis(s: Symbol, i: Type): SingleDenotation = new UniqueRefDenotation(s, i, validFor) + } + + class JointRefDenotation( + symbol: Symbol, + val infoOrCompleter: Type, + initValidFor: Period) extends NonSymSingleDenotation(symbol) { + validFor = initValidFor + override def hasUniqueSym = false + protected def newLikeThis(s: Symbol, i: Type): SingleDenotation = new JointRefDenotation(s, i, validFor) + } + + class ErrorDenotation(implicit ctx: Context) extends NonSymSingleDenotation(NoSymbol) { + override def exists = false + override def hasUniqueSym = false + def infoOrCompleter = NoType + validFor = Period.allInRun(ctx.runId) + protected def newLikeThis(s: Symbol, i: Type): SingleDenotation = this + } + + /** An error denotation that provides more info about the missing reference. + * Produced by staticRef, consumed by requiredSymbol. + */ + case class MissingRef(val owner: SingleDenotation, name: Name)(implicit ctx: Context) extends ErrorDenotation { + val ex: Exception = new Exception + } + + /** An error denotation that provides more info about alternatives + * that were found but that do not qualify. + * Produced by staticRef, consumed by requiredSymbol. + */ + case class NoQualifyingRef(alts: List[SingleDenotation])(implicit ctx: Context) extends ErrorDenotation + + /** A double definition + */ + def isDoubleDef(sym1: Symbol, sym2: Symbol)(implicit ctx: Context): Boolean = + (sym1.exists && sym2.exists && + (sym1 ne sym2) && (sym1.owner eq sym2.owner) && + !sym1.is(Bridge) && !sym2.is(Bridge)) + + def doubleDefError(denot1: Denotation, denot2: Denotation, pre: Type = NoPrefix)(implicit ctx: Context): Nothing = { + val sym1 = denot1.symbol + val sym2 = denot2.symbol + def fromWhere = if (pre == NoPrefix) "" else i"\nwhen seen as members of $pre" + throw new MergeError( + i"""cannot merge + | $sym1: ${sym1.info} and + | $sym2: ${sym2.info}; + |they are both defined in ${sym1.owner} but have matching signatures + | ${denot1.info} and + | ${denot2.info}$fromWhere""", + denot2.info, denot2.info) + } + + // --------------- PreDenotations ------------------------------------------------- + + /** A PreDenotation represents a group of single denotations + * It is used as an optimization to avoid forming MultiDenotations too eagerly. + */ + trait PreDenotation { + + /** A denotation in the group exists */ + def exists: Boolean + + /** First/last denotation in the group */ + def first: Denotation + def last: Denotation + + /** Convert to full denotation by &-ing all elements */ + def toDenot(pre: Type)(implicit ctx: Context): Denotation + + /** Group contains a denotation that refers to given symbol */ + def containsSym(sym: Symbol): Boolean + + /** Group contains a denotation with given signature */ + def matches(other: SingleDenotation)(implicit ctx: Context): Boolean + + /** Keep only those denotations in this group which satisfy predicate `p`. */ + def filterWithPredicate(p: SingleDenotation => Boolean): PreDenotation + + /** Keep only those denotations in this group which have a signature + * that's not already defined by `denots`. + */ + def filterDisjoint(denots: PreDenotation)(implicit ctx: Context): PreDenotation + + /** Keep only those inherited members M of this predenotation for which the following is true + * - M is not marked Private + * - If M has a unique symbol, it does not appear in `prevDenots`. + * - M's signature as seen from prefix `pre` does not appear in `ownDenots` + * Return the denotation as seen from `pre`. + * Called from SymDenotations.computeMember. There, `ownDenots` are the denotations found in + * the base class, which shadow any inherited denotations with the same signature. + * `prevDenots` are the denotations that are defined in the class or inherited from + * a base type which comes earlier in the linearization. + */ + def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(implicit ctx: Context): PreDenotation + + /** Keep only those denotations in this group whose flags do not intersect + * with `excluded`. + */ + def filterExcluded(excluded: FlagSet)(implicit ctx: Context): PreDenotation + + private var cachedPrefix: Type = _ + private var cachedAsSeenFrom: AsSeenFromResult = _ + private var validAsSeenFrom: Period = Nowhere + type AsSeenFromResult <: PreDenotation + + /** The denotation with info(s) as seen from prefix type */ + final def asSeenFrom(pre: Type)(implicit ctx: Context): AsSeenFromResult = + if (Config.cacheAsSeenFrom) { + if ((cachedPrefix ne pre) || ctx.period != validAsSeenFrom) { + cachedAsSeenFrom = computeAsSeenFrom(pre) + cachedPrefix = pre + validAsSeenFrom = ctx.period + } + cachedAsSeenFrom + } else computeAsSeenFrom(pre) + + protected def computeAsSeenFrom(pre: Type)(implicit ctx: Context): AsSeenFromResult + + /** The union of two groups. */ + def union(that: PreDenotation) = + if (!this.exists) that + else if (!that.exists) this + else DenotUnion(this, that) + } + + final case class DenotUnion(denots1: PreDenotation, denots2: PreDenotation) extends PreDenotation { + assert(denots1.exists && denots2.exists, s"Union of non-existing denotations ($denots1) and ($denots2)") + def exists = true + def first = denots1.first + def last = denots2.last + def toDenot(pre: Type)(implicit ctx: Context) = + (denots1 toDenot pre) & (denots2 toDenot pre, pre) + def containsSym(sym: Symbol) = + (denots1 containsSym sym) || (denots2 containsSym sym) + def matches(other: SingleDenotation)(implicit ctx: Context): Boolean = + denots1.matches(other) || denots2.matches(other) + def filterWithPredicate(p: SingleDenotation => Boolean): PreDenotation = + derivedUnion(denots1 filterWithPredicate p, denots2 filterWithPredicate p) + def filterDisjoint(denots: PreDenotation)(implicit ctx: Context): PreDenotation = + derivedUnion(denots1 filterDisjoint denots, denots2 filterDisjoint denots) + def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(implicit ctx: Context): PreDenotation = + derivedUnion(denots1.mapInherited(ownDenots, prevDenots, pre), denots2.mapInherited(ownDenots, prevDenots, pre)) + def filterExcluded(excluded: FlagSet)(implicit ctx: Context): PreDenotation = + derivedUnion(denots1.filterExcluded(excluded), denots2.filterExcluded(excluded)) + + type AsSeenFromResult = PreDenotation + protected def computeAsSeenFrom(pre: Type)(implicit ctx: Context): PreDenotation = + derivedUnion(denots1.asSeenFrom(pre), denots2.asSeenFrom(pre)) + private def derivedUnion(denots1: PreDenotation, denots2: PreDenotation) = + if ((denots1 eq this.denots1) && (denots2 eq this.denots2)) this + else denots1 union denots2 + } + + // --------------- Context Base Trait ------------------------------- + + trait DenotationsBase { this: ContextBase => + + /** The current denotation of the static reference given by path, + * or a MissingRef or NoQualifyingRef instance, if it does not exist. + * if generateStubs is set, generates stubs for missing top-level symbols + */ + def staticRef(path: Name, generateStubs: Boolean = true)(implicit ctx: Context): Denotation = { + def recur(path: Name, len: Int): Denotation = { + val point = path.lastIndexOf('.', len - 1) + val owner = + if (point > 0) recur(path.toTermName, point).disambiguate(_.info.isParameterless) + else if (path.isTermName) defn.RootClass.denot + else defn.EmptyPackageClass.denot + if (owner.exists) { + val name = path slice (point + 1, len) + val result = owner.info.member(name) + if (result ne NoDenotation) result + else { + val alt = + if (generateStubs) missingHook(owner.symbol.moduleClass, name) + else NoSymbol + if (alt.exists) alt.denot + else MissingRef(owner, name) + } + } + else owner + } + recur(path, path.length) + } + + /** If we are looking for a non-existing term name in a package, + * assume it is a package for which we do not have a directory and + * enter it. + */ + def missingHook(owner: Symbol, name: Name)(implicit ctx: Context): Symbol = + if ((owner is Package) && name.isTermName) + ctx.newCompletePackageSymbol(owner, name.asTermName).entered + else + NoSymbol + } + + /** An exception for accessing symbols that are no longer valid in current run */ + class StaleSymbol(msg: => String) extends Exception { + util.Stats.record("stale symbol") + override def getMessage() = msg + } +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala new file mode 100644 index 000000000..63fbc98dc --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -0,0 +1,640 @@ +package dotty.tools.dotc.core + +import language.implicitConversions + +object Flags { + + /** A FlagSet represents a set of flags. Flags are encoded as follows: + * The first two bits indicate whether a flagset applies to terms, + * to types, or to both. Bits 2..63 are available for properties + * and can be doubly used for terms and types. + * Combining two FlagSets with `|` will give a FlagSet + * that has the intersection of the applicability to terms/types + * of the two flag sets. It is checked that the intersection is not empty. + */ + case class FlagSet(val bits: Long) extends AnyVal { + + /** The union of this flag set and the given flag set + */ + def | (that: FlagSet): FlagSet = + if (bits == 0) that + else if (that.bits == 0) this + else { + val tbits = bits & that.bits & KINDFLAGS + assert(tbits != 0, s"illegal flagset combination: $this and $that") + FlagSet(tbits | ((this.bits | that.bits) & ~KINDFLAGS)) + } + + /** The intersection of this flag set and the given flag set */ + def & (that: FlagSet) = FlagSet(bits & that.bits) + + /** The intersection of this flag set with the complement of the given flag set */ + def &~ (that: FlagSet) = { + val tbits = bits & KINDFLAGS + if ((tbits & that.bits) == 0) this + else FlagSet(tbits | ((this.bits & ~that.bits) & ~KINDFLAGS)) + } + + /** Does this flag set have a non-empty intersection with the given flag set? + * This means that both the kind flags and the carrier bits have non-empty intersection. + */ + def is(flags: FlagSet): Boolean = { + val fs = bits & flags.bits + (fs & KINDFLAGS) != 0 && (fs & ~KINDFLAGS) != 0 + } + + /** Does this flag set have a non-empty intersection with the given flag set, + * and at the same time contain none of the flags in the `butNot` set? + */ + def is(flags: FlagSet, butNot: FlagSet): Boolean = is(flags) && !is(butNot) + + /** Does this flag set have all of the flags in given flag conjunction? + * Pre: The intersection of the typeflags of both sets must be non-empty. + */ + def is(flags: FlagConjunction): Boolean = { + val fs = bits & flags.bits + (fs & KINDFLAGS) != 0 && + (fs >>> TYPESHIFT) == (flags.bits >>> TYPESHIFT) + } + + /** Does this flag set have all of the flags in given flag conjunction? + * and at the same time contain none of the flags in the `butNot` set? + * Pre: The intersection of the typeflags of both sets must be non-empty. + */ + def is(flags: FlagConjunction, butNot: FlagSet): Boolean = is(flags) && !is(butNot) + + def isEmpty = (bits & ~KINDFLAGS) == 0 + + /** Is this flag set a subset of that one? */ + def <= (that: FlagSet) = (bits & that.bits) == bits + + /** Does this flag set apply to terms? */ + def isTermFlags = (bits & TERMS) != 0 + + /** Does this flag set apply to terms? */ + def isTypeFlags = (bits & TYPES) != 0 + + /** This flag set with all flags transposed to be type flags */ + def toTypeFlags = if (bits == 0) this else FlagSet(bits & ~KINDFLAGS | TYPES) + + /** This flag set with all flags transposed to be term flags */ + def toTermFlags = if (bits == 0) this else FlagSet(bits & ~KINDFLAGS | TERMS) + + /** This flag set with all flags transposed to be common flags */ + def toCommonFlags = if (bits == 0) this else FlagSet(bits | KINDFLAGS) + + /** The number of non-kind flags in this set */ + def numFlags: Int = java.lang.Long.bitCount(bits & ~KINDFLAGS) + + /** The lowest non-kind bit set in this flagset */ + def firstBit: Int = java.lang.Long.numberOfTrailingZeros(bits & ~KINDFLAGS) + + /** The list of non-empty names of flags with given index idx that are set in this FlagSet */ + private def flagString(idx: Int): List[String] = + if ((bits & (1L << idx)) == 0) Nil + else { + def halfString(kind: Int) = + if ((bits & (1L << kind)) != 0) flagName(idx)(kind) else "" + val termFS = halfString(TERMindex) + val typeFS = halfString(TYPEindex) + val strs = termFS :: (if (termFS == typeFS) Nil else typeFS :: Nil) + strs filter (_.nonEmpty) + } + + /** The list of non-empty names of flags that are set in this FlagSet */ + def flagStrings: Seq[String] = { + val rawStrings = (2 to MaxFlag).flatMap(flagString) + if (this is Local) + rawStrings.filter(_ != "").map { + case "private" => "private[this]" + case "protected" => "protected[this]" + case str => str + } + else rawStrings + } + + /** The string representation of this flag set */ + override def toString = flagStrings.mkString(" ") + } + + /** A class representing flag sets that should be tested + * conjunctively. I.e. for a flag conjunction `fc`, + * `x is fc` tests whether `x` contains all flags in `fc`. + */ + case class FlagConjunction(bits: Long) { + override def toString = FlagSet(bits).toString + } + + private final val TYPESHIFT = 2 + private final val TERMindex = 0 + private final val TYPEindex = 1 + private final val TERMS = 1 << TERMindex + private final val TYPES = 1 << TYPEindex + private final val KINDFLAGS = TERMS | TYPES + + private final val FirstFlag = 2 + private final val FirstNotPickledFlag = 48 + private final val MaxFlag = 63 + + private val flagName = Array.fill(64, 2)("") + + private def isDefinedAsFlag(idx: Int) = flagName(idx) exists (_.nonEmpty) + + /** The flag set containing all defined flags of either kind whose bits + * lie in the given range + */ + private def flagRange(start: Int, end: Int) = + FlagSet((KINDFLAGS.toLong /: (start until end)) ((bits, idx) => + if (isDefinedAsFlag(idx)) bits | (1L << idx) else bits)) + + /** The flag with given index between 2 and 63 which applies to terms. + * Installs given name as the name of the flag. */ + private def termFlag(index: Int, name: String): FlagSet = { + flagName(index)(TERMindex) = name + FlagSet(TERMS | (1L << index)) + } + + /** The flag with given index between 2 and 63 which applies to types. + * Installs given name as the name of the flag. */ + private def typeFlag(index: Int, name: String): FlagSet = { + flagName(index)(TYPEindex) = name + FlagSet(TYPES | (1L << index)) + } + + /** The flag with given index between 2 and 63 which applies to both terms and types + * Installs given name as the name of the flag. */ + private def commonFlag(index: Int, name: String): FlagSet = { + flagName(index)(TERMindex) = name + flagName(index)(TYPEindex) = name + FlagSet(TERMS | TYPES | (1L << index)) + } + + /** The union of all flags in given flag set */ + def union(flagss: FlagSet*) = (EmptyFlags /: flagss)(_ | _) + + /** The conjunction of all flags in given flag set */ + def allOf(flagss: FlagSet*) = { + assert(flagss forall (_.numFlags == 1), "Flags.allOf doesn't support flag " + flagss.find(_.numFlags != 1)) + FlagConjunction(union(flagss: _*).bits) + } + + def commonFlags(flagss: FlagSet*) = union(flagss.map(_.toCommonFlags): _*) + + /** The empty flag set */ + final val EmptyFlags = FlagSet(0) + + /** The undefined flag set */ + final val UndefinedFlags = FlagSet(~KINDFLAGS) + + // Available flags: + + /** Labeled with `private` modifier */ + final val Private = commonFlag(2, "private") + final val PrivateTerm = Private.toTermFlags + final val PrivateType = Private.toTypeFlags + + /** Labeled with `protected` modifier */ + final val Protected = commonFlag(3, "protected") + + /** Labeled with `override` modifier */ + final val Override = commonFlag(4, "override") + + /** A declared, but not defined member */ + final val Deferred = commonFlag(5, "") + final val DeferredTerm = Deferred.toTermFlags + final val DeferredType = Deferred.toTypeFlags + + /** Labeled with `final` modifier */ + final val Final = commonFlag(6, "final") + + /** A method symbol. */ + final val MethodOrHKCommon = commonFlag(7, "") + final val Method = MethodOrHKCommon.toTermFlags + final val HigherKinded = MethodOrHKCommon.toTypeFlags + + /** A (term or type) parameter to a class or method */ + final val Param = commonFlag(8, "") + final val TermParam = Param.toTermFlags + final val TypeParam = Param.toTypeFlags + + /** Labeled with `implicit` modifier (implicit value) */ + final val ImplicitCommon = commonFlag(9, "implicit") + final val Implicit = ImplicitCommon.toTermFlags + + /** Labeled with `lazy` (a lazy val). */ + final val Lazy = termFlag(10, "lazy") + + /** A trait */ + final val Trait = typeFlag(10, "") + + final val LazyOrTrait = Lazy.toCommonFlags + + /** A value or variable accessor (getter or setter) */ + final val Accessor = termFlag(11, "") + + /** Labeled with `sealed` modifier (sealed class) */ + final val Sealed = typeFlag(11, "sealed") + + final val AccessorOrSealed = Accessor.toCommonFlags + + /** A mutable var */ + final val Mutable = termFlag(12, "mutable") + + /** Symbol is local to current class (i.e. private[this] or protected[this] + * pre: Private or Protected are also set + */ + final val Local = commonFlag(13, "") + + /** A field generated for a primary constructor parameter (no matter if it's a 'val' or not), + * or an accessor of such a field. + */ + final val ParamAccessor = commonFlag(14, "") + final val TermParamAccessor = ParamAccessor.toTermFlags + final val TypeParamAccessor = ParamAccessor.toTypeFlags + + /** A value or class implementing a module */ + final val Module = commonFlag(15, "module") + final val ModuleVal = Module.toTermFlags + final val ModuleClass = Module.toTypeFlags + + /** A value or class representing a package */ + final val Package = commonFlag(16, "") + final val PackageVal = Package.toTermFlags + final val PackageClass = Package.toTypeFlags + + /** A case class or its companion object */ + final val Case = commonFlag(17, "case") + final val CaseClass = Case.toTypeFlags + final val CaseVal = Case.toTermFlags + + /** A compiler-generated symbol, which is visible for type-checking + * (compare with artifact) + */ + final val Synthetic = commonFlag(18, "") + + /** Symbol's name is expanded */ + final val ExpandedName = commonFlag(19, "") + + /** A covariant type variable / an outer accessor */ + final val CovariantOrOuter = commonFlag(20, "") + final val Covariant = typeFlag(20, "") + final val OuterAccessor = termFlag(20, "") + + /** A contravariant type variable / a label method */ + final val ContravariantOrLabel = commonFlag(21, "") + final val Contravariant = typeFlag(21, "") + final val Label = termFlag(21, "