diff options
Diffstat (limited to 'src')
68 files changed, 1836 insertions, 1011 deletions
diff --git a/src/dotty/language.scala b/src/dotty/language.scala deleted file mode 100644 index 416a4281b..000000000 --- a/src/dotty/language.scala +++ /dev/null @@ -1,9 +0,0 @@ -package dotty - -object language { - - class Feature - - /** Keep union types */ - val keepUnions = new Feature -} diff --git a/src/dotty/tools/backend/jvm/CollectEntryPoints.scala b/src/dotty/tools/backend/jvm/CollectEntryPoints.scala index 3ed232bc7..2ee1b6011 100644 --- a/src/dotty/tools/backend/jvm/CollectEntryPoints.scala +++ b/src/dotty/tools/backend/jvm/CollectEntryPoints.scala @@ -107,7 +107,7 @@ object CollectEntryPoints{ else (possibles exists(x=> isJavaMainMethod(x.symbol))) || { possibles exists { m => toDenot(m.symbol).info match { - case t:PolyType => + case t: PolyType => fail("main methods cannot be generic.") case t@MethodType(paramNames, paramTypes) => if (t.resultType :: paramTypes exists (_.typeSymbol.isAbstractType)) diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index ecb6a3212..af34164dc 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -9,6 +9,7 @@ import Decorators._ import language.higherKinds import collection.mutable.ListBuffer import util.Property +import reporting.diagnostic.messages._ object desugar { import untpd._ @@ -71,7 +72,9 @@ object desugar { val defctx = ctx.outersIterator.dropWhile(_.scope eq ctx.scope).next var local = defctx.denotNamed(tp.name).suchThat(_ is ParamOrAccessor).symbol if (local.exists) (defctx.owner.thisType select local).dealias - else throw new Error(s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}") + else throw new java.lang.Error( + s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}" + ) case _ => mapOver(tp) } @@ -281,7 +284,7 @@ object desugar { val constrVparamss = if (constr1.vparamss.isEmpty) { // ensure parameter list is non-empty if (isCaseClass) - ctx.error("case class needs to have at least one parameter list", cdef.pos) + ctx.error(CaseClassMissingParamList(cdef), cdef.namePos) ListOfNil } else constr1.vparamss.nestedMap(toDefParam) diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index 70701ecd7..2c02e7d1e 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -3,8 +3,8 @@ package dotc package ast import core._ -import Types._, Names._, Flags._, util.Positions._, Contexts._, Constants._, SymDenotations._, Symbols._ -import Denotations._, StdNames._, Comments._ +import Types._, Names._, Flags._, util.Positions._, Contexts._, Constants._ +import SymDenotations._, Symbols._, Denotations._, StdNames._, Comments._ import annotation.tailrec import language.higherKinds import collection.IndexedSeqOptimized @@ -48,7 +48,7 @@ object Trees { * the existing tree transparently, assigning its `tpe` field, * provided it was `null` before. * - It is impossible to embed untyped trees in typed ones. - * - Typed trees can be embedded untyped ones provided they are rooted + * - Typed trees can be embedded in untyped ones provided they are rooted * in a TypedSplice node. * - Type checking an untyped tree should remove all embedded `TypedSplice` * nodes. @@ -308,8 +308,6 @@ object Trees { if (rawMods.is(Synthetic)) Position(pos.point, pos.point) else Position(pos.point, pos.point + name.length, pos.point) else pos - - } /** A ValDef or DefDef tree */ @@ -564,9 +562,9 @@ object Trees { } /** [typeparams] -> tpt */ - case class TypeLambdaTree[-T >: Untyped] private[ast] (tparams: List[TypeDef[T]], body: Tree[T]) + case class PolyTypeTree[-T >: Untyped] private[ast] (tparams: List[TypeDef[T]], body: Tree[T]) extends TypTree[T] { - type ThisTree[-T >: Untyped] = TypeLambdaTree[T] + type ThisTree[-T >: Untyped] = PolyTypeTree[T] } /** => T */ @@ -822,7 +820,7 @@ object Trees { type OrTypeTree = Trees.OrTypeTree[T] type RefinedTypeTree = Trees.RefinedTypeTree[T] type AppliedTypeTree = Trees.AppliedTypeTree[T] - type TypeLambdaTree = Trees.TypeLambdaTree[T] + type PolyTypeTree = Trees.PolyTypeTree[T] type ByNameTypeTree = Trees.ByNameTypeTree[T] type TypeBoundsTree = Trees.TypeBoundsTree[T] type Bind = Trees.Bind[T] @@ -855,7 +853,7 @@ object Trees { val cpy: TreeCopier - /** A class for copying trees. The copy methods avid creating a new tree + /** A class for copying trees. The copy methods avoid creating a new tree * If all arguments stay the same. * * Note: Some of the copy methods take a context. @@ -986,9 +984,9 @@ object Trees { case tree: AppliedTypeTree if (tpt eq tree.tpt) && (args eq tree.args) => tree case _ => finalize(tree, untpd.AppliedTypeTree(tpt, args)) } - def TypeLambdaTree(tree: Tree)(tparams: List[TypeDef], body: Tree): TypeLambdaTree = tree match { - case tree: TypeLambdaTree if (tparams eq tree.tparams) && (body eq tree.body) => tree - case _ => finalize(tree, untpd.TypeLambdaTree(tparams, body)) + def PolyTypeTree(tree: Tree)(tparams: List[TypeDef], body: Tree): PolyTypeTree = tree match { + case tree: PolyTypeTree if (tparams eq tree.tparams) && (body eq tree.body) => tree + case _ => finalize(tree, untpd.PolyTypeTree(tparams, body)) } def ByNameTypeTree(tree: Tree)(result: Tree): ByNameTypeTree = tree match { case tree: ByNameTypeTree if result eq tree.result => tree @@ -1120,8 +1118,8 @@ object Trees { cpy.RefinedTypeTree(tree)(transform(tpt), transformSub(refinements)) case AppliedTypeTree(tpt, args) => cpy.AppliedTypeTree(tree)(transform(tpt), transform(args)) - case TypeLambdaTree(tparams, body) => - cpy.TypeLambdaTree(tree)(transformSub(tparams), transform(body)) + case PolyTypeTree(tparams, body) => + cpy.PolyTypeTree(tree)(transformSub(tparams), transform(body)) case ByNameTypeTree(result) => cpy.ByNameTypeTree(tree)(transform(result)) case TypeBoundsTree(lo, hi) => @@ -1224,7 +1222,7 @@ object Trees { this(this(x, tpt), refinements) case AppliedTypeTree(tpt, args) => this(this(x, tpt), args) - case TypeLambdaTree(tparams, body) => + case PolyTypeTree(tparams, body) => implicit val ctx: Context = localCtx this(this(x, tparams), body) case ByNameTypeTree(result) => diff --git a/src/dotty/tools/dotc/ast/untpd.scala b/src/dotty/tools/dotc/ast/untpd.scala index cc7cefbac..852c3a346 100644 --- a/src/dotty/tools/dotc/ast/untpd.scala +++ b/src/dotty/tools/dotc/ast/untpd.scala @@ -212,7 +212,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def OrTypeTree(left: Tree, right: Tree): OrTypeTree = new OrTypeTree(left, right) def RefinedTypeTree(tpt: Tree, refinements: List[Tree]): RefinedTypeTree = new RefinedTypeTree(tpt, refinements) def AppliedTypeTree(tpt: Tree, args: List[Tree]): AppliedTypeTree = new AppliedTypeTree(tpt, args) - def TypeLambdaTree(tparams: List[TypeDef], body: Tree): TypeLambdaTree = new TypeLambdaTree(tparams, body) + def PolyTypeTree(tparams: List[TypeDef], body: Tree): PolyTypeTree = new PolyTypeTree(tparams, body) def ByNameTypeTree(result: Tree): ByNameTypeTree = new ByNameTypeTree(result) def TypeBoundsTree(lo: Tree, hi: Tree): TypeBoundsTree = new TypeBoundsTree(lo, hi) def Bind(name: Name, body: Tree): Bind = new Bind(name, body) diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index c188bfab4..7744a5479 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -87,8 +87,12 @@ object Config { */ final val checkLambdaVariance = false - /** Check that certain types cannot be created in erasedTypes phases */ - final val checkUnerased = true + /** Check that certain types cannot be created in erasedTypes phases. + * Note: Turning this option on will get some false negatives, since it is + * possible that And/Or types are still created during erasure as the result + * of some operation on an existing type. + */ + final val checkUnerased = false /** In `derivedSelect`, rewrite * diff --git a/src/dotty/tools/dotc/config/PathResolver.scala b/src/dotty/tools/dotc/config/PathResolver.scala index f9f698e72..55d585e94 100644 --- a/src/dotty/tools/dotc/config/PathResolver.scala +++ b/src/dotty/tools/dotc/config/PathResolver.scala @@ -180,6 +180,7 @@ class PathResolver(implicit ctx: Context) { case "extdirs" => settings.extdirs.value case "classpath" | "cp" => settings.classpath.value case "sourcepath" => settings.sourcepath.value + case "priorityclasspath" => settings.priorityclasspath.value } /** Calculated values based on any given command line options, falling back on @@ -193,6 +194,7 @@ class PathResolver(implicit ctx: Context) { def javaUserClassPath = if (useJavaClassPath) Defaults.javaUserClassPath else "" def scalaBootClassPath = cmdLineOrElse("bootclasspath", Defaults.scalaBootClassPath) def scalaExtDirs = cmdLineOrElse("extdirs", Defaults.scalaExtDirs) + def priorityClassPath = cmdLineOrElse("prioritypath", "") /** Scaladoc doesn't need any bootstrapping, otherwise will create errors such as: * [scaladoc] ../scala-trunk/src/reflect/scala/reflect/macros/Reifiers.scala:89: error: object api is not a member of package reflect * [scaladoc] case class ReificationException(val pos: reflect.api.PositionApi, val msg: String) extends Throwable(msg) @@ -220,7 +222,9 @@ class PathResolver(implicit ctx: Context) { import context._ // Assemble the elements! + // priority class path takes precedence def basis = List[Traversable[ClassPath]]( + classesInExpandedPath(priorityClassPath), // 0. The priority class path (for testing). classesInPath(javaBootClassPath), // 1. The Java bootstrap class path. contentsOfDirsInPath(javaExtDirs), // 2. The Java extension class path. classesInExpandedPath(javaUserClassPath), // 3. The Java application class path. @@ -235,6 +239,7 @@ class PathResolver(implicit ctx: Context) { override def toString = """ |object Calculated { | scalaHome = %s + | priorityClassPath = %s | javaBootClassPath = %s | javaExtDirs = %s | javaUserClassPath = %s @@ -244,7 +249,7 @@ class PathResolver(implicit ctx: Context) { | userClassPath = %s | sourcePath = %s |}""".trim.stripMargin.format( - scalaHome, + scalaHome, ppcp(priorityClassPath), ppcp(javaBootClassPath), ppcp(javaExtDirs), ppcp(javaUserClassPath), useJavaClassPath, ppcp(scalaBootClassPath), ppcp(scalaExtDirs), ppcp(userClassPath), diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index ff17a9939..8f47e08bf 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -15,6 +15,10 @@ class ScalaSettings extends Settings.SettingGroup { val javabootclasspath = PathSetting("-javabootclasspath", "Override java boot classpath.", Defaults.javaBootClassPath) val javaextdirs = PathSetting("-javaextdirs", "Override java extdirs classpath.", Defaults.javaExtDirs) val sourcepath = PathSetting("-sourcepath", "Specify location(s) of source files.", "") // Defaults.scalaSourcePath + val argfiles = BooleanSetting("@<file>", "A text file containing compiler arguments (options and source files)") + val classpath = PathSetting("-classpath", "Specify where to find user class files.", defaultClasspath) withAbbreviation "-cp" + val d = StringSetting("-d", "directory|jar", "destination for generated classfiles.", ".") + val priorityclasspath = PathSetting("-priorityclasspath", "class path that takes precedence over all other paths (or testing only)", "") /** Other settings. */ @@ -23,6 +27,7 @@ class ScalaSettings extends Settings.SettingGroup { val migration = BooleanSetting("-migration", "Emit warning and location for migration issues from Scala 2.") val encoding = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding) val explaintypes = BooleanSetting("-explaintypes", "Explain type errors in more detail.") + val explain = BooleanSetting("-explain", "Explain errors in more detail.") val feature = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.") val g = ChoiceSetting("-g", "level", "Set level of generated debugging info.", List("none", "source", "line", "vars", "notailcalls"), "vars") val help = BooleanSetting("-help", "Print a synopsis of standard options") @@ -45,9 +50,6 @@ class ScalaSettings extends Settings.SettingGroup { val nobootcp = BooleanSetting("-nobootcp", "Do not use the boot classpath for the scala jars.") val strict = BooleanSetting("-strict", "Use strict type rules, which means some formerly legal code does not typecheck anymore.") - val argfiles = BooleanSetting("@<file>", "A text file containing compiler arguments (options and source files)") - val classpath = PathSetting("-classpath", "Specify where to find user class files.", defaultClasspath) withAbbreviation "-cp" - val d = StringSetting("-d", "directory|jar", "destination for generated classfiles.", ".") val nospecialization = BooleanSetting("-no-specialization", "Ignore @specialize annotations.") val language = MultiStringSetting("-language", "feature", "Enable one or more language features.") val rewrite = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with -language:Scala2 rewrites sources to migrate to new syntax") diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index 91e70b7b5..c99b748b7 100644 --- a/src/dotty/tools/dotc/core/Constraint.scala +++ b/src/dotty/tools/dotc/core/Constraint.scala @@ -23,7 +23,7 @@ abstract class Constraint extends Showable { type This <: Constraint /** Does the constraint's domain contain the type parameters of `pt`? */ - def contains(pt: GenericType): Boolean + def contains(pt: PolyType): Boolean /** Does the constraint's domain contain the type parameter `param`? */ def contains(param: PolyParam): Boolean @@ -79,7 +79,7 @@ abstract class Constraint extends Showable { * satisfiability but will solved to give instances of * type variables. */ - def add(poly: GenericType, tvars: List[TypeVar])(implicit ctx: Context): This + 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`. @@ -121,13 +121,13 @@ abstract class Constraint extends Showable { * all type parameters of the entry are associated with type variables * which have their `inst` fields set. */ - def isRemovable(pt: GenericType): Boolean + def isRemovable(pt: PolyType): Boolean /** A new constraint with all entries coming from `pt` removed. */ - def remove(pt: GenericType)(implicit ctx: Context): This + def remove(pt: PolyType)(implicit ctx: Context): This /** The polytypes constrained by this constraint */ - def domainPolys: List[GenericType] + def domainPolys: List[PolyType] /** The polytype parameters constrained by this constraint */ def domainParams: List[PolyParam] diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 5911af72c..3835d553c 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -35,10 +35,19 @@ trait ConstraintHandling { /** If the constraint is frozen we cannot add new bounds to the constraint. */ protected var frozenConstraint = false - /** We are currently comparing lambdas. Used as a flag for + 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 comparingLambdas = false + protected var comparedPolyTypes: Set[PolyType] = Set.empty private def addOneBound(param: PolyParam, bound: Type, isUpper: Boolean): Boolean = !constraint.contains(param) || { @@ -126,14 +135,14 @@ trait ConstraintHandling { final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { val saved = frozenConstraint - frozenConstraint = true + frozenConstraint = !alwaysFluid try isSubType(tp1, tp2) finally frozenConstraint = saved } final def isSameTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { val saved = frozenConstraint - frozenConstraint = true + frozenConstraint = !alwaysFluid try isSameType(tp1, tp2) finally frozenConstraint = saved } @@ -219,7 +228,7 @@ trait ConstraintHandling { // 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 = inst.approximateUnion + 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. @@ -307,12 +316,12 @@ trait ConstraintHandling { * missing. */ def pruneLambdaParams(tp: Type) = - if (comparingLambdas && param.binder.isInstanceOf[PolyType]) { + if (comparedPolyTypes.nonEmpty) { val approx = new ApproximatingTypeMap { def apply(t: Type): Type = t match { - case t @ PolyParam(tl: TypeLambda, n) => + case t @ PolyParam(pt: PolyType, n) if comparedPolyTypes contains pt => val effectiveVariance = if (fromBelow) -variance else variance - val bounds = tl.paramBounds(n) + val bounds = pt.paramBounds(n) if (effectiveVariance > 0) bounds.lo else if (effectiveVariance < 0) bounds.hi else NoType diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index 5c9fdaf88..edc68588d 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -132,7 +132,7 @@ object Contexts { def compilationUnit: CompilationUnit = _compilationUnit /** The current tree */ - private[this] var _tree: Tree[_ >: Untyped] = _ + private[this] var _tree: Tree[_ >: Untyped]= _ protected def tree_=(tree: Tree[_ >: Untyped]) = _tree = tree def tree: Tree[_ >: Untyped] = _tree diff --git a/src/dotty/tools/dotc/core/Decorators.scala b/src/dotty/tools/dotc/core/Decorators.scala index 3bf17730a..b0f1f0c98 100644 --- a/src/dotty/tools/dotc/core/Decorators.scala +++ b/src/dotty/tools/dotc/core/Decorators.scala @@ -176,6 +176,10 @@ object Decorators { */ 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) } } diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 75b75d3d5..50746c61d 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -92,17 +92,17 @@ class Definitions { } private def newPolyMethod(cls: ClassSymbol, name: TermName, typeParamCount: Int, - resultTypeFn: GenericType => Type, flags: FlagSet = EmptyFlags) = { + 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: GenericType => Type, flags: FlagSet) = + 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: GenericType => Type, flags: FlagSet) = + 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] = { @@ -430,10 +430,8 @@ class Definitions { 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("dotty.language") + lazy val LanguageModuleRef = ctx.requiredModule("scala.language") def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass - lazy val Scala2LanguageModuleRef = ctx.requiredModule("scala.language") - def Scala2LanguageModuleClass(implicit ctx: Context) = Scala2LanguageModuleRef.symbol.moduleClass.asClass lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl") lazy val ClassTagType = ctx.requiredClassRef("scala.reflect.ClassTag") diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 4f01c43cf..7866d6697 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -6,6 +6,7 @@ import SymDenotations.{ SymDenotation, ClassDenotation, NoDenotation, NotDefined import Contexts.{Context, ContextBase} import Names.{Name, PreName} import Names.TypeName +import StdNames._ import Symbols.NoSymbol import Symbols._ import Types._ @@ -247,6 +248,25 @@ object Denotations { 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 @@ -277,6 +297,50 @@ object Denotations { */ 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) => @@ -289,95 +353,95 @@ object Denotations { } case denot1: SingleDenotation => if (denot1 eq denot2) denot1 - else if (denot1.matches(denot2)) { - 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)) - } + else if (denot1.matches(denot2)) mergeSingleDenot(denot1, denot2) + else NoDenotation + } - /** 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)) - - /** 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 - if (safeIntersection) - info1 safe_& info2 - else - 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) + /** 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) } - } - } else NoDenotation + new JointRefDenotation(sym, jointInfo, denot1.validFor & denot2.validFor) + } + } } if (this eq that) this @@ -398,6 +462,46 @@ object Denotations { */ 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 @@ -427,7 +531,8 @@ object Denotations { } lubSym(sym1.allOverriddenSymbols, NoSymbol) } - new JointRefDenotation(jointSym, info1 | info2, denot1.validFor & denot2.validFor) + new JointRefDenotation( + jointSym, infoJoin(info1, info2), denot1.validFor & denot2.validFor) } } else NoDenotation @@ -1133,5 +1238,4 @@ object Denotations { util.Stats.record("not defined here") override def getMessage() = msg } -} - +}
\ No newline at end of file diff --git a/src/dotty/tools/dotc/core/OrderingConstraint.scala b/src/dotty/tools/dotc/core/OrderingConstraint.scala index 458f8b82f..72c7a8e51 100644 --- a/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -14,7 +14,7 @@ import annotation.tailrec object OrderingConstraint { - type ArrayValuedMap[T] = SimpleMap[GenericType, Array[T]] + type ArrayValuedMap[T] = SimpleMap[PolyType, Array[T]] /** The type of `OrderingConstraint#boundsMap` */ type ParamBounds = ArrayValuedMap[Type] @@ -32,11 +32,11 @@ object OrderingConstraint { /** A lens for updating a single entry array in one of the three constraint maps */ abstract class ConstraintLens[T <: AnyRef: ClassTag] { - def entries(c: OrderingConstraint, poly: GenericType): Array[T] - def updateEntries(c: OrderingConstraint, poly: GenericType, entries: Array[T])(implicit ctx: Context): OrderingConstraint + def entries(c: OrderingConstraint, poly: PolyType): Array[T] + def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[T])(implicit ctx: Context): OrderingConstraint def initial: T - def apply(c: OrderingConstraint, poly: GenericType, idx: Int) = { + def apply(c: OrderingConstraint, poly: PolyType, idx: Int) = { val es = entries(c, poly) if (es == null) initial else es(idx) } @@ -47,7 +47,7 @@ object OrderingConstraint { * parts of `current` which are not shared by `prev`. */ def update(prev: OrderingConstraint, current: OrderingConstraint, - poly: GenericType, idx: Int, entry: T)(implicit ctx: Context): OrderingConstraint = { + poly: PolyType, idx: Int, entry: T)(implicit ctx: Context): OrderingConstraint = { var es = entries(current, poly) if (es != null && (es(idx) eq entry)) current else { @@ -72,7 +72,7 @@ object OrderingConstraint { update(prev, current, param.binder, param.paramNum, entry) def map(prev: OrderingConstraint, current: OrderingConstraint, - poly: GenericType, idx: Int, f: T => T)(implicit ctx: Context): OrderingConstraint = + poly: PolyType, idx: Int, f: T => T)(implicit ctx: Context): OrderingConstraint = update(prev, current, poly, idx, f(apply(current, poly, idx))) def map(prev: OrderingConstraint, current: OrderingConstraint, @@ -81,25 +81,25 @@ object OrderingConstraint { } val boundsLens = new ConstraintLens[Type] { - def entries(c: OrderingConstraint, poly: GenericType): Array[Type] = + def entries(c: OrderingConstraint, poly: PolyType): Array[Type] = c.boundsMap(poly) - def updateEntries(c: OrderingConstraint, poly: GenericType, entries: Array[Type])(implicit ctx: Context): OrderingConstraint = + def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[Type])(implicit ctx: Context): OrderingConstraint = newConstraint(c.boundsMap.updated(poly, entries), c.lowerMap, c.upperMap) def initial = NoType } val lowerLens = new ConstraintLens[List[PolyParam]] { - def entries(c: OrderingConstraint, poly: GenericType): Array[List[PolyParam]] = + def entries(c: OrderingConstraint, poly: PolyType): Array[List[PolyParam]] = c.lowerMap(poly) - def updateEntries(c: OrderingConstraint, poly: GenericType, entries: Array[List[PolyParam]])(implicit ctx: Context): OrderingConstraint = + def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[List[PolyParam]])(implicit ctx: Context): OrderingConstraint = newConstraint(c.boundsMap, c.lowerMap.updated(poly, entries), c.upperMap) def initial = Nil } val upperLens = new ConstraintLens[List[PolyParam]] { - def entries(c: OrderingConstraint, poly: GenericType): Array[List[PolyParam]] = + def entries(c: OrderingConstraint, poly: PolyType): Array[List[PolyParam]] = c.upperMap(poly) - def updateEntries(c: OrderingConstraint, poly: GenericType, entries: Array[List[PolyParam]])(implicit ctx: Context): OrderingConstraint = + def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[List[PolyParam]])(implicit ctx: Context): OrderingConstraint = newConstraint(c.boundsMap, c.lowerMap, c.upperMap.updated(poly, entries)) def initial = Nil } @@ -149,7 +149,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, // ----------- Contains tests -------------------------------------------------- - def contains(pt: GenericType): Boolean = boundsMap(pt) != null + def contains(pt: PolyType): Boolean = boundsMap(pt) != null def contains(param: PolyParam): Boolean = { val entries = boundsMap(param.binder) @@ -280,7 +280,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, stripParams(tp, paramBuf, isUpper) .orElse(if (isUpper) defn.AnyType else defn.NothingType) - def add(poly: GenericType, tvars: List[TypeVar])(implicit ctx: Context): This = { + def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This = { assert(!contains(poly)) val nparams = poly.paramNames.length val entries1 = new Array[Type](nparams * 2) @@ -293,7 +293,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, * Update all bounds to be normalized and update ordering to account for * dependent parameters. */ - private def init(poly: GenericType)(implicit ctx: Context): This = { + private def init(poly: PolyType)(implicit ctx: Context): This = { var current = this val loBuf, hiBuf = new mutable.ListBuffer[PolyParam] var i = 0 @@ -393,14 +393,14 @@ class OrderingConstraint(private val boundsMap: ParamBounds, val replacement = tp.dealias.stripTypeVar if (param == replacement) this else { - assert(replacement.isValueType) + assert(replacement.isValueTypeOrLambda) val poly = param.binder val idx = param.paramNum def removeParam(ps: List[PolyParam]) = ps.filterNot(p => p.binder.eq(poly) && p.paramNum == idx) - def replaceParam(tp: Type, atPoly: GenericType, atIdx: Int): Type = tp match { + def replaceParam(tp: Type, atPoly: PolyType, atIdx: Int): Type = tp match { case bounds @ TypeBounds(lo, hi) => def recombine(andor: AndOrType, op: (Type, Boolean) => Type, isUpper: Boolean): Type = { @@ -440,9 +440,9 @@ class OrderingConstraint(private val boundsMap: ParamBounds, } } - def remove(pt: GenericType)(implicit ctx: Context): This = { + def remove(pt: PolyType)(implicit ctx: Context): This = { def removeFromOrdering(po: ParamOrdering) = { - def removeFromBoundss(key: GenericType, bndss: Array[List[PolyParam]]): Array[List[PolyParam]] = { + def removeFromBoundss(key: PolyType, bndss: Array[List[PolyParam]]): Array[List[PolyParam]] = { val bndss1 = bndss.map(_.filterConserve(_.binder ne pt)) if (bndss.corresponds(bndss1)(_ eq _)) bndss else bndss1 } @@ -451,7 +451,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, newConstraint(boundsMap.remove(pt), removeFromOrdering(lowerMap), removeFromOrdering(upperMap)) } - def isRemovable(pt: GenericType): Boolean = { + def isRemovable(pt: PolyType): Boolean = { val entries = boundsMap(pt) @tailrec def allRemovable(last: Int): Boolean = if (last < 0) true @@ -464,7 +464,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, // ---------- Exploration -------------------------------------------------------- - def domainPolys: List[GenericType] = boundsMap.keys + def domainPolys: List[PolyType] = boundsMap.keys def domainParams: List[PolyParam] = for { @@ -481,7 +481,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, true } - def foreachParam(p: (GenericType, Int) => Unit): Unit = + def foreachParam(p: (PolyType, Int) => Unit): Unit = boundsMap.foreachBinding { (poly, entries) => 0.until(poly.paramNames.length).foreach(p(poly, _)) } @@ -521,6 +521,11 @@ class OrderingConstraint(private val boundsMap: ParamBounds, case _ if e1 contains e2 => e2 case _ => mergeError } + case tv1: TypeVar => + e2 match { + case tv2: TypeVar if tv1.instanceOpt eq tv2.instanceOpt => e1 + case _ => mergeError + } case _ if e1 eq e2 => e1 case _ => mergeError } @@ -536,7 +541,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, override def checkClosed()(implicit ctx: Context): Unit = { def isFreePolyParam(tp: Type) = tp match { - case PolyParam(binder: GenericType, _) => !contains(binder) + case PolyParam(binder: PolyType, _) => !contains(binder) case _ => false } def checkClosedType(tp: Type, where: String) = diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index c52264637..920c9635e 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -429,7 +429,6 @@ object StdNames { val isEmpty: N = "isEmpty" val isInstanceOf_ : N = "isInstanceOf" val java: N = "java" - val keepUnions: N = "keepUnions" val key: N = "key" val lang: N = "lang" val length: N = "length" diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 5a5eacd18..a98d6732a 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -41,7 +41,7 @@ trait SymDenotations { this: Context => } def stillValid(denot: SymDenotation): Boolean = - if (denot.is(ValidForever) || denot.isRefinementClass) true + if (denot.is(ValidForever) || denot.isRefinementClass || denot.isImport) true else { val initial = denot.initial val firstPhaseId = initial.validFor.firstPhaseId.max(ctx.typerPhase.id) @@ -590,6 +590,9 @@ object SymDenotations { originalName.isSetterName && (!isCompleted || info.firstParamTypes.nonEmpty) // to avoid being fooled by var x_= : Unit = ... + /** is this a symbol representing an import? */ + final def isImport = name == nme.IMPORT + /** is this the constructor of a class? */ final def isClassConstructor = name == nme.CONSTRUCTOR @@ -1147,7 +1150,7 @@ object SymDenotations { case tp: NamedType => hasSkolems(tp.prefix) case tp: RefinedType => hasSkolems(tp.parent) || hasSkolems(tp.refinedInfo) case tp: RecType => hasSkolems(tp.parent) - case tp: GenericType => tp.paramBounds.exists(hasSkolems) || hasSkolems(tp.resType) + case tp: PolyType => tp.paramBounds.exists(hasSkolems) || hasSkolems(tp.resType) case tp: MethodType => tp.paramTypes.exists(hasSkolems) || hasSkolems(tp.resType) case tp: ExprType => hasSkolems(tp.resType) case tp: HKApply => hasSkolems(tp.tycon) || tp.args.exists(hasSkolems) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index f32a591a6..8aaf77032 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -72,7 +72,7 @@ object TypeApplications { } def unapply(tp: Type)(implicit ctx: Context): Option[TypeRef] = tp match { - case tp @ TypeLambda(tparams, AppliedType(fn: TypeRef, args)) if (args == tparams.map(_.toArg)) => Some(fn) + case tp @ PolyType(tparams, AppliedType(fn: TypeRef, args)) if (args == tparams.map(_.toArg)) => Some(fn) case _ => None } } @@ -159,7 +159,7 @@ object TypeApplications { * result type. Using this mode, we can guarantee that `appliedTo` will never * produce a higher-kinded application with a type lambda as type constructor. */ - class Reducer(tycon: TypeLambda, args: List[Type])(implicit ctx: Context) extends TypeMap { + class Reducer(tycon: PolyType, args: List[Type])(implicit ctx: Context) extends TypeMap { private var available = (0 until args.length).toSet var allReplaced = true def hasWildcardArg(p: PolyParam) = @@ -212,7 +212,7 @@ class TypeApplications(val self: Type) extends AnyVal { self match { case self: ClassInfo => self.cls.typeParams - case self: TypeLambda => + case self: PolyType => self.typeParams case self: TypeRef => val tsym = self.symbol @@ -311,7 +311,7 @@ class TypeApplications(val self: Type) extends AnyVal { def isHK(implicit ctx: Context): Boolean = self.dealias match { case self: TypeRef => self.info.isHK case self: RefinedType => false - case self: TypeLambda => true + case self: PolyType => true case self: SingletonType => false case self: TypeVar => // Using `origin` instead of `underlying`, as is done for typeParams, @@ -339,7 +339,7 @@ class TypeApplications(val self: Type) extends AnyVal { */ def LambdaAbstract(tparams: List[TypeParamInfo])(implicit ctx: Context): Type = { def expand(tp: Type) = - TypeLambda( + PolyType( tpnme.syntheticLambdaParamNames(tparams.length), tparams.map(_.paramVariance))( tl => tparams.map(tparam => tl.lifted(tparams, tparam.paramBounds).bounds), tl => tl.lifted(tparams, tp)) @@ -421,10 +421,10 @@ class TypeApplications(val self: Type) extends AnyVal { if (hkParams.isEmpty) self else { def adaptArg(arg: Type): Type = arg match { - case arg @ TypeLambda(tparams, body) if + case arg @ PolyType(tparams, body) if !tparams.corresponds(hkParams)(_.paramVariance == _.paramVariance) && tparams.corresponds(hkParams)(varianceConforms) => - TypeLambda(tparams.map(_.paramName), hkParams.map(_.paramVariance))( + PolyType(tparams.map(_.paramName), hkParams.map(_.paramVariance))( tl => arg.paramBounds.map(_.subst(arg, tl).bounds), tl => arg.resultType.subst(arg, tl) ) @@ -466,7 +466,7 @@ class TypeApplications(val self: Type) extends AnyVal { val dealiased = stripped.safeDealias if (args.isEmpty || ctx.erasedTypes) self else dealiased match { - case dealiased: TypeLambda => + case dealiased: PolyType => def tryReduce = if (!args.exists(_.isInstanceOf[TypeBounds])) { val followAlias = Config.simplifyApplications && { @@ -485,7 +485,7 @@ class TypeApplications(val self: Type) extends AnyVal { // In this case we should always dealias since we cannot handle // higher-kinded applications to wildcard arguments. dealiased - .derivedTypeLambda(resType = tycon.safeDealias.appliedTo(args1)) + .derivedPolyType(resType = tycon.safeDealias.appliedTo(args1)) .appliedTo(args) case _ => val reducer = new Reducer(dealiased, args) @@ -494,8 +494,6 @@ class TypeApplications(val self: Type) extends AnyVal { else HKApply(dealiased, args) } tryReduce - case dealiased: PolyType => - dealiased.instantiate(args) case dealiased: AndOrType => dealiased.derivedAndOrType(dealiased.tp1.appliedTo(args), dealiased.tp2.appliedTo(args)) case dealiased: TypeAlias => diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 991dd2664..1980fe50d 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -8,7 +8,7 @@ import StdNames.{nme, tpnme} import collection.mutable import util.{Stats, DotClass, SimpleMap} import config.Config -import config.Printers.{typr, constr, subtyping} +import config.Printers.{typr, constr, subtyping, noPrinter} import TypeErasure.{erasedLub, erasedGlb} import TypeApplications._ import scala.util.control.NonFatal @@ -324,8 +324,18 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case AndType(tp11, tp12) => if (tp11.stripTypeVar eq tp12.stripTypeVar) isSubType(tp11, tp2) else thirdTry(tp1, tp2) - case OrType(tp11, tp12) => - isSubType(tp11, tp2) && isSubType(tp12, tp2) + case tp1 @ OrType(tp11, tp12) => + def joinOK = tp2.dealias match { + case tp12: HKApply => + // If we apply the default algorithm for `A[X] | B[Y] <: C[Z]` where `C` is a + // type parameter, we will instantiate `C` to `A` and then fail when comparing + // with `B[Y]`. To do the right thing, we need to instantiate `C` to the + // common superclass of `A` and `B`. + isSubType(tp1.join, tp2) + case _ => + false + } + joinOK || isSubType(tp11, tp2) && isSubType(tp12, tp2) case ErrorType => true case _ => @@ -402,9 +412,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { compareRec case tp2 @ HKApply(tycon2, args2) => compareHkApply2(tp1, tp2, tycon2, args2) - case tp2 @ TypeLambda(tparams2, body2) => + case tp2 @ PolyType(tparams2, body2) => def compareHkLambda: Boolean = tp1.stripTypeVar match { - case tp1 @ TypeLambda(tparams1, body1) => + case tp1 @ PolyType(tparams1, body1) => /* Don't compare bounds of lambdas under language:Scala2, or t2994 will fail * The issue is that, logically, bounds should compare contravariantly, * but that would invalidate a pattern exploited in t2994: @@ -422,13 +432,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { ctx.scala2Mode || tparams1.corresponds(tparams2)((tparam1, tparam2) => isSubType(tparam2.paramBounds.subst(tp2, tp1), tparam1.paramBounds)) - val saved = comparingLambdas - comparingLambdas = true + val saved = comparedPolyTypes + comparedPolyTypes += tp1 + comparedPolyTypes += tp2 try variancesConform(tparams1, tparams2) && boundsOK && isSubType(body1, body2.subst(tp2, tp1)) - finally comparingLambdas = saved + finally comparedPolyTypes = saved case _ => if (!tp1.isHK) { tp2 match { @@ -468,16 +479,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { false } compareMethod - case tp2: PolyType => - def comparePoly = tp1 match { - case tp1: PolyType => - (tp1.signature consistentParams tp2.signature) && - matchingTypeParams(tp1, tp2) && - isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) - case _ => - false - } - comparePoly case tp2 @ ExprType(restpe2) => def compareExpr = tp1 match { // We allow ()T to be a subtype of => T. @@ -533,7 +534,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) case _ => false } - (tp1.symbol eq NothingClass) && tp2.isInstanceOf[ValueType] || + (tp1.symbol eq NothingClass) && tp2.isValueTypeOrLambda || (tp1.symbol eq NullClass) && isNullable(tp2) } case tp1: SingletonType => @@ -650,7 +651,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { val tparams1 = tparams1a.drop(lengthDiff) variancesConform(tparams1, tparams) && { if (lengthDiff > 0) - tycon1b = TypeLambda(tparams1.map(_.paramName), tparams1.map(_.paramVariance))( + tycon1b = PolyType(tparams1.map(_.paramName), tparams1.map(_.paramVariance))( tl => tparams1.map(tparam => tl.lifted(tparams, tparam.paramBounds).bounds), tl => tycon1a.appliedTo(args1.take(lengthDiff) ++ tparams1.indices.toList.map(PolyParam(tl, _)))) @@ -827,8 +828,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { op1 && { val leftConstraint = constraint constraint = preConstraint - if (!(op2 && subsumes(leftConstraint, constraint, preConstraint))) + if (!(op2 && subsumes(leftConstraint, constraint, preConstraint))) { + if (constr != noPrinter && !subsumes(constraint, leftConstraint, preConstraint)) + constr.println(i"CUT - prefer $leftConstraint over $constraint") constraint = leftConstraint + } true } || op2 } @@ -961,28 +965,29 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * Test that the resulting bounds are still satisfiable. */ private def narrowGADTBounds(tr: NamedType, bound: Type, isUpper: Boolean): Boolean = - ctx.mode.is(Mode.GADTflexible) && { - val tparam = tr.symbol - typr.println(i"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.isRef(tparam)}") - if (bound.isRef(tparam)) false - else bound match { - case bound: TypeRef - if bound.symbol.is(BindDefinedType) && ctx.gadt.bounds.contains(bound.symbol) && - !tr.symbol.is(BindDefinedType) => - // Avoid having pattern-bound types in gadt bounds, - // as these might be eliminated once the pattern is typechecked. - // Pattern-bound type symbols should be narrowed first, only if that fails - // should symbols in the environment be constrained. - narrowGADTBounds(bound, tr, !isUpper) - case _ => - val oldBounds = ctx.gadt.bounds(tparam) - val newBounds = - if (isUpper) TypeBounds(oldBounds.lo, oldBounds.hi & bound) - else TypeBounds(oldBounds.lo | bound, oldBounds.hi) - isSubType(newBounds.lo, newBounds.hi) && - { ctx.gadt.setBounds(tparam, newBounds); true } + ctx.mode.is(Mode.GADTflexible) && !frozenConstraint && { + val tparam = tr.symbol + typr.println(i"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.isRef(tparam)}") + if (bound.isRef(tparam)) false + else bound match { + case bound: TypeRef + if bound.symbol.is(BindDefinedType) && + ctx.gadt.bounds.contains(bound.symbol) && + !tr.symbol.is(BindDefinedType) => + // Avoid having pattern-bound types in gadt bounds, + // as these might be eliminated once the pattern is typechecked. + // Pattern-bound type symbols should be narrowed first, only if that fails + // should symbols in the environment be constrained. + narrowGADTBounds(bound, tr, !isUpper) + case _ => + val oldBounds = ctx.gadt.bounds(tparam) + val newBounds = + if (isUpper) TypeBounds(oldBounds.lo, oldBounds.hi & bound) + else TypeBounds(oldBounds.lo | bound, oldBounds.hi) + isSubType(newBounds.lo, newBounds.hi) && + { ctx.gadt.setBounds(tparam, newBounds); true } + } } - } // Tests around `matches` @@ -1019,7 +1024,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } /** Are `syms1` and `syms2` parameter lists with pairwise equivalent types? */ - private def matchingParams(formals1: List[Type], formals2: List[Type], isJava1: Boolean, isJava2: Boolean): Boolean = formals1 match { + def matchingParams(formals1: List[Type], formals2: List[Type], isJava1: Boolean, isJava2: Boolean): Boolean = formals1 match { case formal1 :: rest1 => formals2 match { case formal2 :: rest2 => @@ -1037,7 +1042,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { /** Do generic types `poly1` and `poly2` have type parameters that * have the same bounds (after renaming one set to the other)? */ - private def matchingTypeParams(poly1: GenericType, poly2: GenericType): Boolean = + def matchingTypeParams(poly1: PolyType, poly2: PolyType): Boolean = (poly1.paramBounds corresponds poly2.paramBounds)((b1, b2) => isSameType(b1, b2.subst(poly2, poly1))) @@ -1261,7 +1266,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { else if (tparams2.isEmpty) original(tp1.appliedTo(tp1.typeParams.map(_.paramBoundsAsSeenFrom(tp1))), tp2) else - TypeLambda( + PolyType( paramNames = tpnme.syntheticLambdaParamNames(tparams1.length), variances = (tparams1, tparams2).zipped.map((tparam1, tparam2) => (tparam1.paramVariance + tparam2.paramVariance) / 2))( @@ -1305,38 +1310,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } case tp1: RecType => tp1.rebind(distributeAnd(tp1.parent, tp2)) - case tp1: TypeBounds => - tp2 match { - case tp2: TypeBounds => 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) => - tp2 match { - case tp2 @ MethodType(names2, formals2) - if 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 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 ExprType(rt1) => tp2 match { case ExprType(rt2) => @@ -1361,38 +1334,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * The rhs is a proper supertype of the lhs. */ private def distributeOr(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 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: GenericType => - tp2 match { - case tp2: GenericType if matchingTypeParams(tp1, tp2) => - tp1.derivedGenericType( - mergeNames(tp1.paramNames, tp2.paramNames, tpnme.syntheticTypeParamName), - tp1.paramBounds, tp1.resultType | tp2.resultType.subst(tp2, tp1)) - case _ => - mergeConflict(tp1, tp2) - } case ExprType(rt1) => ExprType(rt1 | tp2.widenExpr) case tp1: TypeVar if tp1.isInstantiated => @@ -1403,25 +1344,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { NoType } - /** Handle merge conflict by throwing a `MergeError` exception */ - private def mergeConflict(tp1: Type, tp2: Type): 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. - */ - private 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 - /** Show type, handling type types better than the default */ private def showType(tp: Type)(implicit ctx: Context) = tp match { case ClassInfo(_, cls, _, _, _) => cls.showLocated diff --git a/src/dotty/tools/dotc/core/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala index 1a7342a12..254ea3277 100644 --- a/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/src/dotty/tools/dotc/core/TypeErasure.scala @@ -356,8 +356,6 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean SuperType(this(thistpe), this(supertpe)) case ExprType(rt) => defn.FunctionClass(0).typeRef - case tp: TypeProxy => - this(tp.underlying) case AndType(tp1, tp2) => erasedGlb(this(tp1), this(tp2), isJava) case OrType(tp1, tp2) => @@ -372,11 +370,6 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean case rt => tp.derivedMethodType(tp.paramNames, formals, rt) } - case tp: PolyType => - this(tp.resultType) match { - case rt: MethodType => rt - case rt => MethodType(Nil, Nil, rt) - } case tp @ ClassInfo(pre, cls, classParents, decls, _) => if (cls is Package) tp else { @@ -398,6 +391,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean tp case tp: WildcardType if wildcardOK => tp + case tp: TypeProxy => + this(tp.underlying) } private def eraseArray(tp: RefinedType)(implicit ctx: Context) = { @@ -409,9 +404,9 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean else JavaArrayType(arrayErasure(elemtp)) } - /** The erasure of a symbol's info. This is different from `apply` in the way `ExprType`s are - * treated. `eraseInfo` maps them them to nullary method types, whereas `apply` maps them - * to `Function0`. + /** The erasure of a symbol's info. This is different from `apply` in the way `ExprType`s and + * `PolyType`s are treated. `eraseInfo` maps them them to method types, whereas `apply` maps them + * to the underlying type. */ def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { case ExprType(rt) => @@ -421,6 +416,11 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean // forwarders to mixin methods. // See doc comment for ElimByName for speculation how we could improve this. else MethodType(Nil, Nil, eraseResult(rt)) + case tp: PolyType => + eraseResult(tp.resultType) match { + case rt: MethodType => rt + case rt => MethodType(Nil, Nil, rt) + } case tp => this(tp) } diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 5ba9a3351..92e5f9d57 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -36,7 +36,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. * Instead we produce an annotated type that marks the prefix as unsafe: * * (x: (C @ UnsafeNonvariant)#T)C#T - + * * We also set a global state flag `unsafeNonvariant` to the current run. * When typing a Select node, typer will check that flag, and if it * points to the current run will scan the result type of the select for @@ -136,7 +136,6 @@ trait TypeOps { this: Context => // TODO: Make standalone object. finally seen = saved } case _ => - if (tp.isInstanceOf[MethodicType]) assert(variance != 0, tp) mapOver(tp) } } @@ -173,21 +172,53 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } /** Approximate union type by intersection of its dominators. - * See Type#approximateUnion for an explanation. + * That is, replace a union type Tn | ... | Tn + * by the smallest intersection type of base-class instances of T1,...,Tn. + * Example: Given + * + * trait C[+T] + * trait D + * class A extends C[A] with D + * class B extends C[B] with D with E + * + * we approximate `A | B` by `C[A | B] with D` */ - def approximateUnion(tp: Type): Type = { + def orDominator(tp: Type): Type = { + /** a faster version of cs1 intersect cs2 */ def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] = { val cs2AsSet = new util.HashSet[ClassSymbol](100) cs2.foreach(cs2AsSet.addEntry) cs1.filter(cs2AsSet.contains) } + /** The minimal set of classes in `cs` which derive all other classes in `cs` */ def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = (cs: @unchecked) match { case c :: rest => val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu if (cs == c.baseClasses) accu1 else dominators(rest, accu1) } + + def mergeRefined(tp1: Type, tp2: Type): Type = { + def fail = throw new AssertionError(i"Failure to join alternatives $tp1 and $tp2") + tp1 match { + case tp1 @ RefinedType(parent1, name1, rinfo1) => + tp2 match { + case RefinedType(parent2, `name1`, rinfo2) => + tp1.derivedRefinedType( + mergeRefined(parent1, parent2), name1, rinfo1 | rinfo2) + case _ => fail + } + case tp1 @ TypeRef(pre1, name1) => + tp2 match { + case tp2 @ TypeRef(pre2, `name1`) => + tp1.derivedSelect(pre1 | pre2) + case _ => fail + } + case _ => fail + } + } + def approximateOr(tp1: Type, tp2: Type): Type = { def isClassRef(tp: Type): Boolean = tp match { case tp: TypeRef => tp.symbol.isClass @@ -195,78 +226,70 @@ trait TypeOps { this: Context => // TODO: Make standalone object. case _ => false } - /** If `tp1` and `tp2` are typebounds, try to make one fit into the other - * or to make them equal, by instantiating uninstantiated type variables. - */ - def homogenizedUnion(tp1: Type, tp2: Type): Type = { - tp1 match { - case tp1: TypeBounds => - tp2 match { - case tp2: TypeBounds => - def fitInto(tp1: TypeBounds, tp2: TypeBounds): Unit = { - val nestedCtx = ctx.fresh.setNewTyperState - if (tp2.boundsInterval.contains(tp1.boundsInterval)(nestedCtx)) - nestedCtx.typerState.commit() - } - fitInto(tp1, tp2) - fitInto(tp2, tp1) - case _ => - } - case _ => - } - tp1 | tp2 - } - - tp1 match { - case tp1: RefinedType => - tp2 match { - case tp2: RefinedType if tp1.refinedName == tp2.refinedName => - return tp1.derivedRefinedType( - approximateUnion(OrType(tp1.parent, tp2.parent)), - tp1.refinedName, - homogenizedUnion(tp1.refinedInfo, tp2.refinedInfo)) - //.ensuring { x => println(i"approx or $tp1 | $tp2 = $x\n constr = ${ctx.typerState.constraint}"); true } // DEBUG - case _ => - } - case _ => - } - tp1 match { case tp1: RecType => tp1.rebind(approximateOr(tp1.parent, tp2)) case tp1: TypeProxy if !isClassRef(tp1) => - approximateUnion(tp1.superType | tp2) + orDominator(tp1.superType | tp2) case _ => tp2 match { case tp2: RecType => tp2.rebind(approximateOr(tp1, tp2.parent)) case tp2: TypeProxy if !isClassRef(tp2) => - approximateUnion(tp1 | tp2.superType) + orDominator(tp1 | tp2.superType) case _ => val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect) val doms = dominators(commonBaseClasses, Nil) - def baseTp(cls: ClassSymbol): Type = - if (tp1.typeParams.nonEmpty) tp.baseTypeRef(cls) - else tp.baseTypeWithArgs(cls) + def baseTp(cls: ClassSymbol): Type = { + val base = + if (tp1.typeParams.nonEmpty) tp.baseTypeRef(cls) + else tp.baseTypeWithArgs(cls) + base.mapReduceOr(identity)(mergeRefined) + } doms.map(baseTp).reduceLeft(AndType.apply) } } } - if (ctx.featureEnabled(defn.LanguageModuleClass, nme.keepUnions)) tp - else tp match { + + tp match { case tp: OrType => - approximateOr(tp.tp1, tp.tp2) // Maybe refactor using liftToRec? - case tp @ AndType(tp1, tp2) => - tp derived_& (approximateUnion(tp1), approximateUnion(tp2)) - case tp: RefinedType => - tp.derivedRefinedType(approximateUnion(tp.parent), tp.refinedName, tp.refinedInfo) - case tp: RecType => - tp.rebind(approximateUnion(tp.parent)) + approximateOr(tp.tp1, tp.tp2) case _ => tp } } + /** Given a disjunction T1 | ... | Tn of types with potentially embedded + * type variables, constrain type variables further if this eliminates + * some of the branches of the disjunction. Do this also for disjunctions + * embedded in intersections, as parents in refinements, and in recursive types. + * + * For instance, if `A` is an unconstrained type variable, then + * + * ArrayBuffer[Int] | ArrayBuffer[A] + * + * is approximated by constraining `A` to be =:= to `Int` and returning `ArrayBuffer[Int]` + * instead of `ArrayBuffer[_ >: Int | A <: Int & A]` + */ + def harmonizeUnion(tp: Type): Type = tp match { + case tp: OrType => + joinIfScala2(typeComparer.fluidly(tp.tp1 | tp.tp2)) + case tp @ AndType(tp1, tp2) => + tp derived_& (harmonizeUnion(tp1), harmonizeUnion(tp2)) + case tp: RefinedType => + tp.derivedRefinedType(harmonizeUnion(tp.parent), tp.refinedName, tp.refinedInfo) + case tp: RecType => + tp.rebind(harmonizeUnion(tp.parent)) + case _ => + tp + } + + /** Under -language:Scala2: Replace or-types with their joins */ + private def joinIfScala2(tp: Type) = tp match { + case tp: OrType if scala2Mode => tp.join + case _ => tp + } + /** Not currently needed: * def liftToRec(f: (Type, Type) => Type)(tp1: Type, tp2: Type)(implicit ctx: Context) = { @@ -493,7 +516,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. */ def featureEnabled(owner: ClassSymbol, feature: TermName): Boolean = { def toPrefix(sym: Symbol): String = - if (!sym.exists || (sym eq defn.LanguageModuleClass) || (sym eq defn.Scala2LanguageModuleClass)) "" + if (!sym.exists || (sym eq defn.LanguageModuleClass)) "" else toPrefix(sym.owner) + sym.name + "." def featureName = toPrefix(owner) + feature def hasImport(implicit ctx: Context): Boolean = { @@ -512,13 +535,13 @@ trait TypeOps { this: Context => // TODO: Make standalone object. /** Is auto-tupling enabled? */ def canAutoTuple = - !featureEnabled(defn.Scala2LanguageModuleClass, nme.noAutoTupling) + !featureEnabled(defn.LanguageModuleClass, nme.noAutoTupling) def scala2Mode = featureEnabled(defn.LanguageModuleClass, nme.Scala2) def dynamicsEnabled = - featureEnabled(defn.Scala2LanguageModuleClass, nme.dynamics) + featureEnabled(defn.LanguageModuleClass, nme.dynamics) def testScala2Mode(msg: String, pos: Position) = { if (scala2Mode) migrationWarning(msg, pos) diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 7e332b412..5c476c1cb 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -152,7 +152,7 @@ extends TyperState(r) { } override def gc()(implicit ctx: Context): Unit = { - val toCollect = new mutable.ListBuffer[GenericType] + val toCollect = new mutable.ListBuffer[PolyType] constraint foreachTypeVar { tvar => if (!tvar.inst.exists) { val inst = instType(tvar) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 2f1b6b829..38913a7d0 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -61,14 +61,13 @@ object Types { * | +- ExprType * | +- AnnotatedType * | +- TypeVar + * | +- PolyType * | * +- GroundType -+- AndType * +- OrType * +- MethodType -----+- ImplicitMethodType * | +- JavaMethodType * +- ClassInfo - * +- GenericType ----+- PolyType - * | +- TypeLambda * | * +- NoType * +- NoPrefix @@ -96,6 +95,9 @@ object Types { /** Is this type a value type? */ final def isValueType: Boolean = this.isInstanceOf[ValueType] + /** Is the is value type or type lambda? */ + final def isValueTypeOrLambda: Boolean = isValueType || this.isInstanceOf[PolyType] + /** Does this type denote a stable reference (i.e. singleton type)? */ final def isStable(implicit ctx: Context): Boolean = stripTypeVar match { case tp: TermRef => tp.termSymbol.isStable && tp.prefix.isStable @@ -237,9 +239,14 @@ object Types { /** The parts of this type which are type or term refs and which * satisfy predicate `p`. + * + * @param p The predicate to satisfy + * @param excludeLowerBounds If set to true, the lower bounds of abstract + * types will be ignored. */ - def namedPartsWith(p: NamedType => Boolean)(implicit ctx: Context): collection.Set[NamedType] = - new NamedPartsAccumulator(p).apply(mutable.LinkedHashSet(), this) + def namedPartsWith(p: NamedType => Boolean, excludeLowerBounds: Boolean = false) + (implicit ctx: Context): collection.Set[NamedType] = + new NamedPartsAccumulator(p, excludeLowerBounds).apply(mutable.LinkedHashSet(), this) /** Map function `f` over elements of an AndType, rebuilding with function `g` */ def mapReduceAnd[T](f: Type => T)(g: (T, T) => T)(implicit ctx: Context): T = stripTypeVar match { @@ -436,8 +443,12 @@ object Types { tp.cls.findMember(name, pre, excluded) case AndType(l, r) => goAnd(l, r) - case OrType(l, r) => - goOr(l, r) + case tp: OrType => + // we need to keep the invariant that `pre <: tp`. Branch `union-types-narrow-prefix` + // achieved that by narrowing `pre` to each alternative, but it led to merge errors in + // lots of places. The present strategy is instead of widen `tp` using `join` to be a + // supertype of `pre`. + go(tp.join) case tp: JavaArrayType => defn.ObjectType.findMember(name, pre, excluded) case ErrorType => @@ -515,7 +526,7 @@ object Types { } def goApply(tp: HKApply) = tp.tycon match { - case tl: TypeLambda => + case tl: PolyType => go(tl.resType).mapInfo(info => tl.derivedLambdaAbstraction(tl.paramNames, tl.paramBounds, info).appliedTo(tp.args)) case _ => @@ -556,7 +567,6 @@ object Types { def goAnd(l: Type, r: Type) = { go(l) & (go(r), pre, safeIntersection = ctx.pendingMemberSearches.contains(name)) } - def goOr(l: Type, r: Type) = go(l) | (go(r), pre) { val recCount = ctx.findMemberCount + 1 ctx.findMemberCount = recCount @@ -610,13 +620,13 @@ object Types { /** The set of abstract term members of this type. */ final def abstractTermMembers(implicit ctx: Context): Seq[SingleDenotation] = track("abstractTermMembers") { memberDenots(abstractTermNameFilter, - (name, buf) => buf ++= member(name).altsWith(_ is Deferred)) + (name, buf) => buf ++= nonPrivateMember(name).altsWith(_ is Deferred)) } /** The set of abstract type members of this type. */ final def abstractTypeMembers(implicit ctx: Context): Seq[SingleDenotation] = track("abstractTypeMembers") { memberDenots(abstractTypeNameFilter, - (name, buf) => buf += member(name).asSingleDenotation) + (name, buf) => buf += nonPrivateMember(name).asSingleDenotation) } /** The set of abstract type members of this type. */ @@ -1081,7 +1091,7 @@ object Types { /** The parameter types in the first parameter section of a generic type or MethodType, Empty list for others */ final def firstParamTypes(implicit ctx: Context): List[Type] = this match { case mt: MethodType => mt.paramTypes - case pt: GenericType => pt.resultType.firstParamTypes + case pt: PolyType => pt.resultType.firstParamTypes case _ => Nil } @@ -1230,28 +1240,6 @@ object Types { */ def simplified(implicit ctx: Context) = ctx.simplify(this, null) - /** Approximations of union types: We replace a union type Tn | ... | Tn - * by the smallest intersection type of baseclass instances of T1,...,Tn. - * Example: Given - * - * trait C[+T] - * trait D - * class A extends C[A] with D - * class B extends C[B] with D with E - * - * we approximate `A | B` by `C[A | B] with D` - * - * As a second measure we also homogenize refinements containing - * type variables. For instance, if `A` is an instantiatable type variable, - * then - * - * ArrayBuffer[Int] | ArrayBuffer[A] - * - * is approximated by instantiating `A` to `Int` and returning `ArrayBuffer[Int]` - * instead of `ArrayBuffer[_ >: Int | A <: Int & A]` - */ - def approximateUnion(implicit ctx: Context) = ctx.approximateUnion(this) - /** customized hash code of this type. * NotCached for uncached types. Cached types * compute hash and use it as the type's hashCode. @@ -2216,7 +2204,7 @@ object Types { object AndType { def apply(tp1: Type, tp2: Type)(implicit ctx: Context) = { - assert(tp1.isInstanceOf[ValueType] && tp2.isInstanceOf[ValueType], i"$tp1 & $tp2 / " + s"$tp1 & $tp2") + assert(tp1.isValueType && tp2.isValueType, i"$tp1 & $tp2 / " + s"$tp1 & $tp2") unchecked(tp1, tp2) } def unchecked(tp1: Type, tp2: Type)(implicit ctx: Context) = { @@ -2233,9 +2221,24 @@ object Types { } abstract case class OrType(tp1: Type, tp2: Type) extends CachedGroundType with AndOrType { + assert(tp1.isInstanceOf[ValueType] && tp2.isInstanceOf[ValueType]) def isAnd = false + private[this] var myJoin: Type = _ + private[this] var myJoinPeriod: Period = Nowhere + + /** Replace or type by the closest non-or type above it */ + def join(implicit ctx: Context): Type = { + if (myJoinPeriod != ctx.period) { + myJoin = ctx.orDominator(this) + core.println(i"join of $this == $myJoin") + assert(myJoin != this) + myJoinPeriod = ctx.period + } + myJoin + } + def derivedOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type = if ((tp1 eq this.tp1) && (tp2 eq this.tp2)) this else OrType.make(tp1, tp2) @@ -2264,7 +2267,7 @@ object Types { // and therefore two different poly types would never be equal. /** A trait that mixes in functionality for signature caching */ - trait MethodicType extends Type { + trait MethodicType extends TermType { private[this] var mySignature: Signature = _ private[this] var mySignatureRunId: Int = NoRunId @@ -2504,82 +2507,67 @@ object Types { } } - /** A common supertrait of PolyType and TypeLambda */ - trait GenericType extends BindingType with TermType { - - /** The names of the type parameters */ - val paramNames: List[TypeName] + /** A type lambda of the form `[v_0 X_0, ..., v_n X_n] => T` */ + class PolyType(val paramNames: List[TypeName], val variances: List[Int])( + paramBoundsExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => Type) + extends CachedProxyType with BindingType with MethodOrPoly { /** The bounds of the type parameters */ - val paramBounds: List[TypeBounds] + val paramBounds: List[TypeBounds] = paramBoundsExp(this) /** The result type of a PolyType / body of a type lambda */ - val resType: Type + val resType: Type = resultTypeExp(this) - /** If this is a type lambda, the variances of its parameters, otherwise Nil.*/ - def variances: List[Int] + assert(resType.isInstanceOf[TermType], this) + assert(paramNames.nonEmpty) - override def resultType(implicit ctx: Context) = resType + protected def computeSignature(implicit ctx: Context) = resultSignature - /** Unconditionally create a new generic type like this one with given elements */ - def duplicate(paramNames: List[TypeName] = this.paramNames, paramBounds: List[TypeBounds] = this.paramBounds, resType: Type)(implicit ctx: Context): GenericType + def isPolymorphicMethodType: Boolean = resType match { + case _: MethodType => true + case _ => false + } + + /** PolyParam references to all type parameters of this type */ + lazy val paramRefs: List[PolyParam] = paramNames.indices.toList.map(PolyParam(this, _)) + + lazy val typeParams: List[LambdaParam] = + paramNames.indices.toList.map(new LambdaParam(this, _)) + + override def resultType(implicit ctx: Context) = resType + override def underlying(implicit ctx: Context) = resType /** Instantiate result type by substituting parameters with given arguments */ final def instantiate(argTypes: List[Type])(implicit ctx: Context): Type = resultType.substParams(this, argTypes) /** Instantiate parameter bounds by substituting parameters with given arguments */ - def instantiateBounds(argTypes: List[Type])(implicit ctx: Context): List[TypeBounds] = + final def instantiateBounds(argTypes: List[Type])(implicit ctx: Context): List[TypeBounds] = paramBounds.mapConserve(_.substParams(this, argTypes).bounds) - def derivedGenericType(paramNames: List[TypeName], paramBounds: List[TypeBounds], resType: Type)(implicit ctx: Context) = - if ((paramNames eq this.paramNames) && (paramBounds eq this.paramBounds) && (resType eq this.resType)) this - else duplicate(paramNames, paramBounds, resType) + def newLikeThis(paramNames: List[TypeName], paramBounds: List[TypeBounds], resType: Type)(implicit ctx: Context): PolyType = + PolyType.apply(paramNames, variances)( + x => paramBounds mapConserve (_.subst(this, x).bounds), + x => resType.subst(this, x)) - /** PolyParam references to all type parameters of this type */ - lazy val paramRefs: List[PolyParam] = paramNames.indices.toList.map(PolyParam(this, _)) + def derivedPolyType(paramNames: List[TypeName] = this.paramNames, + paramBounds: List[TypeBounds] = this.paramBounds, + resType: Type = this.resType)(implicit ctx: Context) = + if ((paramNames eq this.paramNames) && (paramBounds eq this.paramBounds) && (resType eq this.resType)) this + else newLikeThis(paramNames, paramBounds, resType) - /** The type `[tparams := paramRefs] tp`, where `tparams` can be - * either a list of type parameter symbols or a list of lambda parameters - */ - def lifted(tparams: List[TypeParamInfo], tp: Type)(implicit ctx: Context): Type = - tparams match { - case LambdaParam(poly, _) :: _ => tp.subst(poly, this) - case tparams: List[Symbol @unchecked] => tp.subst(tparams, paramRefs) + def derivedLambdaAbstraction(paramNames: List[TypeName], paramBounds: List[TypeBounds], resType: Type)(implicit ctx: Context): Type = + resType match { + case resType @ TypeAlias(alias) => + resType.derivedTypeAlias(newLikeThis(paramNames, paramBounds, alias)) + case resType @ TypeBounds(lo, hi) => + resType.derivedTypeBounds( + if (lo.isRef(defn.NothingClass)) lo else newLikeThis(paramNames, paramBounds, lo), + newLikeThis(paramNames, paramBounds, hi)) + case _ => + derivedPolyType(paramNames, paramBounds, resType) } - override def equals(other: Any) = other match { - case other: GenericType => - other.paramNames == this.paramNames && - other.paramBounds == this.paramBounds && - other.resType == this.resType && - other.variances == this.variances - case _ => false - } - } - - /** A type for polymorphic methods */ - class PolyType(val paramNames: List[TypeName])(paramBoundsExp: GenericType => List[TypeBounds], resultTypeExp: GenericType => Type) - extends CachedGroundType with GenericType with MethodOrPoly { - val paramBounds: List[TypeBounds] = paramBoundsExp(this) - val resType: Type = resultTypeExp(this) - def variances = Nil - - protected def computeSignature(implicit ctx: Context) = resultSignature - - def isPolymorphicMethodType: Boolean = resType match { - case _: MethodType => true - case _ => false - } - - def derivedPolyType(paramNames: List[TypeName], paramBounds: List[TypeBounds], resType: Type)(implicit ctx: Context): PolyType = - derivedGenericType(paramNames, paramBounds, resType).asInstanceOf[PolyType] - - def duplicate(paramNames: List[TypeName] = this.paramNames, paramBounds: List[TypeBounds] = this.paramBounds, resType: Type)(implicit ctx: Context): PolyType = - PolyType(paramNames)( - x => paramBounds mapConserve (_.subst(this, x).bounds), - x => resType.subst(this, x)) - /** Merge nested polytypes into one polytype. nested polytypes are normally not supported * but can arise as temporary data structures. */ @@ -2598,66 +2586,55 @@ object Types { case _ => this } - override def toString = s"PolyType($paramNames, $paramBounds, $resType)" + /** The type `[tparams := paramRefs] tp`, where `tparams` can be + * either a list of type parameter symbols or a list of lambda parameters + */ + def lifted(tparams: List[TypeParamInfo], tp: Type)(implicit ctx: Context): Type = + tparams match { + case LambdaParam(poly, _) :: _ => tp.subst(poly, this) + case tparams: List[Symbol @unchecked] => tp.subst(tparams, paramRefs) + } + + override def equals(other: Any) = other match { + case other: PolyType => + other.paramNames == this.paramNames && + other.paramBounds == this.paramBounds && + other.resType == this.resType && + other.variances == this.variances + case _ => false + } + + override def toString = s"PolyType($variances, $paramNames, $paramBounds, $resType)" - override def computeHash = doHash(paramNames, resType, paramBounds) + override def computeHash = doHash(variances ::: paramNames, resType, paramBounds) } object PolyType { - def apply(paramNames: List[TypeName])(paramBoundsExp: GenericType => List[TypeBounds], resultTypeExp: GenericType => Type)(implicit ctx: Context): PolyType = { - unique(new PolyType(paramNames)(paramBoundsExp, resultTypeExp)) + def apply(paramNames: List[TypeName], variances: List[Int] = Nil)( + paramBoundsExp: PolyType => List[TypeBounds], + resultTypeExp: PolyType => Type)(implicit ctx: Context): PolyType = { + val vs = if (variances.isEmpty) paramNames.map(alwaysZero) else variances + unique(new PolyType(paramNames, vs)(paramBoundsExp, resultTypeExp)) } - def fromSymbols(tparams: List[Symbol], resultType: Type)(implicit ctx: Context) = + def fromSymbols(tparams: List[Symbol], resultType: Type)(implicit ctx: Context): Type = if (tparams.isEmpty) resultType - else apply(tparams map (_.name.asTypeName))( + else apply(tparams map (_.name.asTypeName), tparams.map(_.variance))( pt => tparams.map(tparam => pt.lifted(tparams, tparam.info).bounds), pt => pt.lifted(tparams, resultType)) - } - - // ----- HK types: TypeLambda, LambdaParam, HKApply --------------------- - - /** A type lambda of the form `[v_0 X_0, ..., v_n X_n] => T` */ - class TypeLambda(val paramNames: List[TypeName], val variances: List[Int])(paramBoundsExp: GenericType => List[TypeBounds], resultTypeExp: GenericType => Type) - extends CachedProxyType with GenericType with ValueType { - val paramBounds = paramBoundsExp(this) - val resType = resultTypeExp(this) - - assert(resType.isInstanceOf[TermType], this) - assert(paramNames.nonEmpty) - - override def underlying(implicit ctx: Context) = resType - lazy val typeParams: List[LambdaParam] = - paramNames.indices.toList.map(new LambdaParam(this, _)) - - def derivedLambdaAbstraction(paramNames: List[TypeName], paramBounds: List[TypeBounds], resType: Type)(implicit ctx: Context): Type = - resType match { - case resType @ TypeAlias(alias) => - resType.derivedTypeAlias(duplicate(paramNames, paramBounds, alias)) - case resType @ TypeBounds(lo, hi) => - resType.derivedTypeBounds( - if (lo.isRef(defn.NothingClass)) lo else duplicate(paramNames, paramBounds, lo), - duplicate(paramNames, paramBounds, hi)) - case _ => - derivedTypeLambda(paramNames, paramBounds, resType) - } - - def derivedTypeLambda(paramNames: List[TypeName] = paramNames, paramBounds: List[TypeBounds] = paramBounds, resType: Type)(implicit ctx: Context): TypeLambda = - derivedGenericType(paramNames, paramBounds, resType).asInstanceOf[TypeLambda] - - def duplicate(paramNames: List[TypeName] = this.paramNames, paramBounds: List[TypeBounds] = this.paramBounds, resType: Type)(implicit ctx: Context): TypeLambda = - TypeLambda(paramNames, variances)( - x => paramBounds mapConserve (_.subst(this, x).bounds), - x => resType.subst(this, x)) - - override def toString = s"TypeLambda($variances, $paramNames, $paramBounds, $resType)" + def unapply(tl: PolyType): Some[(List[LambdaParam], Type)] = + Some((tl.typeParams, tl.resType)) - override def computeHash = doHash(variances ::: paramNames, resType, paramBounds) + def any(n: Int)(implicit ctx: Context) = + apply(tpnme.syntheticLambdaParamNames(n), List.fill(n)(0))( + pt => List.fill(n)(TypeBounds.empty), pt => defn.AnyType) } + // ----- HK types: LambdaParam, HKApply --------------------- + /** The parameter of a type lambda */ - case class LambdaParam(tl: TypeLambda, n: Int) extends TypeParamInfo { + case class LambdaParam(tl: PolyType, n: Int) extends TypeParamInfo { def isTypeParam(implicit ctx: Context) = true def paramName(implicit ctx: Context): TypeName = tl.paramNames(n) def paramBounds(implicit ctx: Context): TypeBounds = tl.paramBounds(n) @@ -2668,24 +2645,6 @@ object Types { def paramRef(implicit ctx: Context): Type = PolyParam(tl, n) } - object TypeLambda { - def apply(paramNames: List[TypeName], variances: List[Int])(paramBoundsExp: GenericType => List[TypeBounds], resultTypeExp: GenericType => Type)(implicit ctx: Context): TypeLambda = { - unique(new TypeLambda(paramNames, variances)(paramBoundsExp, resultTypeExp)) - } - - def fromSymbols(tparams: List[Symbol], resultType: Type)(implicit ctx: Context) = - if (tparams.isEmpty) resultType - else apply(tparams map (_.name.asTypeName), tparams.map(_.variance))( - pt => tparams.map(tparam => pt.lifted(tparams, tparam.info).bounds), - pt => pt.lifted(tparams, resultType)) - def unapply(tl: TypeLambda): Some[(List[LambdaParam], Type)] = - Some((tl.typeParams, tl.resType)) - - def any(n: Int)(implicit ctx: Context) = - apply(tpnme.syntheticLambdaParamNames(n), List.fill(n)(0))( - pt => List.fill(n)(TypeBounds.empty), pt => defn.AnyType) - } - /** A higher kinded type application `C[T_1, ..., T_n]` */ abstract case class HKApply(tycon: Type, args: List[Type]) extends CachedProxyType with ValueType { @@ -2698,7 +2657,7 @@ object Types { override def superType(implicit ctx: Context): Type = { if (ctx.period != validSuper) { cachedSuper = tycon match { - case tp: TypeLambda => defn.AnyType + case tp: PolyType => defn.AnyType case tp: TypeVar if !tp.inst.exists => // supertype not stable, since underlying might change return tp.underlying.applyIfParameterized(args) @@ -2724,7 +2683,7 @@ object Types { def typeParams(implicit ctx: Context): List[TypeParamInfo] = { val tparams = tycon.typeParams - if (tparams.isEmpty) TypeLambda.any(args.length).typeParams else tparams + if (tparams.isEmpty) PolyType.any(args.length).typeParams else tparams } def derivedAppliedType(tycon: Type, args: List[Type])(implicit ctx: Context): Type = @@ -2737,7 +2696,7 @@ object Types { def check(tycon: Type): Unit = tycon.stripTypeVar match { case tycon: TypeRef if !tycon.symbol.isClass => case _: PolyParam | ErrorType | _: WildcardType => - case _: TypeLambda => + case _: PolyType => assert(args.exists(_.isInstanceOf[TypeBounds]), s"unreduced type apply: $this") case tycon: AnnotatedType => check(tycon.underlying) @@ -2803,8 +2762,8 @@ object Types { } /** TODO Some docs would be nice here! */ - case class PolyParam(binder: GenericType, paramNum: Int) extends ParamType { - type BT = GenericType + case class PolyParam(binder: PolyType, paramNum: Int) extends ParamType { + type BT = PolyType def copyBoundType(bt: BT) = PolyParam(bt, paramNum) /** Looking only at the structure of `bound`, is one of the following true? @@ -3410,8 +3369,8 @@ object Types { tp.derivedMethodType(tp.paramNames, formals, restpe) protected def derivedExprType(tp: ExprType, restpe: Type): Type = tp.derivedExprType(restpe) - protected def derivedGenericType(tp: GenericType, pbounds: List[TypeBounds], restpe: Type): Type = - tp.derivedGenericType(tp.paramNames, pbounds, restpe) + protected def derivedPolyType(tp: PolyType, pbounds: List[TypeBounds], restpe: Type): Type = + tp.derivedPolyType(tp.paramNames, pbounds, restpe) /** Map this function over given type */ def mapOver(tp: Type): Type = { @@ -3453,12 +3412,12 @@ object Types { case tp: ExprType => derivedExprType(tp, this(tp.resultType)) - case tp: GenericType => + case tp: PolyType => def mapOverPoly = { variance = -variance val bounds1 = tp.paramBounds.mapConserve(this).asInstanceOf[List[TypeBounds]] variance = -variance - derivedGenericType(tp, bounds1, this(tp.resultType)) + derivedPolyType(tp, bounds1, this(tp.resultType)) } mapOverPoly @@ -3671,7 +3630,7 @@ object Types { case ExprType(restpe) => this(x, restpe) - case tp: GenericType => + case tp: PolyType => variance = -variance val y = foldOver(x, tp.paramBounds) variance = -variance @@ -3756,7 +3715,8 @@ object Types { def apply(x: Boolean, tp: Type) = x || tp.isUnsafeNonvariant || foldOver(x, tp) } - class NamedPartsAccumulator(p: NamedType => Boolean)(implicit ctx: Context) extends TypeAccumulator[mutable.Set[NamedType]] { + class NamedPartsAccumulator(p: NamedType => Boolean, excludeLowerBounds: Boolean = false) + (implicit ctx: Context) extends TypeAccumulator[mutable.Set[NamedType]] { override def stopAtStatic = false def maybeAdd(x: mutable.Set[NamedType], tp: NamedType) = if (p(tp)) x += tp else x val seen: mutable.Set[Type] = mutable.Set() @@ -3769,7 +3729,8 @@ object Types { apply(foldOver(maybeAdd(x, tp), tp), tp.underlying) case tp: TypeRef => foldOver(maybeAdd(x, tp), tp) - case TypeBounds(_, hi) => + case TypeBounds(lo, hi) => + if (!excludeLowerBounds) apply(x, lo) apply(x, hi) case tp: ThisType => apply(x, tp.tref) @@ -3802,7 +3763,7 @@ object Types { object abstractTypeNameFilter extends NameFilter { def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = name.isTypeName && { - val mbr = pre.member(name) + val mbr = pre.nonPrivateMember(name) (mbr.symbol is Deferred) && mbr.info.isInstanceOf[RealTypeBounds] } } @@ -3819,7 +3780,7 @@ object Types { /** A filter for names of deferred term definitions of a given type */ object abstractTermNameFilter extends NameFilter { def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = - name.isTermName && (pre member name).hasAltWith(_.symbol is Deferred) + name.isTermName && pre.nonPrivateMember(name).hasAltWith(_.symbol is Deferred) } object typeNameFilter extends NameFilter { diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index b6f52c0ec..8889e8a5c 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -251,7 +251,7 @@ class TreePickler(pickler: TastyPickler) { case tpe: ExprType => writeByte(BYNAMEtype) pickleType(tpe.underlying) - case tpe: TypeLambda => + case tpe: PolyType => writeByte(LAMBDAtype) val paramNames = tpe.typeParams.map(tparam => varianceToPrefix(tparam.paramVariance) +: tparam.paramName) @@ -259,9 +259,6 @@ class TreePickler(pickler: TastyPickler) { case tpe: MethodType if richTypes => writeByte(METHODtype) pickleMethodic(tpe.resultType, tpe.paramNames, tpe.paramTypes) - case tpe: PolyType if richTypes => - writeByte(POLYtype) - pickleMethodic(tpe.resultType, tpe.paramNames, tpe.paramBounds) case tpe: PolyParam => if (!pickleParamType(tpe)) // TODO figure out why this case arises in e.g. pickling AbstractFileReader. diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 09f2c0d1f..f67159808 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -270,7 +270,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle val (rawNames, paramReader) = readNamesSkipParams val (variances, paramNames) = rawNames .map(name => (prefixToVariance(name.head), name.tail.toTypeName)).unzip - val result = TypeLambda(paramNames, variances)( + val result = PolyType(paramNames, variances)( pt => registeringType(pt, paramReader.readParamTypes[TypeBounds](end)), pt => readType()) goto(end) @@ -290,7 +290,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle result case PARAMtype => readTypeRef() match { - case binder: GenericType => PolyParam(binder, readNat()) + case binder: PolyType => PolyParam(binder, readNat()) case binder: MethodType => MethodParam(binder, readNat()) } case CLASSconst => diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 9aadf0c61..46e7512f3 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -27,6 +27,8 @@ import rewrite.Rewrites.patch object Parsers { import ast.untpd._ + import reporting.diagnostic.Message + import reporting.diagnostic.messages._ case class OpInfo(operand: Tree, operator: Name, offset: Offset) @@ -97,7 +99,7 @@ object Parsers { /** Issue an error at given offset if beyond last error offset * and update lastErrorOffset. */ - def syntaxError(msg: String, offset: Int = in.offset): Unit = + def syntaxError(msg: Message, offset: Int = in.offset): Unit = if (offset > lastErrorOffset) { syntaxError(msg, Position(offset)) lastErrorOffset = in.offset @@ -106,7 +108,7 @@ object Parsers { /** Unconditionally issue an error at given position, without * updating lastErrorOffset. */ - def syntaxError(msg: String, pos: Position): Unit = + def syntaxError(msg: Message, pos: Position): Unit = ctx.error(msg, source atPos pos) } @@ -213,20 +215,23 @@ object Parsers { } } - def warning(msg: String, offset: Int = in.offset) = + def warning(msg: Message, sourcePos: SourcePosition) = + ctx.warning(msg, sourcePos) + + def warning(msg: Message, offset: Int = in.offset) = ctx.warning(msg, source atPos Position(offset)) - def deprecationWarning(msg: String, offset: Int = in.offset) = + def deprecationWarning(msg: Message, offset: Int = in.offset) = ctx.deprecationWarning(msg, source atPos Position(offset)) /** Issue an error at current offset taht input is incomplete */ - def incompleteInputError(msg: String) = + def incompleteInputError(msg: Message) = ctx.incompleteInputError(msg, source atPos Position(in.offset)) /** If at end of file, issue an incompleteInputError. * Otherwise issue a syntax error and skip to next safe point. */ - def syntaxErrorOrIncomplete(msg: String) = + def syntaxErrorOrIncomplete(msg: Message) = if (in.token == EOF) incompleteInputError(msg) else { syntaxError(msg) @@ -696,9 +701,9 @@ object Parsers { else if (in.token == LBRACKET) { val start = in.offset val tparams = typeParamClause(ParamOwner.TypeParam) - if (isIdent && in.name.toString == "->") - atPos(start, in.skipToken())(TypeLambdaTree(tparams, typ())) - else { syntaxErrorOrIncomplete(expectedMessage("`->'")); typ() } + if (in.token == ARROW) + atPos(start, in.skipToken())(PolyTypeTree(tparams, typ())) + else { accept(ARROW); typ() } } else infixType() @@ -732,7 +737,7 @@ object Parsers { def withTypeRest(t: Tree): Tree = if (in.token == WITH) { - deprecationWarning("`with' as a type operator has been deprecated; use `&' instead") + deprecationWarning(DeprecatedWithOperator()) in.nextToken() AndTypeTree(t, withType()) } @@ -1004,28 +1009,33 @@ object Parsers { DoWhile(body, cond) } case TRY => + val tryOffset = in.offset atPos(in.skipToken()) { val body = expr() - val handler = + val (handler, handlerStart) = if (in.token == CATCH) { + val pos = in.offset in.nextToken() - expr() - } else EmptyTree + (expr(), pos) + } else (EmptyTree, -1) handler match { - case Block(Nil, EmptyTree) => syntaxError( - "`catch` block does not contain a valid expression, try adding a case like - `case e: Exception =>` to the block", - handler.pos - ) + case Block(Nil, EmptyTree) => + assert(handlerStart != -1) + syntaxError( + new EmptyCatchBlock(body), + Position(handlerStart, handler.pos.end) + ) case _ => } val finalizer = if (in.token == FINALLY) { accept(FINALLY); expr() } else { - if (handler.isEmpty) - warning("A try without `catch` or `finally` is equivalent to putting its body in a block; no exceptions are handled.") - + if (handler.isEmpty) warning( + EmptyCatchAndFinallyBlock(body), + source atPos Position(tryOffset, body.pos.end) + ) EmptyTree } ParsedTry(body, handler, finalizer) @@ -2051,7 +2061,7 @@ object Parsers { def templateBody(): (ValDef, List[Tree]) = { val r = inDefScopeBraces { templateStatSeq() } if (in.token == WITH) { - syntaxError("early definitions are not supported; use trait parameters instead") + syntaxError(EarlyDefinitionsNotSupported()) in.nextToken() template(emptyConstructor) } diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index 8921c56a3..e7968b14a 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -8,7 +8,10 @@ import collection.Map import Decorators._ import scala.annotation.switch import scala.util.control.NonFatal -import reporting.Diagnostic +import reporting.diagnostic.MessageContainer +import util.DiffUtil +import Highlighting._ +import SyntaxHighlighting._ object Formatting { @@ -66,17 +69,39 @@ object Formatting { * message composition methods, this is crucial. */ class ErrorMessageFormatter(sc: StringContext) extends StringFormatter(sc) { - override protected def showArg(arg: Any)(implicit ctx: Context): String = { - def isSensical(arg: Any): Boolean = arg match { - case tpe: Type => - tpe.exists && !tpe.isErroneous - case sym: Symbol if sym.isCompleted => - sym.info != ErrorType && sym.info != TypeAlias(ErrorType) && sym.info.exists - case _ => true + override protected def showArg(arg: Any)(implicit ctx: Context): String = + wrapNonSensical(arg, super.showArg(arg)) + } + + class SyntaxFormatter(sc: StringContext) extends StringFormatter(sc) { + override protected def showArg(arg: Any)(implicit ctx: Context): String = + arg match { + case arg: Showable if ctx.settings.color.value != "never" => + val highlighted = + SyntaxHighlighting(wrapNonSensical(arg, super.showArg(arg))) + new String(highlighted.toArray) + case hl: Highlight => + hl.show + case hb: HighlightBuffer => + hb.toString + case str: String if ctx.settings.color.value != "never" => + new String(SyntaxHighlighting(str).toArray) + case _ => super.showArg(arg) } - val str = super.showArg(arg) - if (isSensical(arg)) str else Diagnostic.nonSensicalStartTag + str + Diagnostic.nonSensicalEndTag + } + + private def wrapNonSensical(arg: Any /* Type | Symbol */, str: String)(implicit ctx: Context): String = { + import MessageContainer._ + def isSensical(arg: Any): Boolean = arg match { + case tpe: Type => + tpe.exists && !tpe.isErroneous + case sym: Symbol if sym.isCompleted => + sym.info != ErrorType && sym.info != TypeAlias(ErrorType) && sym.info.exists + case _ => true } + + if (isSensical(arg)) str + else nonSensicalStartTag + str + nonSensicalEndTag } private type Recorded = AnyRef /*Symbol | PolyParam*/ @@ -111,65 +136,123 @@ object Formatting { seen.record(super.polyParamNameString(param), param) } - def explained2(op: Context => String)(implicit ctx: Context): String = { - val seen = new Seen - val explainCtx = ctx.printer match { - case dp: ExplainingPrinter => ctx // re-use outer printer and defer explanation to it - case _ => ctx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx)) - } + /** Create explanation for single `Recorded` type or symbol */ + def explanation(entry: AnyRef)(implicit ctx: Context): String = { + def boundStr(bound: Type, default: ClassSymbol, cmp: String) = + if (bound.isRef(default)) "" else i"$cmp $bound" - def explanation(entry: Recorded): String = { - def boundStr(bound: Type, default: ClassSymbol, cmp: String) = - if (bound.isRef(default)) "" else i"$cmp $bound" + def boundsStr(bounds: TypeBounds): String = { + val lo = boundStr(bounds.lo, defn.NothingClass, ">:") + val hi = boundStr(bounds.hi, defn.AnyClass, "<:") + if (lo.isEmpty) hi + else if (hi.isEmpty) lo + else s"$lo and $hi" + } - def boundsStr(bounds: TypeBounds): String = { - val lo = boundStr(bounds.lo, defn.NothingClass, ">:") - val hi = boundStr(bounds.hi, defn.AnyClass, "<:") - if (lo.isEmpty) hi - else if (hi.isEmpty) lo - else s"$lo and $hi" - } + def addendum(cat: String, info: Type): String = info match { + case bounds @ TypeBounds(lo, hi) if bounds ne TypeBounds.empty => + if (lo eq hi) i" which is an alias of $lo" + else i" with $cat ${boundsStr(bounds)}" + case _ => + "" + } - def addendum(cat: String, info: Type)(implicit ctx: Context): String = info match { - case bounds @ TypeBounds(lo, hi) if bounds ne TypeBounds.empty => - if (lo eq hi) i" which is an alias of $lo" - else i" with $cat ${boundsStr(bounds)}" - case _ => - "" - } + entry match { + case param: PolyParam => + s"is a type variable${addendum("constraint", ctx.typeComparer.bounds(param))}" + case sym: Symbol => + s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", sym.info)}" + } + } - entry match { - case param: PolyParam => - s"is a type variable${addendum("constraint", ctx.typeComparer.bounds(param))}" - case sym: Symbol => - s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", sym.info)}" - } + /** Turns a `Seen` into a `String` to produce an explanation for types on the + * form `where: T is...` + * + * @return string disambiguating types + */ + private def explanations(seen: Seen)(implicit ctx: Context): String = { + def needsExplanation(entry: Recorded) = entry match { + case param: PolyParam => ctx.typerState.constraint.contains(param) + case _ => false } - def explanations(seen: Seen)(implicit ctx: Context): String = { - def needsExplanation(entry: Recorded) = entry match { - case param: PolyParam => ctx.typerState.constraint.contains(param) - case _ => false + val toExplain: List[(String, Recorded)] = seen.toList.flatMap { + case (str, entry :: Nil) => + if (needsExplanation(entry)) (str, entry) :: Nil else Nil + case (str, entries) => + entries.map(alt => (seen.record(str, alt), alt)) + }.sortBy(_._1) + + def columnar(parts: List[(String, String)]): List[String] = { + lazy val maxLen = parts.map(_._1.length).max + parts.map { + case (leader, trailer) => + val variable = hl"$leader" + s"""$variable${" " * (maxLen - leader.length)} $trailer""" } - val toExplain: List[(String, Recorded)] = seen.toList.flatMap { - case (str, entry :: Nil) => - if (needsExplanation(entry)) (str, entry) :: Nil else Nil - case (str, entries) => - entries.map(alt => (seen.record(str, alt), alt)) - }.sortBy(_._1) - val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) } - val explainLines = columnar(explainParts, " ") - if (explainLines.isEmpty) "" else i"\n\nwhere $explainLines%\n %\n" } - op(explainCtx) ++ explanations(seen) + val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) } + val explainLines = columnar(explainParts) + if (explainLines.isEmpty) "" else i"where: $explainLines%\n %\n" } - def columnar(parts: List[(String, String)], sep: String): List[String] = { - lazy val maxLen = parts.map(_._1.length).max - parts.map { - case (leader, trailer) => - s"$leader${" " * (maxLen - leader.length)}$sep$trailer" + /** Context with correct printer set for explanations */ + private def explainCtx(seen: Seen)(implicit ctx: Context): Context = ctx.printer match { + case dp: ExplainingPrinter => + ctx // re-use outer printer and defer explanation to it + case _ => ctx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx)) + } + + /** Entrypoint for explanation string interpolator: + * + * ``` + * ex"disambiguate $tpe1 and $tpe2" + * ``` + */ + def explained2(op: Context => String)(implicit ctx: Context): String = { + val seen = new Seen + op(explainCtx(seen)) ++ explanations(seen) + } + + /** When getting a type mismatch it is useful to disambiguate placeholders like: + * + * ``` + * found: List[Int] + * required: List[T] + * where: T is a type in the initalizer of value s which is an alias of + * String + * ``` + * + * @return the `where` section as well as the printing context for the + * placeholders - `("T is a...", printCtx)` + */ + def disambiguateTypes(args: Type*)(implicit ctx: Context): (String, Context) = { + val seen = new Seen + val printCtx = explainCtx(seen) + args.foreach(_.show(printCtx)) // showing each member will put it into `seen` + (explanations(seen), printCtx) + } + + /** This method will produce a colored type diff from the given arguments. + * The idea is to do this for known cases that are useful and then fall back + * on regular syntax highlighting for the cases which are unhandled. + * + * Please not that if used in combination with `disambiguateTypes` the + * correct `Context` for printing should also be passed when calling the + * method. + * + * @return the (found, expected, changePercentage) with coloring to + * highlight the difference + */ + def typeDiff(found: Type, expected: Type)(implicit ctx: Context): (String, String) = { + val fnd = wrapNonSensical(found, found.show) + val exp = wrapNonSensical(expected, expected.show) + + DiffUtil.mkColoredTypeDiff(fnd, exp) match { + case _ if ctx.settings.color.value == "never" => (fnd, exp) + case (fnd, exp, change) if change < 0.5 => (fnd, exp) + case _ => (fnd, exp) } } } diff --git a/src/dotty/tools/dotc/printing/Highlighting.scala b/src/dotty/tools/dotc/printing/Highlighting.scala new file mode 100644 index 000000000..3bda7fb7a --- /dev/null +++ b/src/dotty/tools/dotc/printing/Highlighting.scala @@ -0,0 +1,77 @@ +package dotty.tools +package dotc +package printing + +import scala.collection.mutable +import core.Contexts.Context + +object Highlighting { + + implicit def highlightShow(h: Highlight)(implicit ctx: Context): String = + h.show + + abstract class Highlight(private val highlight: String) { + def text: String + + def show(implicit ctx: Context) = + if (ctx.settings.color.value == "never") text + else highlight + text + Console.RESET + + override def toString = + highlight + text + Console.RESET + + def +(other: Highlight)(implicit ctx: Context): HighlightBuffer = + new HighlightBuffer(this) + other + + def +(other: String)(implicit ctx: Context): HighlightBuffer = + new HighlightBuffer(this) + other + } + + abstract class Modifier(private val mod: String, text: String) extends Highlight(Console.RESET) { + override def show(implicit ctx: Context) = + if (ctx.settings.color.value == "never") "" + else mod + super.show + } + + case class HighlightBuffer(hl: Highlight)(implicit ctx: Context) { + val buffer = new mutable.ListBuffer[String] + + buffer += hl.show + + def +(other: Highlight): HighlightBuffer = { + buffer += other.show + this + } + + def +(other: String): HighlightBuffer = { + buffer += other + this + } + + override def toString = + buffer.mkString + } + + case class NoColor(text: String) extends Highlight(Console.RESET) + + case class Red(text: String) extends Highlight(Console.RED) + case class Blue(text: String) extends Highlight(Console.BLUE) + case class Cyan(text: String) extends Highlight(Console.CYAN) + case class Black(text: String) extends Highlight(Console.BLACK) + case class Green(text: String) extends Highlight(Console.GREEN) + case class White(text: String) extends Highlight(Console.WHITE) + case class Yellow(text: String) extends Highlight(Console.YELLOW) + case class Magenta(text: String) extends Highlight(Console.MAGENTA) + + case class RedB(text: String) extends Highlight(Console.RED_B) + case class BlueB(text: String) extends Highlight(Console.BLUE_B) + case class CyanB(text: String) extends Highlight(Console.CYAN_B) + case class BlackB(text: String) extends Highlight(Console.BLACK_B) + case class GreenB(text: String) extends Highlight(Console.GREEN_B) + case class WhiteB(text: String) extends Highlight(Console.WHITE_B) + case class YellowB(text: String) extends Highlight(Console.YELLOW_B) + case class MagentaB(text: String) extends Highlight(Console.MAGENTA_B) + + case class Bold(text: String) extends Modifier(Console.BOLD, text) + case class Underlined(text: String) extends Modifier(Console.UNDERLINED, text) +} diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala index a92095d9b..785f57897 100644 --- a/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -114,25 +114,6 @@ class PlainPrinter(_ctx: Context) extends Printer { case _ => toTextGlobal(arg) } - /** The text for a TypeLambda - * - * [v_1 p_1: B_1, ..., v_n p_n: B_n] -> T - * - * where - * @param paramNames = p_1, ..., p_n - * @param variances = v_1, ..., v_n - * @param argBoundss = B_1, ..., B_n - * @param body = T - */ - protected def typeLambdaText(paramNames: List[String], variances: List[Int], argBoundss: List[TypeBounds], body: Type): Text = { - def lambdaParamText(variance: Int, name: String, bounds: TypeBounds): Text = - varianceString(variance) ~ name ~ toText(bounds) - changePrec(GlobalPrec) { - "[" ~ Text((variances, paramNames, argBoundss).zipped.map(lambdaParamText), ", ") ~ - "] -> " ~ toTextGlobal(body) - } - } - /** The longest sequence of refinement types, starting at given type * and following parents. */ @@ -185,15 +166,12 @@ class PlainPrinter(_ctx: Context) extends Printer { } case tp: ExprType => changePrec(GlobalPrec) { "=> " ~ toText(tp.resultType) } - case tp: TypeLambda => - typeLambdaText(tp.paramNames.map(_.toString), tp.variances, tp.paramBounds, tp.resultType) case tp: PolyType => - def paramText(name: TypeName, bounds: TypeBounds): Text = - polyParamNameString(name) ~ polyHash(tp) ~ toText(bounds) + def paramText(variance: Int, name: Name, bounds: TypeBounds): Text = + varianceString(variance) ~ name.toString ~ toText(bounds) changePrec(GlobalPrec) { - "[" ~ - Text((tp.paramNames, tp.paramBounds).zipped map paramText, ", ") ~ - "]" ~ toText(tp.resultType) + "[" ~ Text((tp.variances, tp.paramNames, tp.paramBounds).zipped.map(paramText), ", ") ~ + "] => " ~ toTextGlobal(tp.resultType) } case tp: PolyParam => polyParamNameString(tp) ~ polyHash(tp.binder) @@ -229,7 +207,7 @@ class PlainPrinter(_ctx: Context) extends Printer { protected def simpleNameString(sym: Symbol): String = nameString(sym.name) /** If -uniqid is set, the hashcode of the polytype, after a # */ - protected def polyHash(pt: GenericType): Text = + protected def polyHash(pt: PolyType): Text = if (ctx.settings.uniqid.value) "#" + pt.hashCode else "" /** If -uniqid is set, the unique id of symbol, after a # */ diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 4f3a8d272..6315cfabc 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -20,7 +20,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { /** A stack of enclosing DefDef, TypeDef, or ClassDef, or ModuleDefs nodes */ private var enclosingDef: untpd.Tree = untpd.EmptyTree - private var lambdaNestingLevel: Int = 0 private var myCtx: Context = _ctx override protected[this] implicit def ctx: Context = myCtx @@ -368,7 +367,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toTextLocal(tpt) ~ " " ~ blockText(refines) case AppliedTypeTree(tpt, args) => toTextLocal(tpt) ~ "[" ~ Text(args map argText, ", ") ~ "]" - case TypeLambdaTree(tparams, body) => + case PolyTypeTree(tparams, body) => changePrec(GlobalPrec) { tparamsText(tparams) ~ " -> " ~ toText(body) } @@ -553,7 +552,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { (sym.allOverriddenSymbols exists (_ is TypeParam)) override def toText(sym: Symbol): Text = { - if (sym.name == nme.IMPORT) { + if (sym.isImport) { def importString(tree: untpd.Tree) = s"import ${tree.show}" sym.infoOrCompleter match { case info: Namer#Completer => return importString(info.original) diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 83c428976..86f34e64d 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -5,21 +5,29 @@ package printing import parsing.Tokens._ import scala.annotation.switch import scala.collection.mutable.StringBuilder +import core.Contexts.Context +import Highlighting.{Highlight, HighlightBuffer} /** This object provides functions for syntax highlighting in the REPL */ object SyntaxHighlighting { + val NoColor = Console.RESET - val CommentColor = Console.GREEN - val KeywordColor = Console.CYAN - val LiteralColor = Console.MAGENTA - val TypeColor = Console.GREEN - val AnnotationColor = Console.RED + val CommentColor = Console.BLUE + val KeywordColor = Console.YELLOW + val ValDefColor = Console.CYAN + val LiteralColor = Console.RED + val TypeColor = Console.MAGENTA + val AnnotationColor = Console.MAGENTA - private def none(str: String) = str - private def keyword(str: String) = KeywordColor + str + NoColor - private def typeDef(str: String) = TypeColor + str + NoColor - private def literal(str: String) = LiteralColor + str + NoColor - private def annotation(str: String) = AnnotationColor + str + NoColor + private def none(str: String) = str + private def keyword(str: String) = KeywordColor + str + NoColor + private def typeDef(str: String) = TypeColor + str + NoColor + private def literal(str: String) = LiteralColor + str + NoColor + private def valDef(str: String) = ValDefColor + str + NoColor + private def operator(str: String) = TypeColor + str + NoColor + private def annotation(str: String) = + if (str.trim == "@") str else AnnotationColor + str + NoColor + private val tripleQs = Console.RED_B + "???" + NoColor private val keywords: Seq[String] = for { index <- IF to INLINE // All alpha keywords @@ -33,15 +41,18 @@ object SyntaxHighlighting { 'q' :: 'r' :: 's' :: 't' :: 'u' :: 'v' :: 'w' :: 'x' :: 'y' :: 'z' :: Nil private val typeEnders = - '{' :: '}' :: ')' :: '(' :: '=' :: ' ' :: ',' :: '.' :: '\n' :: Nil + '{' :: '}' :: ')' :: '(' :: '[' :: ']' :: '=' :: ' ' :: ',' :: '.' :: + '\n' :: Nil - def apply(chars: Iterable[Char]): Vector[Char] = { + def apply(chars: Iterable[Char]): Iterable[Char] = { var prev: Char = 0 var remaining = chars.toStream val newBuf = new StringBuilder + var lastToken = "" @inline def keywordStart = - prev == 0 || prev == ' ' || prev == '{' || prev == '(' || prev == '\n' + prev == 0 || prev == ' ' || prev == '{' || prev == '(' || + prev == '\n' || prev == '[' || prev == ',' @inline def numberStart(c: Char) = c.isDigit && (!prev.isLetter || prev == '.' || prev == ' ' || prev == '(' || prev == '\u0000') @@ -67,7 +78,9 @@ object SyntaxHighlighting { if (n.isUpper && keywordStart) { appendWhile(n, !typeEnders.contains(_), typeDef) } else if (keywordStart) { - append(n, keywords.contains(_), keyword) + append(n, keywords.contains(_), { kw => + if (kw == "new") typeDef(kw) else keyword(kw) + }) } else { newBuf += n prev = n @@ -89,17 +102,17 @@ object SyntaxHighlighting { } } else newBuf += '/' case '=' => - append('=', _ == "=>", keyword) + append('=', _ == "=>", operator) case '<' => - append('<', { x => x == "<-" || x == "<:" || x == "<%" }, keyword) + append('<', { x => x == "<-" || x == "<:" || x == "<%" }, operator) case '>' => - append('>', { x => x == ">:" }, keyword) + append('>', { x => x == ">:" }, operator) case '#' => - if (prev != ' ' && prev != '.') newBuf append keyword("#") + if (prev != ' ' && prev != '.') newBuf append operator("#") else newBuf += n prev = '#' case '@' => - appendWhile('@', _ != ' ', annotation) + appendWhile('@', !typeEnders.contains(_), annotation) case '\"' => appendLiteral('\"', multiline = remaining.take(2).mkString == "\"\"") case '\'' => @@ -107,7 +120,11 @@ object SyntaxHighlighting { case '`' => appendTo('`', _ == '`', none) case _ => { - if (n.isUpper && keywordStart) + if (n == '?' && remaining.take(2).mkString == "??") { + takeChars(2) + newBuf append tripleQs + prev = '?' + } else if (n.isUpper && keywordStart) appendWhile(n, !typeEnders.contains(_), typeDef) else if (numberStart(n)) appendWhile(n, { x => x.isDigit || x == '.' || x == '\u0000'}, literal) @@ -169,7 +186,7 @@ object SyntaxHighlighting { prev = '$' } else if (next == '{') { var open = 1 // keep track of open blocks - newBuf append (KeywordColor + curr) + newBuf append (ValDefColor + curr) newBuf += next while (remaining.nonEmpty && open > 0) { var c = takeChar() @@ -179,7 +196,7 @@ object SyntaxHighlighting { } newBuf append LiteralColor } else { - newBuf append (KeywordColor + curr) + newBuf append (ValDefColor + curr) newBuf += next var c: Char = 'a' while (c.isLetterOrDigit && remaining.nonEmpty) { @@ -227,15 +244,32 @@ object SyntaxHighlighting { def append(c: Char, shouldHL: String => Boolean, highlight: String => String) = { var curr: Char = 0 val sb = new StringBuilder(s"$c") - while (remaining.nonEmpty && curr != ' ' && curr != '(' && curr != '\n') { + + def delim(c: Char) = (c: @switch) match { + case ' ' => true + case '\n' => true + case '(' => true + case '[' => true + case ':' => true + case '@' => true + case _ => false + } + + while (remaining.nonEmpty && !delim(curr)) { curr = takeChar() - if (curr != ' ' && curr != '\n') sb += curr + if (!delim(curr)) sb += curr } val str = sb.toString - val toAdd = if (shouldHL(str)) highlight(str) else str - val suffix = if (curr == ' ' || curr == '\n') s"$curr" else "" + val toAdd = + if (shouldHL(str)) + highlight(str) + else if (("var" :: "val" :: "def" :: "case" :: Nil).contains(lastToken)) + valDef(str) + else str + val suffix = if (delim(curr)) s"$curr" else "" newBuf append (toAdd + suffix) + lastToken = str prev = curr } @@ -265,6 +299,6 @@ object SyntaxHighlighting { prev = curr } - newBuf.toVector + newBuf.toIterable } } diff --git a/src/dotty/tools/dotc/repl/AmmoniteReader.scala b/src/dotty/tools/dotc/repl/AmmoniteReader.scala index 614654a28..f3b68e4b0 100644 --- a/src/dotty/tools/dotc/repl/AmmoniteReader.scala +++ b/src/dotty/tools/dotc/repl/AmmoniteReader.scala @@ -28,8 +28,8 @@ class AmmoniteReader(val interpreter: Interpreter)(implicit ctx: Context) extend val selectionFilter = GUILikeFilters.SelectionFilter(indent = 2) val multilineFilter: Filter = Filter("multilineFilter") { case TermState(lb ~: rest, b, c, _) - if (lb == 10 || lb == 13) && incompleteInput(b.mkString) => - BasicFilters.injectNewLine(b, c, rest) + if (lb == 10 || lb == 13) && incompleteInput(b.mkString) => + BasicFilters.injectNewLine(b, c, rest, indent = 2) } def readLine(prompt: String): String = { @@ -61,7 +61,7 @@ class AmmoniteReader(val interpreter: Interpreter)(implicit ctx: Context) extend if (ctx.useColors) SyntaxHighlighting(buffer) else buffer - val ansiBuffer = Ansi.Str.parse(coloredBuffer) + val ansiBuffer = Ansi.Str.parse(coloredBuffer.toVector) val (newBuffer, cursorOffset) = SelectionFilter.mangleBuffer( selectionFilter, ansiBuffer, cursor, Ansi.Reversed.On ) diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala index 163bbea16..5b3669d5e 100644 --- a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala +++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala @@ -117,22 +117,22 @@ class CompilingInterpreter( } } - private def newReporter = new ConsoleReporter(Console.in, out) { - override def printMessage(msg: String) = { - if (!delayOutput) { - out.print(/*clean*/(msg) + "\n") + private def newReporter = + new ConsoleReporter(Console.in, out) { + override def printMessage(msg: String) = + if (!delayOutput) { + out.print(/*clean*/(msg) + "\n") // Suppress clean for now for compiler messages // Otherwise we will completely delete all references to // line$object$ module classes. The previous interpreter did not // have the project because the module class was written without the final `$' // and therefore escaped the purge. We can turn this back on once // we drop the final `$' from module classes. - out.flush() - } else { - previousOutput += (/*clean*/(msg) + "\n") - } + out.flush() + } else { + previousOutput += (/*clean*/(msg) + "\n") + } } - } /** the previous requests this interpreter has processed */ private val prevRequests = new ArrayBuffer[Request]() @@ -212,8 +212,10 @@ class CompilingInterpreter( case None => Interpreter.Incomplete case Some(Nil) => Interpreter.Error // parse error or empty input case Some(tree :: Nil) if tree.isTerm && !tree.isInstanceOf[Assign] => + previousOutput.clear() // clear previous error reporting interpret(s"val $newVarName =\n$line") case Some(trees) => + previousOutput.clear() // clear previous error reporting val req = new Request(line, newLineName) if (!req.compile()) Interpreter.Error // an error happened during compilation, e.g. a type error @@ -314,9 +316,13 @@ class CompilingInterpreter( /** One line of code submitted by the user for interpretation */ private class Request(val line: String, val lineName: String)(implicit ctx: Context) { - private val trees = parse(line) match { - case Some(ts) => ts - case None => Nil + private val trees = { + val parsed = parse(line) + previousOutput.clear() // clear previous error reporting + parsed match { + case Some(ts) => ts + case None => Nil + } } /** name to use for the object that will compute "line" */ diff --git a/src/dotty/tools/dotc/repl/InterpreterLoop.scala b/src/dotty/tools/dotc/repl/InterpreterLoop.scala index 7e5dcc7f1..8b1000f2e 100644 --- a/src/dotty/tools/dotc/repl/InterpreterLoop.scala +++ b/src/dotty/tools/dotc/repl/InterpreterLoop.scala @@ -83,25 +83,16 @@ class InterpreterLoop(compiler: Compiler, config: REPL.Config)(implicit ctx: Con /** interpret all lines from a specified file */ def interpretAllFrom(filename: String): Unit = { - val fileIn = try { - new FileReader(filename) - } catch { - case _: IOException => - output.println("Error opening file: " + filename) - return - } - val oldIn = in - val oldReplay = replayCommandsRev + import java.nio.file.{Files, Paths} + import scala.collection.JavaConversions._ try { - val inFile = new BufferedReader(fileIn) - in = new SimpleReader(inFile, output, false) + val lines = Files.readAllLines(Paths.get(filename)).mkString("\n") output.println("Loading " + filename + "...") output.flush - repl() - } finally { - in = oldIn - replayCommandsRev = oldReplay - fileIn.close + interpreter.interpret(lines) + } catch { + case _: IOException => + output.println("Error opening file: " + filename) } } diff --git a/src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala b/src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala index ebbcf2148..faa97c348 100644 --- a/src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala +++ b/src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala @@ -25,12 +25,11 @@ object BasicFilters { typingFilter ) - def injectNewLine(b: Vector[Char], c: Int, rest: LazyList[Int]) = { + def injectNewLine(b: Vector[Char], c: Int, rest: LazyList[Int], indent: Int = 0) = { val (first, last) = b.splitAt(c) - TermState(rest, (first :+ '\n') ++ last, c + 1) + TermState(rest, (first :+ '\n') ++ last ++ Vector.fill(indent)(' '), c + 1 + indent) } - def navFilter = Filter.merge( Case(Up)((b, c, m) => moveUp(b, c, m.width)), Case(Down)((b, c, m) => moveDown(b, c, m.width)), diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index deb772db5..da3df6984 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -4,54 +4,142 @@ package reporting import scala.collection.mutable import util.SourcePosition -import core.Contexts._ +import core.Contexts._, core.Decorators._ import Reporter._ import java.io.{ BufferedReader, IOException, PrintWriter } import scala.reflect.internal.util._ +import printing.SyntaxHighlighting._ +import printing.Highlighting._ +import diagnostic.{ Message, MessageContainer, NoExplanation } +import diagnostic.messages._ /** - * This class implements a Reporter that displays messages on a text - * console. - */ + * This class implements a Reporter that displays messages on a text console + */ class ConsoleReporter( - reader: BufferedReader = Console.in, - writer: PrintWriter = new PrintWriter(Console.err, true)) - extends Reporter with UniqueMessagePositions with HideNonSensicalMessages { + reader: BufferedReader = Console.in, + writer: PrintWriter = new PrintWriter(Console.err, true) +) extends Reporter with UniqueMessagePositions with HideNonSensicalMessages { + + import MessageContainer._ /** maximal number of error messages to be printed */ protected def ErrorLimit = 100 - def printPos(pos: SourcePosition): Unit = - if (pos.exists) { - printMessage(pos.lineContent.stripLineEnd) - printMessage(" " * pos.column + "^") - if (pos.outer.exists) { - printMessage(s"\n... this location is in code that was inlined at ${pos.outer}:\n") - printPos(pos.outer) + /** Prints the message. */ + def printMessage(msg: String): Unit = { writer.print(msg + "\n"); writer.flush() } + + def stripColor(str: String): String = + str.replaceAll("\u001B\\[[;\\d]*m", "") + + def sourceLines(pos: SourcePosition)(implicit ctx: Context): (List[String], List[String], Int) = { + var maxLen = Int.MinValue + def render(xs: List[Int]) = + xs.map(pos.source.offsetToLine(_)) + .map { lineNbr => + val prefix = s"${lineNbr + 1} |" + maxLen = math.max(maxLen, prefix.length) + (prefix, pos.lineContent(lineNbr).stripLineEnd) + } + .map { case (prefix, line) => + val lnum = Red(" " * math.max(0, maxLen - prefix.length) + prefix) + hl"$lnum$line" } + + val (before, after) = pos.beforeAndAfterPoint + (render(before), render(after), maxLen) + } + + def columnMarker(pos: SourcePosition, offset: Int)(implicit ctx: Context) = { + val prefix = " " * (offset - 1) + val whitespace = " " * pos.startColumn + val carets = Red { + if (pos.startLine == pos.endLine) + "^" * math.max(1, pos.endColumn - pos.startColumn) + else "^" } - /** Prints the message. */ - def printMessage(msg: String): Unit = { writer.print(msg + "\n"); writer.flush() } + s"$prefix|$whitespace${carets.show}" + } + + def errorMsg(pos: SourcePosition, msg: String, offset: Int)(implicit ctx: Context) = { + val leastWhitespace = msg.lines.foldLeft(Int.MaxValue) { (minPad, line) => + val lineLength = stripColor(line).length + val padding = + math.min(math.max(0, ctx.settings.pageWidth.value - offset - lineLength), offset + pos.startColumn) + + if (padding < minPad) padding + else minPad + } + + msg.lines + .map { line => " " * (offset - 1) + "|" + (" " * (leastWhitespace - offset)) + line } + .mkString(sys.props("line.separator")) + } + + def posStr(pos: SourcePosition, diagnosticLevel: String, message: Message)(implicit ctx: Context) = + if (pos.exists) Blue({ + val file = pos.source.file.toString + val errId = + if (message.errorId != NoExplanation.ID) + s"[E${"0" * (3 - message.errorId.toString.length) + message.errorId}] " + else "" + val kind = + if (message.kind == "") diagnosticLevel + else s"${message.kind} $diagnosticLevel" + val prefix = s"-- ${errId}${kind}: $file " + + prefix + + ("-" * math.max(ctx.settings.pageWidth.value - stripColor(prefix).length, 0)) + }).show else "" /** Prints the message with the given position indication. */ - def printMessageAndPos(msg: String, pos: SourcePosition)(implicit ctx: Context): Unit = { - val posStr = if (pos.exists) s"$pos: " else "" - printMessage(posStr + msg) - printPos(pos) + def printMessageAndPos(msg: Message, pos: SourcePosition, diagnosticLevel: String)(implicit ctx: Context): Boolean = { + printMessage(posStr(pos, diagnosticLevel, msg)) + if (pos.exists) { + val (srcBefore, srcAfter, offset) = sourceLines(pos) + val marker = columnMarker(pos, offset) + val err = errorMsg(pos, msg.msg, offset) + + printMessage((srcBefore ::: marker :: err :: srcAfter).mkString("\n")) + } else printMessage(msg.msg) + true + } + + def printExplanation(m: Message)(implicit ctx: Context): Unit = { + printMessage(hl"""| + |${Blue("Explanation")} + |${Blue("===========")}""".stripMargin) + printMessage(m.explanation) + if (m.explanation.lastOption != Some('\n')) printMessage("") } - override def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { - case d: Error => - printMessageAndPos(s"error: ${d.message}", d.pos) - if (ctx.settings.prompt.value) displayPrompt() - case d: ConditionalWarning if !d.enablingOption.value => - case d: MigrationWarning => - printMessageAndPos(s"migration warning: ${d.message}", d.pos) - case d: Warning => - printMessageAndPos(s"warning: ${d.message}", d.pos) - case _ => - printMessageAndPos(d.message, d.pos) + override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { + val didPrint = m match { + case m: Error => + val didPrint = printMessageAndPos(m.contained, m.pos, "Error") + if (ctx.settings.prompt.value) displayPrompt() + didPrint + case m: ConditionalWarning if !m.enablingOption.value => + false + case m: FeatureWarning => + printMessageAndPos(m.contained, m.pos, "Feature Warning") + case m: DeprecationWarning => + printMessageAndPos(m.contained, m.pos, "Deprecation Warning") + case m: UncheckedWarning => + printMessageAndPos(m.contained, m.pos, "Unchecked Warning") + case m: MigrationWarning => + printMessageAndPos(m.contained, m.pos, "Migration Warning") + case m: Warning => + printMessageAndPos(m.contained, m.pos, "Warning") + case m: Info => + printMessageAndPos(m.contained, m.pos, "Info") + } + + if (didPrint && ctx.shouldExplain(m)) + printExplanation(m.contained) + else if (didPrint && m.contained.explanation.nonEmpty) + printMessage("\nlonger explanation available when compiling with `-explain`") } def displayPrompt(): Unit = { @@ -71,3 +159,4 @@ class ConsoleReporter( override def flush()(implicit ctx: Context): Unit = { writer.flush() } } + diff --git a/src/dotty/tools/dotc/reporting/Diagnostic.scala b/src/dotty/tools/dotc/reporting/Diagnostic.scala deleted file mode 100644 index bcf55e993..000000000 --- a/src/dotty/tools/dotc/reporting/Diagnostic.scala +++ /dev/null @@ -1,47 +0,0 @@ -package dotty.tools -package dotc -package reporting - -import util.SourcePosition - -import java.util.Optional - -object Diagnostic { - val nonSensicalStartTag = "<nonsensical>" - val nonSensicalEndTag = "</nonsensical>" -} - -class Diagnostic(msgFn: => String, val pos: SourcePosition, val level: Int) - extends Exception with interfaces.Diagnostic { - import Diagnostic._ - private var myMsg: String = null - private var myIsNonSensical: Boolean = false - - override def position: Optional[interfaces.SourcePosition] = - if (pos.exists && pos.source.exists) Optional.of(pos) else Optional.empty() - - /** The message to report */ - def message: String = { - if (myMsg == null) { - myMsg = msgFn - if (myMsg.contains(nonSensicalStartTag)) { - myIsNonSensical = true - // myMsg might be composed of several d"..." invocations -> nested nonsensical tags possible - myMsg = myMsg.replaceAllLiterally(nonSensicalStartTag, "").replaceAllLiterally(nonSensicalEndTag, "") - } - } - myMsg - } - - /** A message is non-sensical if it contains references to <nonsensical> tags. - * Such tags are inserted by the error diagnostic framework if a message - * contains references to internally generated error types. Normally we - * want to suppress error messages referring to types like this because - * they look weird and are normally follow-up errors to something that - * was diagnosed before. - */ - def isNonSensical = { message; myIsNonSensical } - - override def toString = s"$getClass at $pos: $message" - override def getMessage() = message -} diff --git a/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala b/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala index a325fe9f7..ba1ab9b33 100644 --- a/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala +++ b/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala @@ -3,6 +3,7 @@ package dotc package reporting import core.Contexts.Context +import diagnostic.MessageContainer /** * This trait implements `isHidden` so that we avoid reporting non-sensical messages. @@ -11,9 +12,9 @@ trait HideNonSensicalMessages extends Reporter { /** Hides non-sensical messages, unless we haven't reported any error yet or * `-Yshow-suppressed-errors` is set. */ - override def isHidden(d: Diagnostic)(implicit ctx: Context): Boolean = - super.isHidden(d) || { - d.isNonSensical && + override def isHidden(m: MessageContainer)(implicit ctx: Context): Boolean = + super.isHidden(m) || { + m.isNonSensical && hasErrors && // if there are no errors yet, report even if diagnostic is non-sensical !ctx.settings.YshowSuppressedErrors.value } diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index 75113d823..b38334412 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -4,44 +4,24 @@ package reporting import core.Contexts._ import util.{SourcePosition, NoSourcePosition} -import util.{SourceFile, NoSource} import core.Decorators.PhaseListDecorator import collection.mutable -import config.Settings.Setting import config.Printers import java.lang.System.currentTimeMillis import core.Mode -import interfaces.Diagnostic.{ERROR, WARNING, INFO} import dotty.tools.dotc.core.Symbols.Symbol +import diagnostic.messages._ +import diagnostic._ +import Message._ object Reporter { - class Error(msgFn: => String, pos: SourcePosition) extends Diagnostic(msgFn, pos, ERROR) - class Warning(msgFn: => String, pos: SourcePosition) extends Diagnostic(msgFn, pos, WARNING) - class Info(msgFn: => String, pos: SourcePosition) extends Diagnostic(msgFn, pos, INFO) - - abstract class ConditionalWarning(msgFn: => String, pos: SourcePosition) extends Warning(msgFn, pos) { - def enablingOption(implicit ctx: Context): Setting[Boolean] - } - class FeatureWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) { - def enablingOption(implicit ctx: Context) = ctx.settings.feature - } - class UncheckedWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) { - def enablingOption(implicit ctx: Context) = ctx.settings.unchecked - } - class DeprecationWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) { - def enablingOption(implicit ctx: Context) = ctx.settings.deprecation - } - class MigrationWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) { - def enablingOption(implicit ctx: Context) = ctx.settings.migration - } - /** Convert a SimpleReporter into a real Reporter */ def fromSimpleReporter(simple: interfaces.SimpleReporter): Reporter = new Reporter with UniqueMessagePositions with HideNonSensicalMessages { - override def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { - case d: ConditionalWarning if !d.enablingOption.value => + override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = m match { + case m: ConditionalWarning if !m.enablingOption.value => case _ => - simple.report(d) + simple.report(m) } } } @@ -57,17 +37,17 @@ trait Reporting { this: Context => def echo(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = reporter.report(new Info(msg, pos)) - def deprecationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new DeprecationWarning(msg, pos)) + def deprecationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + reporter.report(msg.deprecationWarning(pos)) - def migrationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new MigrationWarning(msg, pos)) + def migrationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + reporter.report(msg.migrationWarning(pos)) - def uncheckedWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new UncheckedWarning(msg, pos)) + def uncheckedWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + reporter.report(msg.uncheckedWarning(pos)) - def featureWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new FeatureWarning(msg, pos)) + def featureWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + reporter.report(msg.featureWarning(pos)) def featureWarning(feature: String, featureDescription: String, isScala2Feature: Boolean, featureUseSite: Symbol, required: Boolean, pos: SourcePosition): Unit = { @@ -92,26 +72,24 @@ trait Reporting { this: Context => else reporter.report(new FeatureWarning(msg, pos)) } - def warning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new Warning(msg, pos)) + def warning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + reporter.report(msg.warning(pos)) - def strictWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = + def strictWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = if (this.settings.strict.value) error(msg, pos) - else warning(msg + "\n(This would be an error under strict mode)", pos) + else warning(msg.mapMsg(_ + "\n(This would be an error under strict mode)"), pos) - def error(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = { - // println("*** ERROR: " + msg) // !!! DEBUG - reporter.report(new Error(msg, pos)) - } + def error(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + reporter.report(msg.error(pos)) - def errorOrMigrationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = + def errorOrMigrationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = if (ctx.scala2Mode) migrationWarning(msg, pos) else error(msg, pos) - def restrictionError(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - error(s"Implementation restriction: $msg", pos) + def restrictionError(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + error(msg.mapMsg(m => s"Implementation restriction: $m"), pos) - def incompleteInputError(msg: String, pos: SourcePosition = NoSourcePosition)(implicit ctx: Context): Unit = - reporter.incomplete(new Error(msg, pos))(ctx) + def incompleteInputError(msg: Message, pos: SourcePosition = NoSourcePosition)(implicit ctx: Context): Unit = + reporter.incomplete(msg.error(pos))(ctx) /** Log msg if settings.log contains the current phase. * See [[config.CompilerCommand#explainAdvanced]] for the exact meaning of @@ -198,7 +176,7 @@ trait Reporting { this: Context => abstract class Reporter extends interfaces.ReporterResult { /** Report a diagnostic */ - def doReport(d: Diagnostic)(implicit ctx: Context): Unit + def doReport(d: MessageContainer)(implicit ctx: Context): Unit /** Whether very long lines can be truncated. This exists so important * debugging information (like printing the classpath) is not rendered @@ -213,7 +191,7 @@ abstract class Reporter extends interfaces.ReporterResult { finally _truncationOK = saved } - type ErrorHandler = Diagnostic => Context => Unit + type ErrorHandler = MessageContainer => Context => Unit private var incompleteHandler: ErrorHandler = d => c => report(d)(c) def withIncompleteHandler[T](handler: ErrorHandler)(op: => T): T = { val saved = incompleteHandler @@ -242,7 +220,7 @@ abstract class Reporter extends interfaces.ReporterResult { override def default(key: String) = 0 } - def report(d: Diagnostic)(implicit ctx: Context): Unit = + def report(d: MessageContainer)(implicit ctx: Context): Unit = if (!isHidden(d)) { doReport(d)(ctx.addMode(Mode.Printing)) d match { @@ -256,10 +234,9 @@ abstract class Reporter extends interfaces.ReporterResult { } } - def incomplete(d: Diagnostic)(implicit ctx: Context): Unit = + def incomplete(d: MessageContainer)(implicit ctx: Context): Unit = incompleteHandler(d)(ctx) - /** Summary of warnings and errors */ def summary: String = { val b = new mutable.ListBuffer[String] @@ -279,7 +256,7 @@ abstract class Reporter extends interfaces.ReporterResult { } /** Returns a string meaning "n elements". */ - private def countString(n: Int, elements: String): String = n match { + protected def countString(n: Int, elements: String): String = n match { case 0 => "no " + elements + "s" case 1 => "one " + elements case 2 => "two " + elements + "s" @@ -289,7 +266,7 @@ abstract class Reporter extends interfaces.ReporterResult { } /** Should this diagnostic not be reported at all? */ - def isHidden(d: Diagnostic)(implicit ctx: Context): Boolean = ctx.mode.is(Mode.Printing) + def isHidden(m: MessageContainer)(implicit ctx: Context): Boolean = ctx.mode.is(Mode.Printing) /** Does this reporter contain not yet reported errors or warnings? */ def hasPending: Boolean = false diff --git a/src/dotty/tools/dotc/reporting/StoreReporter.scala b/src/dotty/tools/dotc/reporting/StoreReporter.scala index b7b7c1af0..e85017ed2 100644 --- a/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -4,26 +4,27 @@ package reporting import core.Contexts.Context import collection.mutable -import Reporter.{Error, Warning} import config.Printers.typr +import diagnostic.MessageContainer +import diagnostic.messages._ /** * This class implements a Reporter that stores all messages */ class StoreReporter(outer: Reporter) extends Reporter { - private var infos: mutable.ListBuffer[Diagnostic] = null + private var infos: mutable.ListBuffer[MessageContainer] = null - def doReport(d: Diagnostic)(implicit ctx: Context): Unit = { - typr.println(s">>>> StoredError: ${d.message}") // !!! DEBUG + def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { + typr.println(s">>>> StoredError: ${m.message}") // !!! DEBUG if (infos == null) infos = new mutable.ListBuffer - infos += d + infos += m } override def hasPending: Boolean = infos != null && { infos exists { - case d: Error => true - case d: Warning => true + case _: Error => true + case _: Warning => true case _ => false } } diff --git a/src/dotty/tools/dotc/reporting/ThrowingReporter.scala b/src/dotty/tools/dotc/reporting/ThrowingReporter.scala index 026453036..d8e03ab66 100644 --- a/src/dotty/tools/dotc/reporting/ThrowingReporter.scala +++ b/src/dotty/tools/dotc/reporting/ThrowingReporter.scala @@ -4,6 +4,8 @@ package reporting import core.Contexts.Context import collection.mutable +import diagnostic.MessageContainer +import diagnostic.messages.Error import Reporter._ /** @@ -11,8 +13,8 @@ import Reporter._ * info to the underlying reporter. */ class ThrowingReporter(reportInfo: Reporter) extends Reporter { - def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { - case _: Error => throw d - case _ => reportInfo.doReport(d) + def doReport(m: MessageContainer)(implicit ctx: Context): Unit = m match { + case _: Error => throw m + case _ => reportInfo.doReport(m) } } diff --git a/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala b/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala index 32554e6b6..6fd971c2a 100644 --- a/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala +++ b/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala @@ -5,11 +5,10 @@ package reporting import scala.collection.mutable import util.{SourcePosition, SourceFile} import core.Contexts.Context +import diagnostic.MessageContainer -/** - * This trait implements `isHidden` so that multiple messages per position - * are suppressed, unless they are of increasing severity. - */ +/** This trait implements `isHidden` so that multiple messages per position + * are suppressed, unless they are of increasing severity. */ trait UniqueMessagePositions extends Reporter { private val positions = new mutable.HashMap[(SourceFile, Int), Int] @@ -17,13 +16,17 @@ trait UniqueMessagePositions extends Reporter { /** Logs a position and returns true if it was already logged. * @note Two positions are considered identical for logging if they have the same point. */ - override def isHidden(d: Diagnostic)(implicit ctx: Context): Boolean = - super.isHidden(d) || { - d.pos.exists && { - positions get (ctx.source, d.pos.point) match { - case Some(level) if level >= d.level => true - case _ => positions((ctx.source, d.pos.point)) = d.level; false + override def isHidden(m: MessageContainer)(implicit ctx: Context): Boolean = + super.isHidden(m) || { + m.pos.exists && { + var shouldHide = false + for (pos <- m.pos.start to m.pos.end) { + positions get (ctx.source, pos) match { + case Some(level) if level >= m.level => shouldHide = true + case _ => positions((ctx.source, pos)) = m.level + } } + shouldHide } } } diff --git a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala new file mode 100644 index 000000000..8b1f65673 --- /dev/null +++ b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -0,0 +1,106 @@ +package dotty.tools +package dotc +package reporting +package diagnostic + +import util.SourcePosition +import core.Contexts.Context + +object Message { + /** This implicit conversion provides a fallback for error messages that have + * not yet been ported to the new scheme. Comment out this `implicit def` to + * see where old errors still exist + */ + implicit def toNoExplanation(str: String): Message = + new NoExplanation(str) +} + +/** A `Message` contains all semantic information necessary to easily + * comprehend what caused the message to be logged. Each message can be turned + * into a `MessageContainer` which contains the log level and can later be + * consumed by a subclass of `Reporter`. + * + * @param errorId a unique number identifying the message, this will later be + * used to reference documentation online + */ +abstract class Message(val errorId: Int) { self => + import messages._ + + /** The `msg` contains the diagnostic message e.g: + * + * > expected: String + * > found: Int + * + * This message wil be placed underneath the position given by the enclosing + * `MessageContainer` + */ + def msg: String + + /** The kind of the error message is something like "Syntax" or "Type + * Mismatch" + */ + def kind: String + + /** The explanation should provide a detailed description of why the error + * occurred and use examples from the user's own code to illustrate how to + * avoid these errors. + */ + def explanation: String + + /** It is possible to map `msg` to add details, this is at the loss of + * precision since the type of the resulting `Message` won't be original + * extending class + * + * @return a `Message` with the mapped message + */ + def mapMsg(f: String => String) = new Message(errorId) { + val msg = f(self.msg) + val kind = self.kind + val explanation = self.explanation + } + + /** Enclose this message in an `Error` container */ + def error(pos: SourcePosition) = + new Error(self, pos) + + /** Enclose this message in an `Warning` container */ + def warning(pos: SourcePosition) = + new Warning(self, pos) + + /** Enclose this message in an `Info` container */ + def info(pos: SourcePosition) = + new Info(self, pos) + + /** Enclose this message in an `FeatureWarning` container */ + def featureWarning(pos: SourcePosition) = + new FeatureWarning(self, pos) + + /** Enclose this message in an `UncheckedWarning` container */ + def uncheckedWarning(pos: SourcePosition) = + new UncheckedWarning(self, pos) + + /** Enclose this message in an `DeprecationWarning` container */ + def deprecationWarning(pos: SourcePosition) = + new DeprecationWarning(self, pos) + + /** Enclose this message in an `MigrationWarning` container */ + def migrationWarning(pos: SourcePosition) = + new MigrationWarning(self, pos) +} + +/** The fallback `Message` containing no explanation and having no `kind` */ +class NoExplanation(val msg: String) extends Message(NoExplanation.ID) { + val explanation = "" + val kind = "" +} + +/** The extractor for `NoExplanation` can be used to check whether any error + * lacks an explanation + */ +object NoExplanation { + final val ID = -1 + + def unapply(m: Message): Option[Message] = + if (m.explanation == "") Some(m) + else None +} diff --git a/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala new file mode 100644 index 000000000..7fd50bfdc --- /dev/null +++ b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala @@ -0,0 +1,74 @@ +package dotty.tools +package dotc +package reporting +package diagnostic + +import util.SourcePosition +import core.Contexts.Context + +import java.util.Optional + +object MessageContainer { + val nonSensicalStartTag = "<nonsensical>" + val nonSensicalEndTag = "</nonsensical>" + + implicit class MessageContext(val c: Context) extends AnyVal { + def shouldExplain(cont: MessageContainer): Boolean = { + implicit val ctx: Context = c + cont.contained.explanation match { + case "" => false + case _ => ctx.settings.explain.value + } + } + } +} + +class MessageContainer( + msgFn: => Message, + val pos: SourcePosition, + val level: Int +) extends Exception with interfaces.Diagnostic { + import MessageContainer._ + private var myMsg: String = null + private var myIsNonSensical: Boolean = false + private var myContained: Message = null + + override def position: Optional[interfaces.SourcePosition] = + if (pos.exists && pos.source.exists) Optional.of(pos) else Optional.empty() + + /** The message to report */ + def message: String = { + if (myMsg == null) { + myMsg = contained.msg.replaceAll("\u001B\\[[;\\d]*m", "") + if (myMsg.contains(nonSensicalStartTag)) { + myIsNonSensical = true + // myMsg might be composed of several d"..." invocations -> nested + // nonsensical tags possible + myMsg = + myMsg + .replaceAllLiterally(nonSensicalStartTag, "") + .replaceAllLiterally(nonSensicalEndTag, "") + } + } + myMsg + } + + def contained: Message = { + if (myContained == null) + myContained = msgFn + + myContained + } + + /** A message is non-sensical if it contains references to <nonsensical> + * tags. Such tags are inserted by the error diagnostic framework if a + * message contains references to internally generated error types. Normally + * we want to suppress error messages referring to types like this because + * they look weird and are normally follow-up errors to something that was + * diagnosed before. + */ + def isNonSensical = { message; myIsNonSensical } + + override def toString = s"$getClass at $pos: ${message}" + override def getMessage() = message +} diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala new file mode 100644 index 000000000..9a02e0b04 --- /dev/null +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -0,0 +1,321 @@ +package dotty.tools +package dotc +package reporting +package diagnostic + +import dotc.core._ +import Contexts.Context, Decorators._, Symbols._, Names._, Types._ +import util.{SourceFile, NoSource} +import util.{SourcePosition, NoSourcePosition} +import config.Settings.Setting +import interfaces.Diagnostic.{ERROR, WARNING, INFO} +import printing.SyntaxHighlighting._ +import printing.Formatting + +object messages { + + // `MessageContainer`s to be consumed by `Reporter` ---------------------- // + class Error( + msgFn: => Message, + pos: SourcePosition + ) extends MessageContainer(msgFn, pos, ERROR) + + class Warning( + msgFn: => Message, + pos: SourcePosition + ) extends MessageContainer(msgFn, pos, WARNING) + + class Info( + msgFn: => Message, + pos: SourcePosition + ) extends MessageContainer(msgFn, pos, INFO) + + abstract class ConditionalWarning( + msgFn: => Message, + pos: SourcePosition + ) extends Warning(msgFn, pos) { + def enablingOption(implicit ctx: Context): Setting[Boolean] + } + + class FeatureWarning( + msgFn: => Message, + pos: SourcePosition + ) extends ConditionalWarning(msgFn, pos) { + def enablingOption(implicit ctx: Context) = ctx.settings.feature + } + + class UncheckedWarning( + msgFn: => Message, + pos: SourcePosition + ) extends ConditionalWarning(msgFn, pos) { + def enablingOption(implicit ctx: Context) = ctx.settings.unchecked + } + + class DeprecationWarning( + msgFn: => Message, + pos: SourcePosition + ) extends ConditionalWarning(msgFn, pos) { + def enablingOption(implicit ctx: Context) = ctx.settings.deprecation + } + + class MigrationWarning( + msgFn: => Message, + pos: SourcePosition + ) extends ConditionalWarning(msgFn, pos) { + def enablingOption(implicit ctx: Context) = ctx.settings.migration + } + + /** Messages + * ======== + * The role of messages is to provide the necessary details for a simple to + * understand diagnostic event. Each message can be turned into a message + * container (one of the above) by calling the appropriate method on them. + * For instance: + * + * ```scala + * EmptyCatchBlock(tree).error(pos) // res: Error + * EmptyCatchBlock(tree).warning(pos) // res: Warning + * ``` + */ + import dotc.ast.Trees._ + import dotc.ast.untpd + + // Syntax Errors ---------------------------------------------------------- // + abstract class EmptyCatchOrFinallyBlock(tryBody: untpd.Tree, errNo: Int)(implicit ctx: Context) + extends Message(errNo) { + val explanation = { + val tryString = tryBody match { + case Block(Nil, untpd.EmptyTree) => "{}" + case _ => tryBody.show + } + + val code1 = + s"""|import scala.util.control.NonFatal + | + |try $tryString catch { + | case NonFatal(e) => ??? + |}""".stripMargin + + val code2 = + s"""|try $tryString finally { + | // perform your cleanup here! + |}""".stripMargin + + hl"""|A ${"try"} expression should be followed by some mechanism to handle any exceptions + |thrown. Typically a ${"catch"} expression follows the ${"try"} and pattern matches + |on any expected exceptions. For example: + | + |$code1 + | + |It is also possible to follow a ${"try"} immediately by a ${"finally"} - letting the + |exception propagate - but still allowing for some clean up in ${"finally"}: + | + |$code2 + | + |It is recommended to use the ${"NonFatal"} extractor to catch all exceptions as it + |correctly handles transfer functions like ${"return"}.""".stripMargin + } + } + + case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context) + extends EmptyCatchOrFinallyBlock(tryBody, 1) { + val kind = "Syntax" + val msg = + hl"""|The ${"catch"} block does not contain a valid expression, try + |adding a case like - `${"case e: Exception =>"}` to the block""".stripMargin + } + + case class EmptyCatchAndFinallyBlock(tryBody: untpd.Tree)(implicit ctx: Context) + extends EmptyCatchOrFinallyBlock(tryBody, 2) { + val kind = "Syntax" + val msg = + hl"""|A ${"try"} without ${"catch"} or ${"finally"} is equivalent to putting + |its body in a block; no exceptions are handled.""".stripMargin + } + + case class DeprecatedWithOperator()(implicit ctx: Context) + extends Message(3) { + val kind = "Syntax" + val msg = + hl"""${"with"} as a type operator has been deprecated; use `&' instead""" + val explanation = + hl"""|Dotty introduces intersection types - `&' types. These replace the + |use of the ${"with"} keyword. There are a few differences in + |semantics between intersection types and using `${"with"}'.""".stripMargin + } + + case class CaseClassMissingParamList(cdef: untpd.TypeDef)(implicit ctx: Context) + extends Message(4) { + val kind = "Syntax" + val msg = + hl"""|A ${"case class"} must have at least one parameter list""" + + val explanation = + hl"""|${cdef.name} must have at least one parameter list, if you would rather + |have a singleton representation of ${cdef.name}, use a "${"case object"}". + |Or, add an explicit `()' as a parameter list to ${cdef.name}.""".stripMargin + } + + + // Type Errors ------------------------------------------------------------ // + case class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context) + extends Message(5) { + val kind = "Naming" + val msg = em"duplicate pattern variable: `${bind.name}`" + + val explanation = { + val pat = tree.pat.show + val guard = tree.guard match { + case untpd.EmptyTree => "" + case guard => s"if ${guard.show}" + } + + val body = tree.body match { + case Block(Nil, untpd.EmptyTree) => "" + case body => s" ${body.show}" + } + + val caseDef = s"case $pat$guard => $body" + + hl"""|For each ${"case"} bound variable names have to be unique. In: + | + |$caseDef + | + |`${bind.name}` is not unique. Rename one of the bound variables!""".stripMargin + } + } + + case class MissingIdent(tree: untpd.Ident, treeKind: String, name: String)(implicit ctx: Context) + extends Message(6) { + val kind = "Missing Identifier" + val msg = em"not found: $treeKind$name" + + val explanation = { + hl"""|An identifier for `$treeKind$name` is missing. This means that something + |has either been misspelt or you're forgetting an import""".stripMargin + } + } + + case class TypeMismatch(found: Type, expected: Type, whyNoMatch: String = "", implicitFailure: String = "")(implicit ctx: Context) + extends Message(7) { + val kind = "Type Mismatch" + val msg = { + val (where, printCtx) = Formatting.disambiguateTypes(found, expected) + val (fnd, exp) = Formatting.typeDiff(found, expected)(printCtx) + s"""|found: $fnd + |required: $exp + | + |$where""".stripMargin + whyNoMatch + implicitFailure + } + + val explanation = "" + } + + case class NotAMember(site: Type, name: Name, selected: String)(implicit ctx: Context) + extends Message(8) { + val kind = "Member Not Found" + + val msg = { + import core.Flags._ + val maxDist = 3 + val decls = site.decls.flatMap { sym => + if (sym.is(Synthetic | PrivateOrLocal) || sym.isConstructor) Nil + else List((sym.name.show, sym)) + } + + // Calculate Levenshtein distance + def distance(n1: Iterable[_], n2: Iterable[_]) = + n1.foldLeft(List.range(0, n2.size)) { (prev, x) => + (prev zip prev.tail zip n2).scanLeft(prev.head + 1) { + case (h, ((d, v), y)) => math.min( + math.min(h + 1, v + 1), + if (x == y) d else d + 1 + ) + } + }.last + + // Count number of wrong characters + def incorrectChars(x: (String, Int, Symbol)): (String, Symbol, Int) = { + val (currName, _, sym) = x + val matching = name.show.zip(currName).foldLeft(0) { + case (acc, (x,y)) => if (x != y) acc + 1 else acc + } + (currName, sym, matching) + } + + // Get closest match in `site` + val closest = + decls + .map { case (n, sym) => (n, distance(n, name.show), sym) } + .collect { case (n, dist, sym) if dist <= maxDist => (n, dist, sym) } + .groupBy(_._2).toList + .sortBy(_._1) + .headOption.map(_._2).getOrElse(Nil) + .map(incorrectChars).toList + .sortBy(_._3) + .take(1).map { case (n, sym, _) => (n, sym) } + + val siteName = site match { + case site: NamedType => site.name.show + case site => i"$site" + } + + val closeMember = closest match { + case (n, sym) :: Nil => hl""" - did you mean `${s"$siteName.$n"}`?""" + case Nil => "" + case _ => assert( + false, + "Could not single out one distinct member to match on input with" + ) + } + + ex"$selected `$name` is not a member of $site$closeMember" + } + + val explanation = "" + } + + case class EarlyDefinitionsNotSupported()(implicit ctx:Context) extends Message(9) { + val kind = "Syntax" + + val msg = "early definitions are not supported; use trait parameters instead" + + val code1 = + """|trait Logging { + | val f: File + | f.open() + | onExit(f.close()) + | def log(msg: String) = f.write(msg) + |} + | + |class B extends Logging { + | val f = new File("log.data") // triggers a null pointer exception + |} + | + |class C extends { + | val f = new File("log.data") // early definition gets around the null pointer exception + |} with Logging""".stripMargin + + val code2 = + """|trait Logging(f: File) { + | f.open() + | onExit(f.close()) + | def log(msg: String) = f.write(msg) + |} + | + |class C extends Logging(new File("log.data"))""".stripMargin + + val explanation = + hl"""Earlier versions of Scala did not support trait parameters and "early definitions" (also known as "early initializers") + |were used as an alternative. + | + |Example of old syntax: + | + |$code1 + | + |The above code can now be written as: + | + |$code2 + |""".stripMargin + } +} diff --git a/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/src/dotty/tools/dotc/sbt/ExtractAPI.scala index a7b18b6d6..bc8528c05 100644 --- a/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -363,7 +363,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder val apiTycon = simpleType(tycon) val apiArgs = args.map(processArg) new api.Parameterized(apiTycon, apiArgs.toArray) - case TypeLambda(tparams, res) => + case PolyType(tparams, res) => val apiTparams = tparams.map(apiTypeParameter) val apiRes = apiType(res) new api.Polymorphic(apiRes, apiTparams.toArray) diff --git a/src/dotty/tools/dotc/transform/ExplicitSelf.scala b/src/dotty/tools/dotc/transform/ExplicitSelf.scala index 618a0f108..7bb65e575 100644 --- a/src/dotty/tools/dotc/transform/ExplicitSelf.scala +++ b/src/dotty/tools/dotc/transform/ExplicitSelf.scala @@ -20,12 +20,21 @@ import Flags._ * * where `S` is the self type of `C`. * See run/i789.scala for a test case why this is needed. + * + * Also replaces idents referring to the self type with ThisTypes. */ class ExplicitSelf extends MiniPhaseTransform { thisTransform => import ast.tpd._ override def phaseName = "explicitSelf" + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = tree.tpe match { + case tp: ThisType => + ctx.debuglog(s"owner = ${ctx.owner}, context = ${ctx}") + This(tp.cls) withPos tree.pos + case _ => tree + } + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = tree match { case Select(thiz: This, name) if name.isTermName => val cls = thiz.symbol.asClass diff --git a/src/dotty/tools/dotc/transform/FullParameterization.scala b/src/dotty/tools/dotc/transform/FullParameterization.scala index d2052d8cb..6c69c735b 100644 --- a/src/dotty/tools/dotc/transform/FullParameterization.scala +++ b/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -111,13 +111,13 @@ trait FullParameterization { } /** Replace class type parameters by the added type parameters of the polytype `pt` */ - def mapClassParams(tp: Type, pt: GenericType): Type = { + def mapClassParams(tp: Type, pt: PolyType): Type = { val classParamsRange = (mtparamCount until mtparamCount + ctparams.length).toList tp.substDealias(ctparams, classParamsRange map (PolyParam(pt, _))) } /** The bounds for the added type parameters of the polytype `pt` */ - def mappedClassBounds(pt: GenericType): List[TypeBounds] = + def mappedClassBounds(pt: PolyType): List[TypeBounds] = ctparams.map(tparam => mapClassParams(tparam.info, pt).bounds) info match { diff --git a/src/dotty/tools/dotc/transform/LambdaLift.scala b/src/dotty/tools/dotc/transform/LambdaLift.scala index 18b030913..19fb3dd0c 100644 --- a/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -121,7 +121,10 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet = f.getOrElseUpdate(sym, newSymSet) - def freeVars(sym: Symbol): List[Symbol] = free.getOrElse(sym, Nil).toList + def freeVars(sym: Symbol): List[Symbol] = free get sym match { + case Some(set) => set.toList + case None => Nil + } def proxyOf(sym: Symbol, fv: Symbol) = proxyMap.getOrElse(sym, Map.empty)(fv) diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 49c0eabec..8636d5084 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -240,17 +240,21 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer { val isDefined = extractorMemberType(prev.tpe, nme.isDefined) if ((isDefined isRef defn.BooleanClass) && getTp.exists) { - val tmpSym = freshSym(prev.pos, prev.tpe, "o") - val prevValue = ref(tmpSym).select("get".toTermName).ensureApplied + // isDefined and get may be overloaded + val getDenot = prev.tpe.member(nme.get).suchThat(_.info.isParameterless) + val isDefinedDenot = prev.tpe.member(nme.isDefined).suchThat(_.info.isParameterless) - Block( - List(ValDef(tmpSym, prev)), - // must be isEmpty and get as we don't control the target of the call (prev is an extractor call) - ifThenElseZero( - ref(tmpSym).select(nme.isDefined), - Block(List(ValDef(b.asTerm, prevValue)), next) - ) + val tmpSym = freshSym(prev.pos, prev.tpe, "o") + val prevValue = ref(tmpSym).select(getDenot.symbol).ensureApplied + + Block( + List(ValDef(tmpSym, prev)), + // must be isEmpty and get as we don't control the target of the call (prev is an extractor call) + ifThenElseZero( + ref(tmpSym).select(isDefinedDenot.symbol), + Block(List(ValDef(b.asTerm, prevValue)), next) ) + ) } else { assert(defn.isProductSubType(prev.tpe)) val nullCheck: Tree = prev.select(defn.Object_ne).appliedTo(Literal(Constant(null))) diff --git a/src/dotty/tools/dotc/transform/Splitter.scala b/src/dotty/tools/dotc/transform/Splitter.scala index efcf95ede..d62be1a82 100644 --- a/src/dotty/tools/dotc/transform/Splitter.scala +++ b/src/dotty/tools/dotc/transform/Splitter.scala @@ -6,25 +6,34 @@ import ast.Trees._ import core._ import Contexts._, Types._, Decorators._, Denotations._, Symbols._, SymDenotations._, Names._ -/** This transform makes sure every identifier and select node - * carries a symbol. To do this, certain qualifiers with a union type - * have to be "splitted" with a type test. - * - * For now, only self references are treated. +/** Distribute applications into Block and If nodes */ class Splitter extends MiniPhaseTransform { thisTransform => import ast.tpd._ override def phaseName: String = "splitter" - /** Replace self referencing idents with ThisTypes. */ - override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = tree.tpe match { - case tp: ThisType => - ctx.debuglog(s"owner = ${ctx.owner}, context = ${ctx}") - This(tp.cls) withPos tree.pos - case _ => tree + /** Distribute arguments among splitted branches */ + def distribute(tree: GenericApply[Type], rebuild: (Tree, List[Tree]) => Context => Tree)(implicit ctx: Context) = { + def recur(fn: Tree): Tree = fn match { + case Block(stats, expr) => Block(stats, recur(expr)) + case If(cond, thenp, elsep) => If(cond, recur(thenp), recur(elsep)) + case _ => rebuild(fn, tree.args)(ctx) withPos tree.pos + } + recur(tree.fun) } + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo) = + distribute(tree, typeApply) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + distribute(tree, apply) + + private val typeApply = (fn: Tree, args: List[Tree]) => (ctx: Context) => TypeApply(fn, args)(ctx) + private val apply = (fn: Tree, args: List[Tree]) => (ctx: Context) => Apply(fn, args)(ctx) + +/* The following is no longer necessary, since we select members on the join of an or type: + * /** If we select a name, make sure the node has a symbol. * If necessary, split the qualifier with type tests. * Example: Assume: @@ -108,23 +117,5 @@ class Splitter extends MiniPhaseTransform { thisTransform => evalOnce(qual)(qual => choose(qual, candidates(qual.tpe))) } } - - /** Distribute arguments among splitted branches */ - def distribute(tree: GenericApply[Type], rebuild: (Tree, List[Tree]) => Context => Tree)(implicit ctx: Context) = { - def recur(fn: Tree): Tree = fn match { - case Block(stats, expr) => Block(stats, recur(expr)) - case If(cond, thenp, elsep) => If(cond, recur(thenp), recur(elsep)) - case _ => rebuild(fn, tree.args)(ctx) withPos tree.pos - } - recur(tree.fun) - } - - override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo) = - distribute(tree, typeApply) - - override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = - distribute(tree, apply) - - private val typeApply = (fn: Tree, args: List[Tree]) => (ctx: Context) => TypeApply(fn, args)(ctx) - private val apply = (fn: Tree, args: List[Tree]) => (ctx: Context) => Apply(fn, args)(ctx) +*/ } diff --git a/src/dotty/tools/dotc/transform/TailRec.scala b/src/dotty/tools/dotc/transform/TailRec.scala index b345dda61..d99a48af3 100644 --- a/src/dotty/tools/dotc/transform/TailRec.scala +++ b/src/dotty/tools/dotc/transform/TailRec.scala @@ -145,17 +145,22 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete }) Block(List(labelDef), ref(label).appliedToArgss(vparamss0.map(_.map(x=> ref(x.symbol))))) }} else { - if (mandatory) - ctx.error("TailRec optimisation not applicable, method not tail recursive", dd.pos) + if (mandatory) ctx.error( + "TailRec optimisation not applicable, method not tail recursive", + // FIXME: want to report this error on `dd.namePos`, but + // because of extension method getting a weird pos, it is + // better to report on symbol so there's no overlap + sym.pos + ) dd.rhs } }) } case d: DefDef if d.symbol.hasAnnotation(defn.TailrecAnnot) || methodsWithInnerAnnots.contains(d.symbol) => - ctx.error("TailRec optimisation not applicable, method is neither private nor final so can be overridden", d.pos) + ctx.error("TailRec optimisation not applicable, method is neither private nor final so can be overridden", sym.pos) d case d if d.symbol.hasAnnotation(defn.TailrecAnnot) || methodsWithInnerAnnots.contains(d.symbol) => - ctx.error("TailRec optimisation not applicable, not a method", d.pos) + ctx.error("TailRec optimisation not applicable, not a method", sym.pos) d case _ => tree } @@ -247,7 +252,10 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete } else targs val method = if (callTargs.nonEmpty) TypeApply(Ident(label.termRef), callTargs) else Ident(label.termRef) - val thisPassed = if(this.method.owner.isClass) method appliedTo(receiver.ensureConforms(method.tpe.widen.firstParamTypes.head)) else method + val thisPassed = + if (this.method.owner.isClass) + method.appliedTo(receiver.ensureConforms(method.tpe.widen.firstParamTypes.head)) + else method val res = if (thisPassed.tpe.widen.isParameterless) thisPassed diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index 4b3927ccf..808178369 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -133,10 +133,7 @@ class TreeChecker extends Phase with SymTransformer { catch { case NonFatal(ex) => //TODO CHECK. Check that we are bootstrapped implicit val ctx: Context = checkingCtx - ctx.echo(i"*** error while checking ${ctx.compilationUnit} after phase ${checkingCtx.phase.prev} ***") - ctx.echo(ex.toString) - ctx.echo(ex.getStackTrace.take(30).deep.mkString("\n")) - ctx.echo("<<<") + println(i"*** error while checking ${ctx.compilationUnit} after phase ${checkingCtx.phase.prev} ***") throw ex } } @@ -331,8 +328,30 @@ class TreeChecker extends Phase with SymTransformer { checkNotRepeated(super.typedIdent(tree, pt)) } + /** Makes sure the symbol in the tree can be approximately reconstructed by + * calling `member` on the qualifier type. + * Approximately means: The two symbols might be different but one still overrides the other. + */ override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { assert(tree.isTerm || !ctx.isAfterTyper, tree.show + " at " + ctx.phase) + val tpe = tree.typeOpt + val sym = tree.symbol + if (!tpe.isInstanceOf[WithFixedSym] && sym.exists && !sym.is(Private)) { + val qualTpe = tree.qualifier.typeOpt + val member = + if (sym.is(Private)) qualTpe.member(tree.name) + else qualTpe.nonPrivateMember(tree.name) + val memberSyms = member.alternatives.map(_.symbol) + assert(memberSyms.exists(mbr => + sym == mbr || + sym.overriddenSymbol(mbr.owner.asClass) == mbr || + mbr.overriddenSymbol(sym.owner.asClass) == sym), + ex"""symbols differ for $tree + |was : $sym + |alternatives by type: $memberSyms%, % of types ${memberSyms.map(_.info)}%, % + |qualifier type : ${tree.qualifier.typeOpt} + |tree type : ${tree.typeOpt} of class ${tree.typeOpt.getClass}""") + } checkNotRepeated(super.typedSelect(tree, pt)) } @@ -425,10 +444,11 @@ class TreeChecker extends Phase with SymTransformer { !tree.isEmpty && !isPrimaryConstructorReturn && !pt.isInstanceOf[FunProto]) - assert(tree.tpe <:< pt, - i"""error at ${sourcePos(tree.pos)} - |${err.typeMismatchStr(tree.tpe, pt)} - |tree = $tree""") + assert(tree.tpe <:< pt, { + val mismatch = err.typeMismatchMsg(tree.tpe, pt) + i"""|${mismatch.msg} + |tree = $tree""".stripMargin + }) tree } } diff --git a/src/dotty/tools/dotc/transform/TreeTransform.scala b/src/dotty/tools/dotc/transform/TreeTransform.scala index 52a3ad94e..45fa3d607 100644 --- a/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -42,7 +42,7 @@ object TreeTransforms { * the general dispatch overhead as opposed to the concrete work done in transformations. So that leaves us with * 0.2sec, or roughly 600M processor cycles. * - * Now, to the amount of work that needs to be done. The codebase produces of about 250'000 trees after typechecking. + * Now, to the amount of work that needs to be done. The codebase produces an average of about 250'000 trees after typechecking. * Transformations are likely to make this bigger so let's assume 300K trees on average. We estimate to have about 100 * micro-transformations. Let's say 5 transformation groups of 20 micro-transformations each. (by comparison, * scalac has in excess of 20 phases, and most phases do multiple transformations). There are then 30M visits @@ -208,7 +208,7 @@ object TreeTransforms { if (cls.getDeclaredMethods.exists(_.getName == name)) cls != classOf[TreeTransform] else hasRedefinedMethod(cls.getSuperclass, name) - /** Create an index array `next` of size one larger than teh size of `transforms` such that + /** Create an index array `next` of size one larger than the size of `transforms` such that * for each index i, `next(i)` is the smallest index j such that * * i <= j diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 2c9039db1..56595a637 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -27,6 +27,7 @@ import collection.mutable import config.Printers.{typr, unapp, overload} import TypeApplications._ import language.implicitConversions +import reporting.diagnostic.Message object Applications { import tpd._ @@ -132,10 +133,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic => protected def harmonizeArgs(args: List[TypedArg]): List[TypedArg] /** Signal failure with given message at position of given argument */ - protected def fail(msg: => String, arg: Arg): Unit + protected def fail(msg: => Message, arg: Arg): Unit /** Signal failure with given message at position of the application itself */ - protected def fail(msg: => String): Unit + protected def fail(msg: => Message): Unit protected def appPos: Position @@ -186,7 +187,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => // it might be healed by an implicit conversion assert(ctx.typerState.constraint eq savedConstraint) else - fail(err.typeMismatchStr(methType.resultType, resultType)) + fail(err.typeMismatchMsg(methType.resultType, resultType)) } // match all arguments with corresponding formal parameters matchArgs(orderedArgs, methType.paramTypes, 0) @@ -388,9 +389,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def addArg(arg: TypedArg, formal: Type) = ok = ok & isCompatible(argType(arg, formal), formal) def makeVarArg(n: Int, elemFormal: Type) = {} - def fail(msg: => String, arg: Arg) = + def fail(msg: => Message, arg: Arg) = ok = false - def fail(msg: => String) = + def fail(msg: => Message) = ok = false def appPos = NoPosition lazy val normalizedFun = ref(methRef) @@ -455,12 +456,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => override def appPos = app.pos - def fail(msg: => String, arg: Trees.Tree[T]) = { + def fail(msg: => Message, arg: Trees.Tree[T]) = { ctx.error(msg, arg.pos) ok = false } - def fail(msg: => String) = { + def fail(msg: => Message) = { ctx.error(msg, app.pos) ok = false } diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index b02b0ad21..7899174f5 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -45,14 +45,14 @@ object Checking { for ((arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate)) ctx.error( ex"Type argument ${arg.tpe} does not conform to $which bound $bound ${err.whyNoMatchStr(arg.tpe, bound)}", - arg.pos) + arg.pos.focus) } /** Check that type arguments `args` conform to corresponding bounds in `poly` * Note: This does not check the bounds of AppliedTypeTrees. These * are handled by method checkBounds in FirstTransform */ - def checkBounds(args: List[tpd.Tree], poly: GenericType)(implicit ctx: Context): Unit = + def checkBounds(args: List[tpd.Tree], poly: PolyType)(implicit ctx: Context): Unit = checkBounds(args, poly.paramBounds, _.substParams(poly, _)) /** If type is a higher-kinded application with wildcard arguments, @@ -63,7 +63,7 @@ object Checking { def checkWildcardHKApply(tp: Type, pos: Position)(implicit ctx: Context): Unit = tp match { case tp @ HKApply(tycon, args) if args.exists(_.isInstanceOf[TypeBounds]) => tycon match { - case tycon: TypeLambda => + case tycon: PolyType => ctx.errorOrMigrationWarning( ex"unreducible application of higher-kinded type $tycon to wildcard arguments", pos) @@ -98,9 +98,9 @@ object Checking { checkWildcardHKApply(tycon.tpe.appliedTo(args.map(_.tpe)), tree.pos) checkValidIfHKApply(ctx.addMode(Mode.AllowLambdaWildcardApply)) case Select(qual, name) if name.isTypeName => - checkRealizable(qual.tpe, qual.pos) + checkRealizable(qual.tpe, qual.pos.focus) case SingletonTypeTree(ref) => - checkRealizable(ref.tpe, ref.pos) + checkRealizable(ref.tpe, ref.pos.focus) case _ => } traverseChildren(tree) @@ -378,7 +378,7 @@ object Checking { if (tp.symbol.is(Private) && !accessBoundary(sym).isContainedIn(tp.symbol.owner)) { errors = (em"non-private $sym refers to private ${tp.symbol}\n in its type signature ${sym.info}", - pos) :: errors + sym.pos) :: errors tp } else mapOver(tp) @@ -542,6 +542,13 @@ trait Checking { errorTree(tpt, ex"missing type parameter for ${tpt.tpe}") } else tpt + + /** Check that `tpt` does not refer to a singleton type */ + def checkNotSingleton(tpt: Tree, where: String)(implicit ctx: Context): Tree = + if (tpt.tpe.isInstanceOf[SingletonType]) { + errorTree(tpt, ex"Singleton type ${tpt.tpe} is not allowed $where") + } + else tpt } trait NoChecking extends Checking { @@ -556,4 +563,5 @@ trait NoChecking extends Checking { override def checkNoDoubleDefs(cls: Symbol)(implicit ctx: Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context) = () override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt + override def checkNotSingleton(tpt: Tree, where: String)(implicit ctx: Context): Tree = tpt } diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index ad84ff583..1d22dc646 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -8,19 +8,20 @@ import Trees._ import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._ import Applications._, Implicits._, Flags._ import util.Positions._ -import reporting.Diagnostic import printing.{Showable, RefinedPrinter} import scala.collection.mutable import java.util.regex.Matcher.quoteReplacement +import reporting.diagnostic.Message +import reporting.diagnostic.messages._ object ErrorReporting { import tpd._ - def errorTree(tree: untpd.Tree, msg: => String)(implicit ctx: Context): tpd.Tree = + def errorTree(tree: untpd.Tree, msg: => Message)(implicit ctx: Context): tpd.Tree = tree withType errorType(msg, tree.pos) - def errorType(msg: => String, pos: Position)(implicit ctx: Context): ErrorType = { + def errorType(msg: => Message, pos: Position)(implicit ctx: Context): ErrorType = { ctx.error(msg, pos) ErrorType } @@ -101,7 +102,7 @@ object ErrorReporting { def patternConstrStr(tree: Tree): String = ??? def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailure = NoImplicitMatches): Tree = - errorTree(tree, typeMismatchStr(normalize(tree.tpe, pt), pt) + implicitFailure.postscript) + errorTree(tree, typeMismatchMsg(normalize(tree.tpe, pt), pt, implicitFailure.postscript)) /** A subtype log explaining why `found` does not conform to `expected` */ def whyNoMatchStr(found: Type, expected: Type) = @@ -110,7 +111,7 @@ object ErrorReporting { else "" - def typeMismatchStr(found: Type, expected: Type) = { + def typeMismatchMsg(found: Type, expected: Type, postScript: String = "") = { // replace constrained polyparams and their typevars by their bounds where possible object reported extends TypeMap { def setVariance(v: Int) = variance = v @@ -132,9 +133,7 @@ object ErrorReporting { val found1 = reported(found) reported.setVariance(-1) val expected1 = reported(expected) - ex"""type mismatch: - | found : $found1 - | required: $expected1""" + whyNoMatchStr(found, expected) + TypeMismatch(found1, expected1, whyNoMatchStr(found, expected), postScript) } /** Format `raw` implicitNotFound argument, replacing all diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index ef32e0ba6..f3dceea71 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -302,7 +302,7 @@ trait ImplicitRunInfo { self: RunInfo => case _ => arg } (apply(tp.tycon) /: tp.args)((tc, arg) => AndType.make(tc, applyArg(arg))) - case tp: TypeLambda => + case tp: PolyType => apply(tp.resType) case _ => mapOver(tp) @@ -343,7 +343,9 @@ trait ImplicitRunInfo { self: RunInfo => } tp.classSymbols(liftingCtx) foreach addClassScope case _ => - for (part <- tp.namedPartsWith(_.isType)) + // We exclude lower bounds to conform to SLS 7.2: + // "The parts of a type T are: [...] if T is an abstract type, the parts of its upper bound" + for (part <- tp.namedPartsWith(_.isType, excludeLowerBounds = true)) comps ++= iscopeRefs(part) } comps @@ -412,7 +414,7 @@ trait Implicits { self: Typer => && !to.isError && !ctx.isAfterTyper && (ctx.mode is Mode.ImplicitsEnabled) - && from.isInstanceOf[ValueType] + && from.isValueType && ( from.isValueSubType(to) || inferView(dummyTreeOfType(from), to) (ctx.fresh.addMode(Mode.ImplicitExploration).setExploreTyperState) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 4f4278468..00e92cbfb 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -898,7 +898,7 @@ class Namer { typer: Typer => // definition is inline (i.e. final in Scala2). def widenRhs(tp: Type): Type = tp.widenTermRefExpr match { case tp: ConstantType if isInline => tp - case _ => tp.widen.approximateUnion + case _ => ctx.harmonizeUnion(tp.widen) } // Replace aliases to Unit by Unit itself. If we leave the alias in diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 0e6697fb7..08f566d49 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -43,6 +43,11 @@ object ProtoTypes { isCompatible(normalize(tp, pt)(nestedCtx), pt)(nestedCtx) } + private def disregardProto(pt: Type)(implicit ctx: Context): Boolean = pt.dealias match { + case _: OrType => true + case pt => pt.isRef(defn.UnitClass) + } + /** Check that the result type of the current method * fits the given expected result type. */ @@ -54,7 +59,7 @@ object ProtoTypes { case _ => true } - case _: ValueTypeOrProto if !(pt isRef defn.UnitClass) => + case _: ValueTypeOrProto if !disregardProto(pt) => mt match { case mt: MethodType => mt.isDependent || isCompatible(normalize(mt, pt), pt) @@ -311,7 +316,7 @@ object ProtoTypes { override def isMatchedBy(tp: Type)(implicit ctx: Context) = { def isInstantiatable(tp: Type) = tp.widen match { - case tp: GenericType => tp.paramNames.length == targs.length + case tp: PolyType => tp.paramNames.length == targs.length case _ => false } isInstantiatable(tp) || tp.member(nme.apply).hasAltWith(d => isInstantiatable(d.info)) @@ -358,7 +363,7 @@ object ProtoTypes { yield new TypeVar(PolyParam(pt, n), state, owningTree, ctx.owner) val added = - if (state.constraint contains pt) pt.duplicate(pt.paramNames, pt.paramBounds, pt.resultType) + if (state.constraint contains pt) pt.newLikeThis(pt.paramNames, pt.paramBounds, pt.resultType) else pt val tvars = if (owningTree.isEmpty) Nil else newTypeVars(added) ctx.typeComparer.addToConstraint(added, tvars) @@ -395,9 +400,10 @@ object ProtoTypes { if (mt.isDependent) tp else { val rt = normalize(mt.resultType, pt) - if (pt.isInstanceOf[ApplyingProto]) - mt.derivedMethodType(mt.paramNames, mt.paramTypes, rt) - else { + pt match { + case pt: IgnoredProto => mt + case pt: ApplyingProto => mt.derivedMethodType(mt.paramNames, mt.paramTypes, rt) + case _ => val ft = defn.FunctionOf(mt.paramTypes, rt) if (mt.paramTypes.nonEmpty || ft <:< pt) ft else rt } diff --git a/src/dotty/tools/dotc/typer/RefChecks.scala b/src/dotty/tools/dotc/typer/RefChecks.scala index 1f150c519..834bb37a8 100644 --- a/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/src/dotty/tools/dotc/typer/RefChecks.scala @@ -200,7 +200,7 @@ object RefChecks { infoStringWithLocation(other), infoStringWithLocation(member)) else if (ctx.settings.debug.value) - err.typeMismatchStr(memberTp, otherTp) + err.typeMismatchMsg(memberTp, otherTp) else "" "overriding %s;\n %s %s%s".format( @@ -487,7 +487,7 @@ object RefChecks { // abstract method, and a cursory examination of the difference reveals // something obvious to us, let's make it more obvious to them. val abstractParams = underlying.info.firstParamTypes - val matchingName = clazz.info.member(underlying.name).alternatives + val matchingName = clazz.info.nonPrivateMember(underlying.name).alternatives val matchingArity = matchingName filter { m => !m.symbol.is(Deferred) && m.info.firstParamTypes.length == abstractParams.length diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 0c55d977e..861847b11 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -12,6 +12,8 @@ import config.Printers.typr import ast.Trees._ import NameOps._ import collection.mutable +import reporting.diagnostic.Message +import reporting.diagnostic.messages._ trait TypeAssigner { import tpd._ @@ -220,7 +222,7 @@ trait TypeAssigner { else "" ctx.error( if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor" - else ex"$kind $name is not a member of $site$addendum", + else NotAMember(site, name, kind), pos) } ErrorType @@ -414,7 +416,7 @@ trait TypeAssigner { def assignType(tree: untpd.SeqLiteral, elems: List[Tree], elemtpt: Tree)(implicit ctx: Context) = { val ownType = tree match { - case tree: JavaSeqLiteral => defn.ArrayOf(elemtpt.tpe) + case tree: untpd.JavaSeqLiteral => defn.ArrayOf(elemtpt.tpe) case _ => if (ctx.erasedTypes) defn.SeqType else defn.SeqType.appliedTo(elemtpt.tpe) } tree.withType(ownType) @@ -453,8 +455,8 @@ trait TypeAssigner { tree.withType(ownType) } - def assignType(tree: untpd.TypeLambdaTree, tparamDefs: List[TypeDef], body: Tree)(implicit ctx: Context) = - tree.withType(TypeLambda.fromSymbols(tparamDefs.map(_.symbol), body.tpe)) + def assignType(tree: untpd.PolyTypeTree, tparamDefs: List[TypeDef], body: Tree)(implicit ctx: Context) = + tree.withType(PolyType.fromSymbols(tparamDefs.map(_.symbol), body.tpe)) def assignType(tree: untpd.ByNameTypeTree, result: Tree)(implicit ctx: Context) = tree.withType(ExprType(result.tpe)) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 3e3bb32f5..6fb0dd7c7 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -37,6 +37,7 @@ import rewrite.Rewrites.patch import NavigateAST._ import transform.SymUtils._ import language.implicitConversions +import printing.SyntaxHighlighting._ object Typer { @@ -64,6 +65,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit import tpd.{cpy => _, _} import untpd.cpy import Dynamic.isDynamicMethod + import reporting.diagnostic.Message + import reporting.diagnostic.messages._ /** A temporary data item valid for a single typed ident: * The set of all root import symbols that have been @@ -96,7 +99,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit /** Method is necessary because error messages need to bind to * to typedIdent's context which is lost in nested calls to findRef */ - def error(msg: => String, pos: Position) = ctx.error(msg, pos) + def error(msg: => Message, pos: Position) = ctx.error(msg, pos) /** Is this import a root import that has been shadowed by an explicit * import in the same program? @@ -141,9 +144,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit * imported by <tree> * or defined in <symbol> */ - def bindingString(prec: Int, whereFound: Context, qualifier: String = "")(implicit ctx: Context) = - if (prec == wildImport || prec == namedImport) ex"imported$qualifier by ${whereFound.importInfo}" - else ex"defined$qualifier in ${whereFound.owner}" + def bindingString(prec: Int, whereFound: Context, qualifier: String = "") = + if (prec == wildImport || prec == namedImport) { + ex"""imported$qualifier by ${hl"${whereFound.importInfo.toString}"}""" + } else + ex"""defined$qualifier in ${hl"${whereFound.owner.toString}"}""" /** Check that any previously found result from an inner context * does properly shadow the new one from an outer context. @@ -166,9 +171,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit else { if (!scala2pkg && !previous.isError && !found.isError) { error( - ex"""reference to $name is ambiguous; - |it is both ${bindingString(newPrec, ctx, "")} - |and ${bindingString(prevPrec, prevCtx, " subsequently")}""", + ex"""|reference to `$name` is ambiguous + |it is both ${bindingString(newPrec, ctx, "")} + |and ${bindingString(prevPrec, prevCtx, " subsequently")}""", tree.pos) } previous @@ -181,7 +186,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def checkUnambiguous(found: Type) = { val other = namedImportRef(site, selectors.tail) if (other.exists && found.exists && (found != other)) - error(em"reference to $name is ambiguous; it is imported twice in ${ctx.tree}", + error(em"reference to `$name` is ambiguous; it is imported twice in ${ctx.tree}", tree.pos) found } @@ -326,7 +331,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if (rawType.exists) ensureAccessible(rawType, superAccess = false, tree.pos) else { - error(em"not found: $kind$name", tree.pos) + error(new MissingIdent(tree, kind, name.show), tree.pos) ErrorType } @@ -572,16 +577,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } def escapingRefs(block: Tree, localSyms: => List[Symbol])(implicit ctx: Context): collection.Set[NamedType] = { - var hoisted: Set[Symbol] = Set() lazy val locals = localSyms.toSet - def leakingTypes(tp: Type): collection.Set[NamedType] = - tp namedPartsWith (tp => locals.contains(tp.symbol)) - def typeLeaks(tp: Type): Boolean = leakingTypes(tp).nonEmpty - def classLeaks(sym: ClassSymbol): Boolean = - (ctx.owner is Method) || // can't hoist classes out of method bodies - (sym.info.parents exists typeLeaks) || - (sym.info.decls.toList exists (t => typeLeaks(t.info))) - leakingTypes(block.tpe) + block.tpe namedPartsWith (tp => locals.contains(tp.symbol)) } /** Check that expression's type can be expressed without references to locally defined @@ -767,10 +764,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit TypeTree(pt) case _ => if (!mt.isDependent) EmptyTree - else throw new Error(i"internal error: cannot turn dependent method type $mt into closure, position = ${tree.pos}, raw type = ${mt.toString}") // !!! DEBUG. Eventually, convert to an error? + else throw new java.lang.Error(i"internal error: cannot turn dependent method type $mt into closure, position = ${tree.pos}, raw type = ${mt.toString}") // !!! DEBUG. Eventually, convert to an error? } case tp => - throw new Error(i"internal error: closing over non-method $tp, pos = ${tree.pos}") + throw new java.lang.Error(i"internal error: closing over non-method $tp, pos = ${tree.pos}") } else typed(tree.tpt) //println(i"typing closure $tree : ${meth1.tpe.widen}") @@ -839,11 +836,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit mapOver(t) } } - override def transform(tree: Tree)(implicit ctx: Context) = - super.transform(tree.withType(elimWildcardSym(tree.tpe))) match { + override def transform(trt: Tree)(implicit ctx: Context) = + super.transform(trt.withType(elimWildcardSym(trt.tpe))) match { case b: Bind => if (ctx.scope.lookup(b.name) == NoSymbol) ctx.enter(b.symbol) - else ctx.error(em"duplicate pattern variable: ${b.name}", b.pos) + else ctx.error(new DuplicateBind(b, tree), b.pos) b.symbol.info = elimWildcardSym(b.symbol.info) b case t => t @@ -994,8 +991,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } def typedOrTypeTree(tree: untpd.OrTypeTree)(implicit ctx: Context): OrTypeTree = track("typedOrTypeTree") { - val left1 = typed(tree.left) - val right1 = typed(tree.right) + val where = "in a union type" + val left1 = checkNotSingleton(typed(tree.left), where) + val right1 = checkNotSingleton(typed(tree.right), where) assignType(cpy.OrTypeTree(tree)(left1, right1), left1, right1) } @@ -1055,12 +1053,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } - def typedTypeLambdaTree(tree: untpd.TypeLambdaTree)(implicit ctx: Context): Tree = track("typedTypeLambdaTree") { - val TypeLambdaTree(tparams, body) = tree + def typedPolyTypeTree(tree: untpd.PolyTypeTree)(implicit ctx: Context): Tree = track("typedPolyTypeTree") { + val PolyTypeTree(tparams, body) = tree index(tparams) val tparams1 = tparams.mapconserve(typed(_).asInstanceOf[TypeDef]) val body1 = typedType(tree.body) - assignType(cpy.TypeLambdaTree(tree)(tparams1, body1), tparams1, body1) + assignType(cpy.PolyTypeTree(tree)(tparams1, body1), tparams1, body1) } def typedByNameTypeTree(tree: untpd.ByNameTypeTree)(implicit ctx: Context): ByNameTypeTree = track("typedByNameTypeTree") { @@ -1254,7 +1252,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) .withType(dummy.nonMemberTermRef) checkVariance(impl1) - if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.typeRef, cdef.pos) + if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.typeRef, cdef.namePos) val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1, Nil), cls) if (ctx.phase.isTyper && cdef1.tpe.derivesFrom(defn.DynamicClass) && !ctx.dynamicsEnabled) { val isRequired = parents1.exists(_.tpe.isRef(defn.DynamicClass)) @@ -1463,7 +1461,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case tree: untpd.OrTypeTree => typedOrTypeTree(tree) case tree: untpd.RefinedTypeTree => typedRefinedTypeTree(tree) case tree: untpd.AppliedTypeTree => typedAppliedTypeTree(tree) - case tree: untpd.TypeLambdaTree => typedTypeLambdaTree(tree)(localContext(tree, NoSymbol).setNewScope) + case tree: untpd.PolyTypeTree => typedPolyTypeTree(tree)(localContext(tree, NoSymbol).setNewScope) case tree: untpd.ByNameTypeTree => typedByNameTypeTree(tree) case tree: untpd.TypeBoundsTree => typedTypeBoundsTree(tree) case tree: untpd.Alternative => typedAlternative(tree, pt) @@ -1915,7 +1913,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case _ => adaptOverloaded(ref) } - case poly: PolyType => + case poly: PolyType if !(ctx.mode is Mode.Type) => if (pt.isInstanceOf[PolyProto]) tree else { var typeArgs = tree match { diff --git a/src/dotty/tools/dotc/typer/Variances.scala b/src/dotty/tools/dotc/typer/Variances.scala index a8abe5e30..92bd9fd74 100644 --- a/src/dotty/tools/dotc/typer/Variances.scala +++ b/src/dotty/tools/dotc/typer/Variances.scala @@ -94,7 +94,7 @@ object Variances { v } varianceInArgs(varianceInType(tycon)(tparam), args, tycon.typeParams) - case tp: GenericType => + case tp: PolyType => flip(varianceInTypes(tp.paramBounds)(tparam)) & varianceInType(tp.resultType)(tparam) case AnnotatedType(tp, annot) => varianceInType(tp)(tparam) & varianceInAnnot(annot)(tparam) diff --git a/src/dotty/tools/dotc/util/DiffUtil.scala b/src/dotty/tools/dotc/util/DiffUtil.scala index b7c77ad62..b55aee719 100644 --- a/src/dotty/tools/dotc/util/DiffUtil.scala +++ b/src/dotty/tools/dotc/util/DiffUtil.scala @@ -12,9 +12,7 @@ object DiffUtil { private final val DELETION_COLOR = ANSI_RED private final val ADDITION_COLOR = ANSI_GREEN - def mkColoredCodeDiff(code: String, lastCode: String, printDiffDel: Boolean): String = { - - @tailrec def splitTokens(str: String, acc: List[String] = Nil): List[String] = { + @tailrec private def splitTokens(str: String, acc: List[String] = Nil): List[String] = { if (str == "") { acc.reverse } else { @@ -33,6 +31,35 @@ object DiffUtil { } } + + /** @return a tuple of the (found, expected, changedPercentage) diffs as strings */ + def mkColoredTypeDiff(found: String, expected: String): (String, String, Double) = { + var totalChange = 0 + val foundTokens = splitTokens(found, Nil).toArray + val expectedTokens = splitTokens(expected, Nil).toArray + + val diffExp = hirschberg(foundTokens, expectedTokens) + val diffAct = hirschberg(expectedTokens, foundTokens) + + val exp = diffExp.collect { + case Unmodified(str) => str + case Inserted(str) => + totalChange += str.length + ADDITION_COLOR + str + ANSI_DEFAULT + }.mkString + + val fnd = diffAct.collect { + case Unmodified(str) => str + case Inserted(str) => + totalChange += str.length + DELETION_COLOR + str + ANSI_DEFAULT + }.mkString + + (fnd, exp, totalChange.toDouble / (expected.length + found.length)) + } + + def mkColoredCodeDiff(code: String, lastCode: String, printDiffDel: Boolean): String = { + val tokens = splitTokens(code, Nil).toArray val lastTokens = splitTokens(lastCode, Nil).toArray diff --git a/src/dotty/tools/dotc/util/SourceFile.scala b/src/dotty/tools/dotc/util/SourceFile.scala index 8bd0ecfd6..1d4c9c2ab 100644 --- a/src/dotty/tools/dotc/util/SourceFile.scala +++ b/src/dotty/tools/dotc/util/SourceFile.scala @@ -97,7 +97,7 @@ case class SourceFile(file: AbstractFile, content: Array[Char]) extends interfac private lazy val lineIndices: Array[Int] = calculateLineIndices(content) /** Map line to offset of first character in line */ - def lineToOffset(index : Int): Int = lineIndices(index) + def lineToOffset(index: Int): Int = lineIndices(index) /** A cache to speed up offsetToLine searches to similar lines */ private var lastLine = 0 diff --git a/src/dotty/tools/dotc/util/SourcePosition.scala b/src/dotty/tools/dotc/util/SourcePosition.scala index 68a9b6403..595ea34ca 100644 --- a/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/src/dotty/tools/dotc/util/SourcePosition.scala @@ -14,6 +14,23 @@ extends interfaces.SourcePosition { def point: Int = pos.point /** The line of the position, starting at 0 */ def line: Int = source.offsetToLine(point) + + /** The lines of the position */ + def lines: List[Int] = + List.range(source.offsetToLine(start), source.offsetToLine(end + 1)) match { + case Nil => line :: Nil + case xs => xs + } + + def lineOffsets: List[Int] = + lines.map(source.lineToOffset(_)) + + def lineContent(lineNumber: Int): String = + source.lineContent(source.lineToOffset(lineNumber)) + + def beforeAndAfterPoint: (List[Int], List[Int]) = + lineOffsets.partition(_ < point) + /** The column of the position, starting at 0 */ def column: Int = source.column(point) diff --git a/src/strawman/collections/CollectionStrawMan4.scala b/src/strawman/collections/CollectionStrawMan4.scala index fb95ea59f..7e8de2c82 100644 --- a/src/strawman/collections/CollectionStrawMan4.scala +++ b/src/strawman/collections/CollectionStrawMan4.scala @@ -216,7 +216,7 @@ object CollectionStrawMan4 { object ListBuffer extends IterableFactory[ListBuffer] { def fromIterable[B](coll: Iterable[B]): ListBuffer[B] = coll match { - case pd @ View.Partitioned(partition: View.Partition[B]) => + case pd @ View.Partitioned(partition: View.Partition[B] @unchecked) => partition.distribute(new ListBuffer[B]()) new ListBuffer[B] ++= pd.forced.get case _ => @@ -267,7 +267,7 @@ object CollectionStrawMan4 { Array.copy(fst.elems, fst.start, elems, 0, fst.length) Array.copy(snd.elems, snd.start, elems, fst.length, snd.length) new ArrayBuffer(elems, elems.length) - case pd @ View.Partitioned(partition: View.Partition[B]) => + case pd @ View.Partitioned(partition: View.Partition[B] @unchecked) => partition.distribute(new ArrayBuffer[B]()) pd.forced.get.asInstanceOf[ArrayBuffer[B]] case c if c.knownLength >= 0 => |