diff --git a/README.md b/README.md index bdac6376d2bb..20fea6653daf 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,10 @@ See [github contributors page](https://github.com/lampepfl/dotty/graphs/contribu | Colored Repl | Implemented | | Sbt incremental build | Implemented | | Non-blocking lazy vals | Implemented | +| Multiverse equality | Implemented | | Option-less pattern matching(based on [name-based patmat](https://github.com/scala/scala/pull/2848)) | Implemented | | Function arity adaptation | Implemented | +| Exhaustivity checks in pattern matching | Implemented | | | | | Non-boxed arrays of value classes | In progress | | Working contravariant implicits | In progress | @@ -44,8 +46,7 @@ See [github contributors page](https://github.com/lampepfl/dotty/graphs/contribu | Effects | Under consideration | | Auto-completion in repl | Under consideration | | Spec Option-less pattern matching | Under consideration | -| Multiverse equality | Under consideration | -| Exhaustivity checks in pattern matching | Under consideration | + There are also plethora of small details such as [per-callsite @tailrec annotations](https://github.com/lampepfl/dotty/issues/1221) ####What are the complications that I can have If I start using Dotty? diff --git a/bridge/src/main/scala/xsbt/CompilerInterface.scala b/bridge/src/main/scala/xsbt/CompilerInterface.scala index ee272b8b127f..cd10b1b45c73 100644 --- a/bridge/src/main/scala/xsbt/CompilerInterface.scala +++ b/bridge/src/main/scala/xsbt/CompilerInterface.scala @@ -49,15 +49,17 @@ class CachedCompilerImpl(args: Array[String], output: Output, resident: Boolean) def commandArguments(sources: Array[File]): Array[String] = (outputArgs ++ args.toList ++ sources.map(_.getAbsolutePath).sortWith(_ < _)).toArray[String] - def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, progress: CompileProgress): Unit = synchronized { - run(sources.toList, changes, callback, log, progress) + def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, + log: Logger, delegate: Reporter, progress: CompileProgress): Unit = synchronized { + + run(sources.toList, changes, callback, log, delegate, progress) } - private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, compileProgress: CompileProgress): Unit = { + private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, + log: Logger, delegate: Reporter, compileProgress: CompileProgress): Unit = { debug(log, args.mkString("Calling Dotty compiler with arguments (CompilerInterface):\n\t", "\n\t", "")) - val ctx = (new ContextBase).initialCtx.fresh - .setSbtCallback(callback) + val freshContext = (new ContextBase).initialCtx.fresh + val ctx = freshContext.setSbtCallback(callback).setReporter(DelegatingReporter(delegate)) val cl = getClass.getClassLoader.asInstanceOf[URLClassLoader] - val reporter = DottyMain.process(commandArguments(sources.toArray), ctx) if (reporter.hasErrors) { throw new InterfaceCompileFailed(args, Array()) diff --git a/bridge/src/main/scala/xsbt/DelegatingReporter.scala b/bridge/src/main/scala/xsbt/DelegatingReporter.scala new file mode 100644 index 000000000000..8bdb2e6fc29a --- /dev/null +++ b/bridge/src/main/scala/xsbt/DelegatingReporter.scala @@ -0,0 +1,65 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt + +import dotty.tools._ +import dotc._ +import reporting._ +import core.Contexts._ + +import xsbti.Maybe + +object DelegatingReporter { + def apply(delegate: xsbti.Reporter) = new DelegatingReporter(delegate) +} + +final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter + with UniqueMessagePositions + with HideNonSensicalMessages { + + def doReport(d: Diagnostic)(implicit ctx: Context): Unit = { + println("doReport " + d.toString) + val severity = + d match { + case _: Reporter.Error => xsbti.Severity.Error + case _: Reporter.Warning => xsbti.Severity.Warn + case _ => xsbti.Severity.Info + } + val pos = + if(d.pos.exists) Some(d.pos) + else None + + val file = + if(d.pos.source.file.exists) { + val r = d.pos.source.file.file + if(r == null) None + else Some(r) + } + else None + + val offset0 = pos.map(_.point) + + val position = new xsbti.Position { + def line: Maybe[Integer] = maybe(pos.map(_.line)) + def lineContent(): String = pos.map(_.lineContent).getOrElse("") + def offset(): xsbti.Maybe[Integer] = maybeInt(offset0) + def pointer(): xsbti.Maybe[Integer] = offset() + def pointerSpace(): xsbti.Maybe[String] = maybe(offset0.map(" " * _)) + def sourceFile(): xsbti.Maybe[java.io.File] = maybe(file) + def sourcePath(): xsbti.Maybe[String] = maybe(file.map(_.getPath)) + } + + delegate.log(position, d.message, severity) + } + + private[this] def maybe[T](opt: Option[T]): Maybe[T] = opt match { + case None => Maybe.nothing[T] + case Some(s) => Maybe.just[T](s) + } + import java.lang.{ Integer => I } + private[this] def maybeInt(opt: Option[Int]): Maybe[I] = opt match { + case None => Maybe.nothing[I] + case Some(s) => Maybe.just[I](s) + } +} \ No newline at end of file diff --git a/bridge/src/sbt-test/compilerReporter/simple/B.scala b/bridge/src/sbt-test/compilerReporter/simple/B.scala new file mode 100644 index 000000000000..6f06785990c3 --- /dev/null +++ b/bridge/src/sbt-test/compilerReporter/simple/B.scala @@ -0,0 +1,10 @@ +trait A +trait B + +trait Wr { + val z: A with B +} + +object Er { + val a = er1 +} \ No newline at end of file diff --git a/bridge/src/sbt-test/compilerReporter/simple/build.sbt b/bridge/src/sbt-test/compilerReporter/simple/build.sbt new file mode 100644 index 000000000000..017846f5ed23 --- /dev/null +++ b/bridge/src/sbt-test/compilerReporter/simple/build.sbt @@ -0,0 +1 @@ +Reporter.checkSettings \ No newline at end of file diff --git a/bridge/src/sbt-test/compilerReporter/simple/project/DottyInjectedPlugin.scala b/bridge/src/sbt-test/compilerReporter/simple/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..3433779b6c66 --- /dev/null +++ b/bridge/src/sbt-test/compilerReporter/simple/project/DottyInjectedPlugin.scala @@ -0,0 +1,17 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := "0.1-SNAPSHOT", + scalaOrganization := "ch.epfl.lamp", + scalacOptions += "-language:Scala2", + scalaBinaryVersion := "2.11", + autoScalaLibrary := false, + libraryDependencies ++= Seq("org.scala-lang" % "scala-library" % "2.11.5"), + scalaCompilerBridgeSource := ("ch.epfl.lamp" % "dotty-bridge" % "0.1.1-SNAPSHOT" % "component").sources() + ) +} diff --git a/bridge/src/sbt-test/compilerReporter/simple/project/Reporter.scala b/bridge/src/sbt-test/compilerReporter/simple/project/Reporter.scala new file mode 100644 index 000000000000..1c5952d28041 --- /dev/null +++ b/bridge/src/sbt-test/compilerReporter/simple/project/Reporter.scala @@ -0,0 +1,45 @@ +import sbt._ +import Keys._ +import KeyRanks.DTask + +object Reporter { + import xsbti.{Reporter, Problem, Position, Severity, Maybe} + + lazy val check = TaskKey[Unit]("check", "make sure compilation info are forwared to sbt") + + // compilerReporter is marked private in sbt + lazy val compilerReporter = TaskKey[Option[xsbti.Reporter]]("compilerReporter", "Experimental hook to listen (or send) compilation failure messages.", DTask) + + lazy val reporter = + Some(new xsbti.Reporter { + private val buffer = collection.mutable.ArrayBuffer.empty[Problem] + def reset(): Unit = buffer.clear() + def hasErrors: Boolean = buffer.exists(_.severity == Severity.Error) + def hasWarnings: Boolean = buffer.exists(_.severity == Severity.Warn) + def printSummary(): Unit = println(problems.mkString(System.lineSeparator)) + def problems: Array[Problem] = buffer.toArray + def log(pos: Position, msg: String, sev: Severity): Unit = { + object MyProblem extends Problem { + def category: String = null + def severity: Severity = sev + def message: String = msg + def position: Position = pos + override def toString = s"custom: $position:$severity: $message" + } + buffer.append(MyProblem) + } + def comment(pos: xsbti.Position, msg: String): Unit = () + }) + + lazy val checkSettings = Seq( + compilerReporter in (Compile, compile) := reporter, + check <<= (compile in Compile).mapFailure( _ => { + val problems = reporter.get.problems + println(problems.toList) + assert(problems.size == 3) + assert(problems.count(_.severity == Severity.Error) == 1) // not found: er1, + assert(problems.count(_.severity == Severity.Warn) == 1) // `with' as a type operator has been deprecated; use `&' instead, + assert(problems.count(_.severity == Severity.Info) == 1) // one error found + }) + ) +} \ No newline at end of file diff --git a/bridge/src/sbt-test/compilerReporter/simple/test b/bridge/src/sbt-test/compilerReporter/simple/test new file mode 100644 index 000000000000..a5912a391a4d --- /dev/null +++ b/bridge/src/sbt-test/compilerReporter/simple/test @@ -0,0 +1 @@ +> check \ No newline at end of file diff --git a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala index 2d4c7abcf3ee..a80efb1657be 100644 --- a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala +++ b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala @@ -1,6 +1,7 @@ package dotty.tools package dottydoc +import dotty.tools.dottydoc.util.syntax._ import core._ import core.transform._ import dotc.config.CompilerCommand @@ -31,7 +32,9 @@ class DocCompiler extends Compiler { List(new DocFrontEnd), List(new DocImplicitsPhase), List(new DocASTPhase), - List(DocMiniTransformations(new LinkReturnTypes, + List(DocMiniTransformations(new UsecasePhase, + new DocstringPhase, + new LinkReturnTypes, new LinkParamListTypes, new LinkImplicitlyAddedTypes, new LinkSuperTypes, @@ -54,6 +57,7 @@ abstract class DocDriver extends Driver { ctx.setSettings(summary.sstate) ctx.setSetting(ctx.settings.YkeepComments, true) + ctx.setProperty(DocContext, new DocBase) val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)(ctx) (fileNames, ctx) @@ -65,7 +69,7 @@ abstract class DocDriver extends Driver { val (fileNames, ctx) = setup(args, initCtx.fresh) doCompile(newCompiler(ctx), fileNames)(ctx) - ctx.docbase.packages[Package] + ctx.docbase.packages } def compiledDocsJava(args: Array[String]): JMap[String, Package] = diff --git a/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala index 7744752ce530..c6e9d61902d9 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala @@ -6,7 +6,7 @@ package core import dotc.ast.Trees._ import dotc.CompilationUnit import dotc.config.Printers.dottydoc -import dotc.core.Contexts.Context +import dotc.core.Contexts.{ Context, DocBase } import dotc.core.Phases.Phase import dotc.core.Symbols.{ Symbol, NoSymbol } @@ -14,29 +14,17 @@ class DocASTPhase extends Phase { import model._ import model.factories._ import model.internal._ - import model.parsers.WikiParser import model.comment.Comment import dotty.tools.dotc.core.Flags import dotty.tools.dotc.ast.tpd._ + import dotty.tools.dottydoc.util.syntax._ import util.traversing._ import util.internal.setters._ def phaseName = "docphase" - private[this] val commentParser = new WikiParser - - /** Saves the commentParser function for later evaluation, for when the AST has been filled */ - def track(symbol: Symbol, ctx: Context, parent: Symbol = NoSymbol)(op: => Entity) = { - val entity = op - - if (entity != NonEntity) - commentParser += (entity, symbol, parent, ctx) - - entity - } - /** Build documentation hierarchy from existing tree */ - def collect(tree: Tree, prev: List[String] = Nil)(implicit ctx: Context): Entity = track(tree.symbol, ctx) { + def collect(tree: Tree, prev: List[String] = Nil)(implicit ctx: Context): Entity = { val implicitConversions = ctx.docbase.defs(tree.symbol) def collectList(xs: List[Tree], ps: List[String]): List[Entity] = @@ -58,28 +46,26 @@ class DocASTPhase extends Phase { val defs = sym.info.bounds.hi.membersBasedOnFlags(Flags.Method, Flags.Synthetic | Flags.Private) .filterNot(_.symbol.owner.name.show == "Any") .map { meth => - track(meth.symbol, ctx, tree.symbol) { - DefImpl( - meth.symbol.name.show, - Nil, - path(meth.symbol), - returnType(meth.info), - typeParams(meth.symbol), - paramLists(meth.info), - implicitlyAddedFrom = Some(returnType(meth.symbol.owner.info)) - ) - } + DefImpl( + meth.symbol, + meth.symbol.name.show, + Nil, + path(meth.symbol), + returnType(meth.info), + typeParams(meth.symbol), + paramLists(meth.info), + implicitlyAddedFrom = Some(returnType(meth.symbol.owner.info)) + ) }.toList val vals = sym.info.fields.filterNot(_.symbol.is(Flags.Private | Flags.Synthetic)).map { value => - track(value.symbol, ctx, tree.symbol) { - ValImpl( - value.symbol.name.show, - Nil, path(value.symbol), - returnType(value.info), - implicitlyAddedFrom = Some(returnType(value.symbol.owner.info)) - ) - } + ValImpl( + value.symbol, + value.symbol.name.show, + Nil, path(value.symbol), + returnType(value.info), + implicitlyAddedFrom = Some(returnType(value.symbol.owner.info)) + ) } defs ++ vals @@ -90,38 +76,38 @@ class DocASTPhase extends Phase { /** package */ case pd @ PackageDef(pid, st) => val newPath = prev :+ pid.name.toString - addEntity(PackageImpl(newPath.mkString("."), collectEntityMembers(st, newPath), newPath)) + addEntity(PackageImpl(pd.symbol, newPath.mkString("."), collectEntityMembers(st, newPath), newPath)) /** trait */ case t @ TypeDef(n, rhs) if t.symbol.is(Flags.Trait) => val name = n.decode.toString val newPath = prev :+ name //TODO: should not `collectMember` from `rhs` - instead: get from symbol, will get inherited members as well - TraitImpl(name, collectMembers(rhs), flags(t), newPath, typeParams(t.symbol), traitParameters(t.symbol), superTypes(t)) + TraitImpl(t.symbol, name, collectMembers(rhs), flags(t), newPath, typeParams(t.symbol), traitParameters(t.symbol), superTypes(t)) /** objects, on the format "Object$" so drop the last letter */ case o @ TypeDef(n, rhs) if o.symbol.is(Flags.Module) => val name = n.decode.toString.dropRight(1) //TODO: should not `collectMember` from `rhs` - instead: get from symbol, will get inherited members as well - ObjectImpl(name, collectMembers(rhs, prev :+ name), flags(o), prev :+ (name + "$"), superTypes(o)) + ObjectImpl(o.symbol, name, collectMembers(rhs, prev :+ name), flags(o), prev :+ (name + "$"), superTypes(o)) /** class / case class */ case c @ TypeDef(n, rhs) if c.symbol.isClass => val name = n.decode.toString val newPath = prev :+ name //TODO: should not `collectMember` from `rhs` - instead: get from symbol, will get inherited members as well - (name, collectMembers(rhs), flags(c), newPath, typeParams(c.symbol), constructors(c.symbol), superTypes(c), None) match { + (c.symbol, name, collectMembers(rhs), flags(c), newPath, typeParams(c.symbol), constructors(c.symbol), superTypes(c), None) match { case x if c.symbol.is(Flags.CaseClass) => CaseClassImpl.tupled(x) case x => ClassImpl.tupled(x) } /** def */ case d: DefDef => - DefImpl(d.name.decode.toString, flags(d), path(d.symbol), returnType(d.tpt.tpe), typeParams(d.symbol), paramLists(d.symbol.info)) + DefImpl(d.symbol, d.name.decode.toString, flags(d), path(d.symbol), returnType(d.tpt.tpe), typeParams(d.symbol), paramLists(d.symbol.info)) /** val */ case v: ValDef if !v.symbol.is(Flags.ModuleVal) => - ValImpl(v.name.decode.toString, flags(v), path(v.symbol), returnType(v.tpt.tpe)) + ValImpl(v.symbol, v.name.decode.toString, flags(v), path(v.symbol), returnType(v.tpt.tpe)) case x => { //dottydoc.println(s"Found unwanted entity: $x (${x.pos},\n${x.show}") @@ -175,15 +161,8 @@ class DocASTPhase extends Phase { child <- parent.children } setParent(child, to = parent) - // (3) Create documentation template from docstrings, with internal links - println("Generating documentation, this might take a while...") - commentParser.parse(packages) - - // (4) Clear caches - commentParser.clear() - - // (5) Update Doc AST in ctx.base - for (kv <- packages) ctx.docbase.packages += kv + // (3) Update Doc AST in ctx.base + for (kv <- packages) ctx.docbase.packagesMutable += kv // Return super's result compUnits diff --git a/dottydoc/src/dotty/tools/dottydoc/core/DocImplicitsPhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/DocImplicitsPhase.scala index f322d7a5a62d..6577f0cb784f 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/DocImplicitsPhase.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/DocImplicitsPhase.scala @@ -5,6 +5,7 @@ package core import dotty.tools.dotc.transform.TreeTransforms.{ MiniPhaseTransform, TransformerInfo } import dotty.tools.dotc.core.Flags import dotc.core.Contexts.Context +import util.syntax._ class DocImplicitsPhase extends MiniPhaseTransform { thisTransformer => import dotty.tools.dotc.ast.tpd._ diff --git a/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala new file mode 100644 index 000000000000..cff614528644 --- /dev/null +++ b/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala @@ -0,0 +1,47 @@ +package dotty.tools +package dottydoc +package core + +import dotc.core.Contexts.Context +import transform.DocMiniPhase +import model._ +import model.internal._ +import model.comment._ +import BodyParsers._ +import util.syntax._ + +class DocstringPhase extends DocMiniPhase with CommentParser with CommentCleaner { + private def parsedComment[E <: Entity](ent: E)(implicit ctx: Context): Option[Comment] = + ctx.docbase.docstring(ent.symbol).map { cmt => + parse(ent, ctx.docbase.packages, clean(cmt.raw), cmt.raw, cmt.pos) + .toComment(_.toHtml(ent)) + } + + override def transformPackage(implicit ctx: Context) = { case ent: PackageImpl => + ent.copy(comment = parsedComment(ent)) + } + + override def transformClass(implicit ctx: Context) = { case ent: ClassImpl => + ent.copy(comment = parsedComment(ent)) + } + + override def transformCaseClass(implicit ctx: Context) = { case ent: CaseClassImpl => + ent.copy(comment = parsedComment(ent)) + } + + override def transformTrait(implicit ctx: Context) = { case ent: TraitImpl => + ent.copy(comment = parsedComment(ent)) + } + + override def transformObject(implicit ctx: Context) = { case ent: ObjectImpl => + ent.copy(comment = parsedComment(ent)) + } + + override def transformDef(implicit ctx: Context) = { case ent: DefImpl => + ent.copy(comment = parsedComment(ent)) + } + + override def transformVal(implicit ctx: Context) = { case ent: ValImpl => + ent.copy(comment = parsedComment(ent)) + } +} diff --git a/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala b/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala index 2690ac7b77c9..201640e4a9b2 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala @@ -3,10 +3,11 @@ package dottydoc package core import dotc.CompilationUnit -import dotc.core.Contexts.Context +import dotc.core.Contexts.{ Context, DocBase } import dotc.core.Phases.Phase import model._ import model.internal._ +import util.syntax._ object transform { /** @@ -43,9 +44,9 @@ object transform { override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { for { rootName <- rootPackages - pack = ctx.docbase.packages[Package](rootName) + pack = ctx.docbase.packages(rootName) transformed = performPackageTransform(pack) - } yield ctx.docbase.packages(rootName) = transformed + } yield ctx.docbase.packagesMutable(rootName) = transformed super.runOn(units) } @@ -77,6 +78,7 @@ object transform { def traverse(ent: Entity): Entity = ent match { case p: Package => transformEntity(p, _.packageTransformation) { p => val newPackage = PackageImpl( + p.symbol, p.name, p.members.map(traverse), p.path, @@ -84,12 +86,13 @@ object transform { ) // Update reference in context to newPackage - ctx.docbase.packages[Package] += (newPackage.path.mkString(".") -> newPackage) + ctx.docbase.packagesMutable += (newPackage.path.mkString(".") -> newPackage) newPackage } case c: Class => transformEntity(c, _.classTransformation) { cls => ClassImpl( + cls.symbol, cls.name, cls.members.map(traverse), cls.modifiers, @@ -102,6 +105,7 @@ object transform { } case cc: CaseClass => transformEntity(cc, _.caseClassTransformation) { cc => CaseClassImpl( + cc.symbol, cc.name, cc.members.map(traverse), cc.modifiers, @@ -114,6 +118,7 @@ object transform { } case trt: Trait => transformEntity(trt, _.traitTransformation) { trt => TraitImpl( + trt.symbol, trt.name, trt.members.map(traverse), trt.modifiers, @@ -126,6 +131,7 @@ object transform { } case obj: Object => transformEntity(obj, _.objectTransformation) { obj => ObjectImpl( + obj.symbol, obj.name, obj.members.map(traverse), obj.modifiers, @@ -136,6 +142,7 @@ object transform { } case df: Def => transformEntity(df, _.defTransformation) { df => DefImpl( + df.symbol, df.name, df.modifiers, df.path, @@ -148,6 +155,7 @@ object transform { } case vl: Val => transformEntity(vl, _.valTransformation) { vl => ValImpl( + vl.symbol, vl.name, vl.modifiers, vl.path, diff --git a/dottydoc/src/dotty/tools/dottydoc/core/TypeLinkingPhases.scala b/dottydoc/src/dotty/tools/dottydoc/core/TypeLinkingPhases.scala index ae07effa9ed9..1aecca9e12f3 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/TypeLinkingPhases.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/TypeLinkingPhases.scala @@ -14,15 +14,16 @@ import BodyParsers._ import util.MemberLookup import util.traversing._ import util.internal.setters._ +import util.syntax._ class LinkReturnTypes extends DocMiniPhase with TypeLinker { override def transformDef(implicit ctx: Context) = { case df: DefImpl => - val returnValue = linkReference(df, df.returnValue, ctx.docbase.packages[Package].toMap) + val returnValue = linkReference(df, df.returnValue, ctx.docbase.packages) df.copy(returnValue = returnValue) } override def transformVal(implicit ctx: Context) = { case vl: ValImpl => - val returnValue = linkReference(vl, vl.returnValue, ctx.docbase.packages[Package].toMap) + val returnValue = linkReference(vl, vl.returnValue, ctx.docbase.packages) vl.copy(returnValue = returnValue) } } @@ -31,7 +32,7 @@ class LinkParamListTypes extends DocMiniPhase with TypeLinker { override def transformDef(implicit ctx: Context) = { case df: DefImpl => val newParamLists = for { ParamListImpl(list, isImplicit) <- df.paramLists - newList = list.map(linkReference(df, _, ctx.docbase.packages[Package].toMap)) + newList = list.map(linkReference(df, _, ctx.docbase.packages)) } yield ParamListImpl(newList.asInstanceOf[List[NamedReference]], isImplicit) df.copy(paramLists = newParamLists) @@ -42,7 +43,7 @@ class LinkSuperTypes extends DocMiniPhase with TypeLinker { def linkSuperTypes(ent: Entity with SuperTypes)(implicit ctx: Context): List[MaterializableLink] = ent.superTypes.collect { case UnsetLink(title, query) => - val packages = ctx.docbase.packages[Package].toMap + val packages = ctx.docbase.packages val entityLink = makeEntityLink(ent, packages, Text(title), NoPosition, query).link handleEntityLink(title, entityLink, ent) } @@ -67,13 +68,13 @@ class LinkSuperTypes extends DocMiniPhase with TypeLinker { class LinkImplicitlyAddedTypes extends DocMiniPhase with TypeLinker { override def transformDef(implicit ctx: Context) = { case df: DefImpl if df.implicitlyAddedFrom.isDefined => - val implicitlyAddedFrom = linkReference(df, df.implicitlyAddedFrom.get, ctx.docbase.packages[Package].toMap) + val implicitlyAddedFrom = linkReference(df, df.implicitlyAddedFrom.get, ctx.docbase.packages) df.copy(implicitlyAddedFrom = Some(implicitlyAddedFrom)) } override def transformVal(implicit ctx: Context) = { case vl: ValImpl if vl.implicitlyAddedFrom.isDefined => - val implicitlyAddedFrom = linkReference(vl, vl.implicitlyAddedFrom.get, ctx.docbase.packages[Package].toMap) + val implicitlyAddedFrom = linkReference(vl, vl.implicitlyAddedFrom.get, ctx.docbase.packages) vl.copy(implicitlyAddedFrom = Some(implicitlyAddedFrom)) } } diff --git a/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala new file mode 100644 index 000000000000..47376eb264bb --- /dev/null +++ b/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala @@ -0,0 +1,31 @@ +package dotty.tools +package dottydoc +package core + +import dotc.core.Contexts.Context +import dotc.ast.tpd + +import transform.DocMiniPhase +import model.internal._ +import model.factories._ +import dotty.tools.dotc.core.Symbols.Symbol +import util.syntax._ + +class UsecasePhase extends DocMiniPhase { + private def defdefToDef(d: tpd.DefDef, sym: Symbol)(implicit ctx: Context) = { + val name = d.name.show.split("\\$").head // UseCase defs get $pos appended to their names + DefImpl( + sym, + name, + flags(d), + path(d.symbol).init :+ name, + returnType(d.tpt.tpe), + typeParams(d.symbol), + paramLists(d.symbol.info) + ) + } + + override def transformDef(implicit ctx: Context) = { case df: DefImpl => + ctx.docbase.docstring(df.symbol).flatMap(_.usecases.headOption.map(_.tpdCode)).map(defdefToDef(_, df.symbol)).getOrElse(df) + } +} diff --git a/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala b/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala deleted file mode 100644 index 32a0d8128b55..000000000000 --- a/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Port of DocComment.scala from nsc - * @author Martin Odersky - * @author Felix Mulder - */ - -package dotty.tools -package dottydoc -package model -package comment - -import dotc.config.Printers.dottydoc -import dotc.core.Contexts.Context -import dotc.core.Symbols._ -import dotc.core.Flags -import dotc.util.Positions._ - -import scala.collection.mutable - -trait CommentExpander { - import CommentUtils._ - - def expand(sym: Symbol, site: Symbol)(implicit ctx: Context): String = { - val parent = if (site != NoSymbol) site else sym - defineVariables(parent) - expandedDocComment(sym, parent) - } - - /** The cooked doc comment of symbol `sym` after variable expansion, or "" if missing. - * - * @param sym The symbol for which doc comment is returned - * @param site The class for which doc comments are generated - * @throws ExpansionLimitExceeded when more than 10 successive expansions - * of the same string are done, which is - * interpreted as a recursive variable definition. - */ - def expandedDocComment(sym: Symbol, site: Symbol, docStr: String = "")(implicit ctx: Context): String = { - // when parsing a top level class or module, use the (module-)class itself to look up variable definitions - val parent = if ((sym.is(Flags.Module) || sym.isClass) && site.is(Flags.Package)) sym - else site - expandVariables(cookedDocComment(sym, docStr), sym, parent) - } - - private def template(raw: String): String = { - val sections = tagIndex(raw) - - val defines = sections filter { startsWithTag(raw, _, "@define") } - val usecases = sections filter { startsWithTag(raw, _, "@usecase") } - - val end = startTag(raw, (defines ::: usecases).sortBy(_._1)) - - if (end == raw.length - 2) raw else raw.substring(0, end) + "*/" - } - - def defines(raw: String): List[String] = { - val sections = tagIndex(raw) - val defines = sections filter { startsWithTag(raw, _, "@define") } - val usecases = sections filter { startsWithTag(raw, _, "@usecase") } - val end = startTag(raw, (defines ::: usecases).sortBy(_._1)) - - defines map { case (start, end) => raw.substring(start, end) } - } - - private def replaceInheritDocToInheritdoc(docStr: String): String = - docStr.replaceAll("""\{@inheritDoc\p{Zs}*\}""", "@inheritdoc") - - /** The cooked doc comment of an overridden symbol */ - protected def superComment(sym: Symbol)(implicit ctx: Context): Option[String] = - allInheritedOverriddenSymbols(sym).iterator map (x => cookedDocComment(x)) find (_ != "") - - private val cookedDocComments = mutable.HashMap[Symbol, String]() - - /** The raw doc comment of symbol `sym`, minus usecase and define sections, augmented by - * missing sections of an inherited doc comment. - * If a symbol does not have a doc comment but some overridden version of it does, - * the doc comment of the overridden version is copied instead. - */ - def cookedDocComment(sym: Symbol, docStr: String = "")(implicit ctx: Context): String = cookedDocComments.getOrElseUpdate(sym, { - var ownComment = - if (docStr.length == 0) ctx.docbase.docstring(sym).map(c => template(c.chrs)).getOrElse("") - else template(docStr) - ownComment = replaceInheritDocToInheritdoc(ownComment) - - superComment(sym) match { - case None => - // SI-8210 - The warning would be false negative when this symbol is a setter - if (ownComment.indexOf("@inheritdoc") != -1 && ! sym.isSetter) - dottydoc.println(s"${sym.pos}: the comment for ${sym} contains @inheritdoc, but no parent comment is available to inherit from.") - ownComment.replaceAllLiterally("@inheritdoc", "") - case Some(sc) => - if (ownComment == "") sc - else expandInheritdoc(sc, merge(sc, ownComment, sym), sym) - } - }) - - private def isMovable(str: String, sec: (Int, Int)): Boolean = - startsWithTag(str, sec, "@param") || - startsWithTag(str, sec, "@tparam") || - startsWithTag(str, sec, "@return") - - def merge(src: String, dst: String, sym: Symbol, copyFirstPara: Boolean = false): String = { - val srcSections = tagIndex(src) - val dstSections = tagIndex(dst) - val srcParams = paramDocs(src, "@param", srcSections) - val dstParams = paramDocs(dst, "@param", dstSections) - val srcTParams = paramDocs(src, "@tparam", srcSections) - val dstTParams = paramDocs(dst, "@tparam", dstSections) - val out = new StringBuilder - var copied = 0 - var tocopy = startTag(dst, dstSections dropWhile (!isMovable(dst, _))) - - if (copyFirstPara) { - val eop = // end of comment body (first para), which is delimited by blank line, or tag, or end of comment - (findNext(src, 0)(src.charAt(_) == '\n')) min startTag(src, srcSections) - out append src.substring(0, eop).trim - copied = 3 - tocopy = 3 - } - - def mergeSection(srcSec: Option[(Int, Int)], dstSec: Option[(Int, Int)]) = dstSec match { - case Some((start, end)) => - if (end > tocopy) tocopy = end - case None => - srcSec match { - case Some((start1, end1)) => { - out append dst.substring(copied, tocopy).trim - out append "\n" - copied = tocopy - out append src.substring(start1, end1).trim - } - case None => - } - } - - //TODO: enable this once you know how to get `sym.paramss` - /* - for (params <- sym.paramss; param <- params) - mergeSection(srcParams get param.name.toString, dstParams get param.name.toString) - for (tparam <- sym.typeParams) - mergeSection(srcTParams get tparam.name.toString, dstTParams get tparam.name.toString) - - mergeSection(returnDoc(src, srcSections), returnDoc(dst, dstSections)) - mergeSection(groupDoc(src, srcSections), groupDoc(dst, dstSections)) - */ - - if (out.length == 0) dst - else { - out append dst.substring(copied) - out.toString - } - } - - /** - * Expand inheritdoc tags - * - for the main comment we transform the inheritdoc into the super variable, - * and the variable expansion can expand it further - * - for the param, tparam and throws sections we must replace comments on the spot - * - * This is done separately, for two reasons: - * 1. It takes longer to run compared to merge - * 2. The inheritdoc annotation should not be used very often, as building the comment from pieces severely - * impacts performance - * - * @param parent The source (or parent) comment - * @param child The child (overriding member or usecase) comment - * @param sym The child symbol - * @return The child comment with the inheritdoc sections expanded - */ - def expandInheritdoc(parent: String, child: String, sym: Symbol): String = - if (child.indexOf("@inheritdoc") == -1) - child - else { - val parentSections = tagIndex(parent) - val childSections = tagIndex(child) - val parentTagMap = sectionTagMap(parent, parentSections) - val parentNamedParams = Map() + - ("@param" -> paramDocs(parent, "@param", parentSections)) + - ("@tparam" -> paramDocs(parent, "@tparam", parentSections)) + - ("@throws" -> paramDocs(parent, "@throws", parentSections)) - - val out = new StringBuilder - - def replaceInheritdoc(childSection: String, parentSection: => String) = - if (childSection.indexOf("@inheritdoc") == -1) - childSection - else - childSection.replaceAllLiterally("@inheritdoc", parentSection) - - def getParentSection(section: (Int, Int)): String = { - - def getSectionHeader = extractSectionTag(child, section) match { - case param@("@param"|"@tparam"|"@throws") => param + " " + extractSectionParam(child, section) - case other => other - } - - def sectionString(param: String, paramMap: Map[String, (Int, Int)]): String = - paramMap.get(param) match { - case Some(section) => - // Cleanup the section tag and parameter - val sectionTextBounds = extractSectionText(parent, section) - cleanupSectionText(parent.substring(sectionTextBounds._1, sectionTextBounds._2)) - case None => - dottydoc.println(s"""${sym.pos}: the """" + getSectionHeader + "\" annotation of the " + sym + - " comment contains @inheritdoc, but the corresponding section in the parent is not defined.") - "" - } - - child.substring(section._1, section._1 + 7) match { - case param@("@param "|"@tparam"|"@throws") => - sectionString(extractSectionParam(child, section), parentNamedParams(param.trim)) - case _ => - sectionString(extractSectionTag(child, section), parentTagMap) - } - } - - def mainComment(str: String, sections: List[(Int, Int)]): String = - if (str.trim.length > 3) - str.trim.substring(3, startTag(str, sections)) - else - "" - - // Append main comment - out.append("/**") - out.append(replaceInheritdoc(mainComment(child, childSections), mainComment(parent, parentSections))) - - // Append sections - for (section <- childSections) - out.append(replaceInheritdoc(child.substring(section._1, section._2), getParentSection(section))) - - out.append("*/") - out.toString - } - - protected def expandVariables(initialStr: String, sym: Symbol, site: Symbol)(implicit ctx: Context): String = { - val expandLimit = 10 - - def expandInternal(str: String, depth: Int): String = { - if (depth >= expandLimit) - throw new ExpansionLimitExceeded(str) - - val out = new StringBuilder - var copied, idx = 0 - // excluding variables written as \$foo so we can use them when - // necessary to document things like Symbol#decode - def isEscaped = idx > 0 && str.charAt(idx - 1) == '\\' - while (idx < str.length) { - if ((str charAt idx) != '$' || isEscaped) - idx += 1 - else { - val vstart = idx - idx = skipVariable(str, idx + 1) - def replaceWith(repl: String) { - out append str.substring(copied, vstart) - out append repl - copied = idx - } - variableName(str.substring(vstart + 1, idx)) match { - case "super" => - superComment(sym) foreach { sc => - val superSections = tagIndex(sc) - replaceWith(sc.substring(3, startTag(sc, superSections))) - for (sec @ (start, end) <- superSections) - if (!isMovable(sc, sec)) out append sc.substring(start, end) - } - case "" => idx += 1 - case vname => - lookupVariable(vname, site) match { - case Some(replacement) => replaceWith(replacement) - case None => - dottydoc.println(s"Variable $vname undefined in comment for $sym in $site") - } - } - } - } - if (out.length == 0) str - else { - out append str.substring(copied) - expandInternal(out.toString, depth + 1) - } - } - - // We suppressed expanding \$ throughout the recursion, and now we - // need to replace \$ with $ so it looks as intended. - expandInternal(initialStr, 0).replaceAllLiterally("""\$""", "$") - } - - def defineVariables(sym: Symbol)(implicit ctx: Context) = { - val Trim = "(?s)^[\\s&&[^\n\r]]*(.*?)\\s*$".r - - val raw = ctx.docbase.docstring(sym).map(_.chrs).getOrElse("") - defs(sym) ++= defines(raw).map { - str => { - val start = skipWhitespace(str, "@define".length) - val (key, value) = str.splitAt(skipVariable(str, start)) - key.drop(start) -> value - } - } map { - case (key, Trim(value)) => - variableName(key) -> value.replaceAll("\\s+\\*+$", "") - } - } - - /** Maps symbols to the variable -> replacement maps that are defined - * in their doc comments - */ - private val defs = mutable.HashMap[Symbol, Map[String, String]]() withDefaultValue Map() - - /** Lookup definition of variable. - * - * @param vble The variable for which a definition is searched - * @param site The class for which doc comments are generated - */ - def lookupVariable(vble: String, site: Symbol)(implicit ctx: Context): Option[String] = site match { - case NoSymbol => None - case _ => - val searchList = - if (site.flags.is(Flags.Module)) site :: site.info.baseClasses - else site.info.baseClasses - - searchList collectFirst { case x if defs(x) contains vble => defs(x)(vble) } match { - case Some(str) if str startsWith "$" => lookupVariable(str.tail, site) - case res => res orElse lookupVariable(vble, site.owner) - } - } - - /** The position of the raw doc comment of symbol `sym`, or NoPosition if missing - * If a symbol does not have a doc comment but some overridden version of it does, - * the position of the doc comment of the overridden version is returned instead. - */ - def docCommentPos(sym: Symbol)(implicit ctx: Context): Position = - ctx.docbase.docstring(sym).map(_.pos).getOrElse(NoPosition) - - /** A version which doesn't consider self types, as a temporary measure: - * an infinite loop has broken out between superComment and cookedDocComment - * since r23926. - */ - private def allInheritedOverriddenSymbols(sym: Symbol)(implicit ctx: Context): List[Symbol] = { - if (!sym.owner.isClass) Nil - else sym.allOverriddenSymbols.toList.filter(_ != NoSymbol) //TODO: could also be `sym.owner.allOverrid..` - //else sym.owner.ancestors map (sym overriddenSymbol _) filter (_ != NoSymbol) - } - - class ExpansionLimitExceeded(str: String) extends Exception -} diff --git a/dottydoc/src/dotty/tools/dottydoc/model/entities.scala b/dottydoc/src/dotty/tools/dottydoc/model/entities.scala index 76792070cf5b..52379f9ad30c 100644 --- a/dottydoc/src/dotty/tools/dottydoc/model/entities.scala +++ b/dottydoc/src/dotty/tools/dottydoc/model/entities.scala @@ -3,8 +3,11 @@ package model import comment._ import references._ +import dotty.tools.dotc.core.Symbols.{ Symbol, NoSymbol } trait Entity { + def symbol: Symbol + def name: String /** Path from root, i.e. `scala.Option$` */ @@ -103,6 +106,7 @@ trait Var extends Entity with Modifiers with ReturnValue { trait NonEntity extends Entity { val name = "" + val symbol = NoSymbol val comment = None val path = Nil val kind = "" diff --git a/dottydoc/src/dotty/tools/dottydoc/model/internal.scala b/dottydoc/src/dotty/tools/dottydoc/model/internal.scala index 6afb1ec9b33b..09f642d0bb38 100644 --- a/dottydoc/src/dotty/tools/dottydoc/model/internal.scala +++ b/dottydoc/src/dotty/tools/dottydoc/model/internal.scala @@ -3,6 +3,7 @@ package model import comment.Comment import references._ +import dotty.tools.dotc.core.Symbols.Symbol object internal { @@ -11,6 +12,7 @@ object internal { } final case class PackageImpl( + symbol: Symbol, name: String, var members: List[Entity], path: List[String], @@ -21,6 +23,7 @@ object internal { } final case class ClassImpl( + symbol: Symbol, name: String, members: List[Entity], modifiers: List[String], @@ -32,6 +35,7 @@ object internal { ) extends Class with Impl final case class CaseClassImpl( + symbol: Symbol, name: String, members: List[Entity], modifiers: List[String], @@ -43,6 +47,7 @@ object internal { ) extends CaseClass with Impl final case class TraitImpl( + symbol: Symbol, name: String, members: List[Entity], modifiers: List[String], @@ -54,6 +59,7 @@ object internal { ) extends Trait with Impl final case class ObjectImpl( + symbol: Symbol, name: String, members: List[Entity], modifiers: List[String], @@ -63,6 +69,7 @@ object internal { ) extends Object with Impl final case class DefImpl( + symbol: Symbol, name: String, modifiers: List[String], path: List[String], @@ -74,6 +81,7 @@ object internal { ) extends Def with Impl final case class ValImpl( + symbol: Symbol, name: String, modifiers: List[String], path: List[String], diff --git a/dottydoc/src/dotty/tools/dottydoc/model/parsers.scala b/dottydoc/src/dotty/tools/dottydoc/model/parsers.scala deleted file mode 100644 index fa54163e5838..000000000000 --- a/dottydoc/src/dotty/tools/dottydoc/model/parsers.scala +++ /dev/null @@ -1,98 +0,0 @@ -package dotty.tools -package dottydoc -package model - -import dotc.core.Symbols.Symbol -import dotc.core.Contexts.Context -import dotc.util.Positions.NoPosition - -object parsers { - import comment._ - import BodyParsers._ - import model.internal._ - import util.MemberLookup - import util.traversing._ - import util.internal.setters._ - - class WikiParser extends CommentCleaner with CommentParser with CommentExpander { - private[this] var commentCache: Map[String, (Entity, Map[String, Package]) => Option[Comment]] = Map.empty - - /** Parses comment and returns the path to the entity with an optional comment - * - * The idea here is to use this fact to create `Future[Seq[(String, Option[Comment]]]` - * which can then be awaited near the end of the run - before the pickling. - */ - def parseHtml(sym: Symbol, parent: Symbol, entity: Entity, packages: Map[String, Package])(implicit ctx: Context): (String, Option[Comment]) = { - val cmt = ctx.docbase.docstring(sym).map { d => - val expanded = expand(sym, parent) - parse(entity, packages, clean(expanded), expanded, d.pos).toComment(_.toHtml(entity)) - } - - (entity.path.mkString("."), cmt) - } - - - def add(entity: Entity, symbol: Symbol, parent: Symbol, ctx: Context): Unit = { - val commentParser = { (entity: Entity, packs: Map[String, Package]) => - parseHtml(symbol, parent, entity, packs)(ctx)._2 - } - - /** TODO: this if statement searches for doc comments in parent - * definitions if one is not defined for the current symbol. - * - * It might be a good idea to factor this out of the WikiParser - since - * it mutates the state of docbase sort of silently. - */ - implicit val implCtx = ctx - if (!ctx.docbase.docstring(symbol).isDefined) { - val parentCmt = - symbol.extendedOverriddenSymbols - .find(ctx.docbase.docstring(_).isDefined) - .flatMap(p => ctx.docbase.docstring(p)) - - ctx.docbase.addDocstring(symbol, parentCmt) - } - - - val path = entity.path.mkString(".") - if (!commentCache.contains(path) || ctx.docbase.docstring(symbol).isDefined) - commentCache = commentCache + (path -> commentParser) - } - - def +=(entity: Entity, symbol: Symbol, parent: Symbol, ctx: Context) = add(entity, symbol, parent, ctx) - - def size: Int = commentCache.size - - private def parse(entity: Entity, packs: Map[String, Package]): Option[Comment] = - commentCache(entity.path.mkString("."))(entity, packs) - - def parse(packs: Map[String, Package]): Unit = { - def rootPackages: List[String] = { - var currentDepth = Int.MaxValue - var packages: List[String] = Nil - - for (key <- packs.keys) { - val keyDepth = key.split("\\.").length - packages = - if (keyDepth < currentDepth) { - currentDepth = keyDepth - key :: Nil - } else if (keyDepth == currentDepth) { - key :: packages - } else packages - } - - packages - } - - for (pack <- rootPackages) { - mutateEntities(packs(pack)) { e => - val comment = parse(e, packs) - setComment(e, to = comment) - } - } - } - - def clear(): Unit = commentCache = Map.empty - } -} diff --git a/dottydoc/src/dotty/tools/dottydoc/util/syntax.scala b/dottydoc/src/dotty/tools/dottydoc/util/syntax.scala new file mode 100644 index 000000000000..140b3e761847 --- /dev/null +++ b/dottydoc/src/dotty/tools/dottydoc/util/syntax.scala @@ -0,0 +1,21 @@ +package dotty.tools +package dottydoc +package util + +import dotc.core.Contexts.{ Context, DocBase } +import model.Package + +object syntax { + implicit class RichDocContext(val ctx: Context) extends AnyVal { + def docbase: DocBase = ctx.getDocbase getOrElse { + throw new IllegalStateException("DocBase must be set before running dottydoc phases") + } + } + + implicit class RichDocBase(val db: DocBase) { + def packages: Map[String, Package] = db.packagesAs[Package].toMap + + def packagesMutable: collection.mutable.Map[String, Package] = + db.packagesAs[Package] + } +} diff --git a/dottydoc/test/BaseTest.scala b/dottydoc/test/BaseTest.scala index 2233d03c8b53..71bd1703f5b9 100644 --- a/dottydoc/test/BaseTest.scala +++ b/dottydoc/test/BaseTest.scala @@ -2,12 +2,13 @@ package dotty.tools package dottydoc import dotc.core.Contexts -import Contexts.{ Context, ContextBase, FreshContext } +import Contexts.{ Context, ContextBase, FreshContext, DocContext, DocBase } import dotc.util.SourceFile import dotc.core.Phases.Phase import dotc.typer.FrontEnd import dottydoc.core.DocASTPhase import model.Package +import dotty.tools.dottydoc.util.syntax._ trait DottyTest { dotty.tools.dotc.parsing.Scanners // initialize keywords @@ -18,6 +19,7 @@ trait DottyTest { val ctx = base.initialCtx.fresh ctx.setSetting(ctx.settings.language, List("Scala2")) ctx.setSetting(ctx.settings.YkeepComments, true) + ctx.setProperty(DocContext, new DocBase) base.initialize()(ctx) ctx } @@ -27,7 +29,7 @@ trait DottyTest { List(new Phase { def phaseName = "assertionPhase" override def run(implicit ctx: Context): Unit = - assertion(ctx.docbase.packages[Package].toMap) + assertion(ctx.docbase.packages) }) :: Nil override def phases = diff --git a/dottydoc/test/ConstructorTest.scala b/dottydoc/test/ConstructorTest.scala index 8aa8830223ef..44a05acad371 100644 --- a/dottydoc/test/ConstructorTest.scala +++ b/dottydoc/test/ConstructorTest.scala @@ -22,7 +22,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(cls: Class), _, _) => + case PackageImpl(_, _, List(cls: Class), _, _) => cls.constructors.headOption match { case Some(ParamListImpl(NamedReference("str", _, false, false) :: Nil, false) :: Nil) => // success! @@ -44,7 +44,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(cls: Class), _, _) => + case PackageImpl(_, _, List(cls: Class), _, _) => cls.constructors match { case ( ParamListImpl(NamedReference("str1", _, false, false) :: Nil, false) :: @@ -69,7 +69,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(cls: Class), _, _) => + case PackageImpl(_, _, List(cls: Class), _, _) => cls.constructors match { case ( ParamListImpl(NamedReference("str1", _, false, false) :: Nil, false) :: @@ -101,7 +101,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(cls: Class), _, _) => + case PackageImpl(_, _, List(cls: Class), _, _) => cls.constructors match { case ( ParamListImpl(NamedReference("main", _, false, false) :: Nil, false) :: Nil @@ -139,7 +139,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(cls: CaseClass, obj: Object), _, _) => + case PackageImpl(_, _, List(cls: CaseClass, obj: Object), _, _) => cls.constructors match { case ( ParamListImpl(NamedReference("main", _, false, false) :: Nil, false) :: Nil @@ -172,7 +172,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(trt: Trait), _, _) => + case PackageImpl(_, _, List(trt: Trait), _, _) => trt.traitParams match { case ParamListImpl(NamedReference("main", _, false, false) :: Nil, false) :: Nil => case _ => @@ -199,7 +199,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(cc: CaseClass, _, cls: Class, trt: Trait), _, _) => + case PackageImpl(_, _, List(cc: CaseClass, _, cls: Class, trt: Trait), _, _) => import model.json._ lazy val incorrectJson = s"The json generated for:\n$actualSource\n\nIs not correct" assert(cc.json.contains(s""""constructors":[[{"list":[{"title":"main""""), incorrectJson) diff --git a/dottydoc/test/PackageStructure.scala b/dottydoc/test/PackageStructure.scala index 00caaa2c0e0f..4e7006bfe262 100644 --- a/dottydoc/test/PackageStructure.scala +++ b/dottydoc/test/PackageStructure.scala @@ -29,7 +29,7 @@ class PackageStructure extends DottyTest { checkSources(source1 :: source2 :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(tA, tB), _, _) => + case PackageImpl(_, _, List(tA, tB), _, _) => assert( tA.name == "A" && tB.name == "B", s"trait A had name '${tA.name}' and trait B had name '${tB.name}'" @@ -62,8 +62,9 @@ class PackageStructure extends DottyTest { checkSources(source1 :: source2 :: Nil) { packages => packages("scala") match { case PackageImpl( + _, "scala", - List(PackageImpl("scala.collection", List(tA, tB), _, _)), + List(PackageImpl(_, "scala.collection", List(tA, tB), _, _)), _, _ ) => assert( @@ -76,7 +77,7 @@ class PackageStructure extends DottyTest { } packages("scala.collection") match { - case PackageImpl("scala.collection", List(tA, tB), _, _) => + case PackageImpl(_, "scala.collection", List(tA, tB), _, _) => assert( tA.name == "A" && tB.name == "B", s"trait A had name '${tA.name}' and trait B had name '${tB.name}'" diff --git a/dottydoc/test/UsecaseTest.scala b/dottydoc/test/UsecaseTest.scala new file mode 100644 index 000000000000..b5d47e4b6ce8 --- /dev/null +++ b/dottydoc/test/UsecaseTest.scala @@ -0,0 +1,234 @@ +package dotty.tools +package dottydoc + +import org.junit.Test +import org.junit.Assert._ + +import dotc.util.SourceFile +import model._ +import model.internal._ +import model.references._ +import util.syntax._ + +class UsecaseTest extends DottyTest { + @Test def simpleUsecase = { + val source = new SourceFile( + "DefWithUseCase.scala", + """ + |package scala + | + |trait Test[A] { + | /** Definition with a "disturbing" signature + | * + | * @usecase def foo: A + | */ + | def foo[B]: A => B + |} + """.stripMargin + ) + + checkSources(source :: Nil) { packages => + packages("scala") match { + case PackageImpl(_, _, List(trt: Trait), _, _) => + val List(foo: Def) = trt.members + + assert(foo.comment.isDefined, "Lost comment in transformations") + + val returnValue = foo.returnValue match { + case ref: TypeReference => ref.title + case _ => + assert( + false, + "Incorrect return value after usecase transformation" + ) + "" + } + + assert( + foo.typeParams.isEmpty, + "Type parameters were not stripped by usecase" + ) + assert(returnValue == "A", "Incorrect return type after usecase") + + assert(foo.name == "foo", s"Incorrect name after transform: ${foo.name}") + } + } + } + + @Test def simpleUsecaseAddedArg = { + val source = new SourceFile( + "DefWithUseCase.scala", + """ + |package scala + | + |trait Test[A] { + | /** Definition with a "disturbing" signature + | * + | * @usecase def foo(a: A): A + | */ + | def foo[B]: A => B + |} + """.stripMargin + ) + + checkSources(source :: Nil) { packages => + packages("scala") match { + case PackageImpl(_, _, List(trt: Trait), _, _) => + val List(foo: Def) = trt.members + + val returnValue = foo.returnValue match { + case ref: TypeReference => ref.title + case _ => + assert( + false, + "Incorrect return value after usecase transformation" + ) + "" + } + + assert( + foo.typeParams.isEmpty, + "Type parameters were not stripped by usecase" + ) + assert(returnValue == "A", "Incorrect return type after usecase") + assert( + foo.paramLists.head.list.head.title == "a", + "Incorrect parameter to function after usecase transformation" + ) + assert(foo.name == "foo", s"Incorrect name after transform: ${foo.name}") + } + } + } + + @Test def simpleTparamUsecase = { + val source = new SourceFile( + "DefWithUseCase.scala", + """ + |package scala + | + |trait Test[A] { + | /** Definition with a "disturbing" signature + | * + | * @usecase def foo[C]: A + | */ + | def foo[B]: A => B + |} + """.stripMargin + ) + + checkSources(source :: Nil) { packages => + packages("scala") match { + case PackageImpl(_, _, List(trt: Trait), _, _) => + val List(foo: Def) = trt.members + + val returnValue = foo.returnValue match { + case ref: TypeReference => ref.title + case _ => + assert( + false, + "Incorrect return value after usecase transformation" + ) + "" + } + + assert( + foo.typeParams.nonEmpty, + "Type parameters were incorrectly stripped by usecase" + ) + + assert(foo.typeParams.head == "C", "Incorrectly switched tparam") + assert(returnValue == "A", "Incorrect return type after usecase") + + assert(foo.name == "foo", s"Incorrect name after transform: ${foo.name}") + } + } + } + + @Test def expandColl = { + val source = new SourceFile( + "ExpandColl.scala", + """ + |package scala + | + |/** The trait $Coll + | * + | * @define Coll Iterable + | */ + |trait Iterable[A] { + | /** Definition with a "disturbing" signature + | * + | * @usecase def map[B](f: A => B): $Coll[B] + | */ + | def map[B, M[B]](f: A => B): M[B] = ??? + |} + """.stripMargin + ) + + checkSources(source :: Nil) { packages => + packages("scala") match { + case PackageImpl(_, _, List(trt: Trait), _, _) => + val List(map: Def) = trt.members + + val returnValue = map.returnValue match { + case ref: TypeReference => ref.title + case _ => + assert( + false, + "Incorrect return value after usecase transformation" + ) + "" + } + + assert( + returnValue == "Iterable", + "Incorrect return type after usecase transformation" + ) + } + } + } + + @Test def checkStripping = { + val source = new SourceFile( + "CheckStripping.scala", + """ + |package scala + | + |/** The trait $Coll + | * + | * @define Coll Iterable + | */ + |trait Iterable[A] { + | /** Definition with a "disturbing" signature + | * + | * @usecase def map[B](f: A => B): $Coll[B] + | */ + | def map[B, M[B]](f: A => B): M[B] = ??? + |} + """.stripMargin + ) + + checkSources(source :: Nil) { packages => + packages("scala") match { + case PackageImpl(_, _, List(trt: Trait), _, _) => + val List(map: Def) = trt.members + assert(map.comment.isDefined, "Lost comment in transformations") + + val docstr = ctx.docbase.docstring(map.symbol).get.raw + assert( + !docstr.contains("@usecase"), + s"Comment should not contain usecase after stripping, but was:\n$docstr" + ) + } + } + } + + @Test def checkIterator = + checkFiles("./scala-scala/src/library/scala/collection/Iterator.scala" :: Nil) { _ => + // success if typer throws no errors! :) + } + + @Test def checkIterableLike = + checkFiles("./scala-scala/src/library/scala/collection/IterableLike.scala" :: Nil) { _ => + // success if typer throws no errors! :) + } +} diff --git a/project/Build.scala b/project/Build.scala index eb3e41126e87..b06cadde7065 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -9,8 +9,10 @@ import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ object DottyBuild extends Build { + lazy val cleanBridge = TaskKey[Unit]("clean-sbt-bridge", "delete dotty-sbt-bridge cache") + val baseVersion = "0.1" - val isNightly = sys.props.get("NIGHTLYBUILD") == Some("yes") + val isNightly = sys.env.get("NIGHTLYBUILD") == Some("yes") val jenkinsMemLimit = List("-Xmx1300m") @@ -93,8 +95,6 @@ object DottyBuild extends Build { //http://stackoverflow.com/questions/10472840/how-to-attach-sources-to-sbt-managed-dependencies-in-scala-ide#answer-11683728 com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys.withSource := true, - resolvers += Resolver.sonatypeRepo("snapshots"), - // get libraries onboard partestDeps := Seq(scalaCompiler, "org.scala-lang" % "scala-reflect" % scalaVersion.value, @@ -102,7 +102,7 @@ object DottyBuild extends Build { libraryDependencies ++= partestDeps.value, libraryDependencies ++= Seq("org.scala-lang.modules" %% "scala-xml" % "1.0.1", "org.scala-lang.modules" %% "scala-partest" % "1.0.11" % "test", - "ch.epfl.lamp" % "dottydoc-client" % "0.1-SNAPSHOT", + "ch.epfl.lamp" % "dottydoc-client" % "0.1.0", "com.novocode" % "junit-interface" % "0.11" % "test", "com.github.spullara.mustache.java" % "compiler" % "0.9.3", "com.typesafe.sbt" % "sbt-interface" % sbtVersion.value), @@ -239,6 +239,21 @@ object DottyBuild extends Build { ). settings(ScriptedPlugin.scriptedSettings: _*). settings( + // until sbt/sbt#2402 is fixed (https://github.com/sbt/sbt/issues/2402) + cleanBridge := { + println("*** cleanBridge ***") + val dottyBridgeVersion = version.value + val dottyVersion = (version in dotty).value + val classVersion = System.getProperty("java.class.version") + val sbtV = sbtVersion.value + val home = System.getProperty("user.home") + val org = organization.value + val artifact = moduleName.value + + IO.delete(file(home) / ".ivy2" / "cache" / "org.scala-sbt" / s"$org-$artifact-$dottyBridgeVersion-bin_${dottyVersion}__$classVersion") + IO.delete(file(home) / ".sbt" / "boot" / "scala-2.10.6" / "org.scala-sbt" / "sbt" / sbtV / s"$org-$artifact-$dottyBridgeVersion-bin_${dottyVersion}__$classVersion") + }, + publishLocal <<= publishLocal.dependsOn(cleanBridge), ScriptedPlugin.scriptedLaunchOpts := Seq("-Xmx1024m"), ScriptedPlugin.scriptedBufferLog := false // TODO: Use this instead of manually copying DottyInjectedPlugin.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index d1f12686046d..2120fa73ec6e 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -25,7 +25,7 @@ class Compiler { /** Meta-ordering constraint: * - * DenotTransformers that change the signature of their denotation's info must go + * DenotTransformers that change the signature of their denotation's info must go * after erasure. The reason is that denotations are permanently referred to by * TermRefs which contain a signature. If the signature of a symbol would change, * all refs to it would become outdated - they could not be dereferenced in the @@ -83,7 +83,7 @@ class Compiler { new CapturedVars, // Represent vars captured by closures as heap objects new Constructors, // Collect initialization code in primary constructors // Note: constructors changes decls in transformTemplate, no InfoTransformers should be added after it - new FunctionalInterfaces,// Rewrites closures to implement @specialized types of Functions. + new FunctionalInterfaces, // Rewrites closures to implement @specialized types of Functions. new GetClass), // Rewrites getClass calls on primitive types. List(new LambdaLift, // Lifts out nested functions to class scope, storing free variables in environments // Note: in this mini-phase block scopes are incorrect. No phases that rely on scopes should be here diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 500b28233973..f7a85f54c3cc 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -8,7 +8,7 @@ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ import Decorators._ import language.higherKinds import collection.mutable.ListBuffer -import util.Attachment +import util.Property import config.Printers._ object desugar { @@ -21,13 +21,13 @@ object desugar { /** Tags a .withFilter call generated by desugaring a for expression. * Such calls can alternatively be rewritten to use filter. */ - val MaybeFilter = new Attachment.Key[Unit] + val MaybeFilter = new Property.Key[Unit] /** Info of a variable in a pattern: The named tree and its type */ private type VarInfo = (NameTree, Tree) /** Names of methods that are added unconditionally to case classes */ - def isDesugaredCaseClassMethodName(name: Name)(implicit ctx: Context) = + def isDesugaredCaseClassMethodName(name: Name)(implicit ctx: Context): Boolean = name == nme.isDefined || name == nme.copy || name == nme.productArity || @@ -699,7 +699,7 @@ object desugar { Apply(Select(left, op), args) } else { val x = ctx.freshName().toTermName - Block( + new InfixOpBlock( ValDef(x, TypeTree(), left).withMods(synthetic), Apply(Select(right, op), Ident(x))) } diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index a48651ebf981..7c3f7f3854ec 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -630,20 +630,6 @@ object TreeInfo { } } - def isApplyDynamicName(name: Name) = (name == nme.updateDynamic) || (name == nme.selectDynamic) || (name == nme.applyDynamic) || (name == nme.applyDynamicNamed) - - class DynamicApplicationExtractor(nameTest: Name => Boolean) { - def unapply(tree: Tree) = tree match { - case Apply(TypeApply(Select(qual, oper), _), List(Literal(Constant(name)))) if nameTest(oper) => Some((qual, name)) - case Apply(Select(qual, oper), List(Literal(Constant(name)))) if nameTest(oper) => Some((qual, name)) - case Apply(Ident(oper), List(Literal(Constant(name)))) if nameTest(oper) => Some((EmptyTree(), name)) - case _ => None - } - } - object DynamicUpdate extends DynamicApplicationExtractor(_ == nme.updateDynamic) - object DynamicApplication extends DynamicApplicationExtractor(isApplyDynamicName) - object DynamicApplicationNamed extends DynamicApplicationExtractor(_ == nme.applyDynamicNamed) - object MacroImplReference { private def refPart(tree: Tree): Tree = tree match { case TypeApply(fun, _) => refPart(fun) diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index cf11c27faa12..e4323e7ad9b0 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -4,7 +4,7 @@ package ast import core._ import Types._, Names._, Flags._, util.Positions._, Contexts._, Constants._, SymDenotations._, Symbols._ -import Denotations._, StdNames._ +import Denotations._, StdNames._, Comments._ import annotation.tailrec import language.higherKinds import collection.IndexedSeqOptimized @@ -12,10 +12,9 @@ import collection.immutable.IndexedSeq import collection.mutable.ListBuffer import parsing.Tokens.Token import printing.Printer -import util.{Stats, Attachment, DotClass} +import util.{Stats, Attachment, Property, DotClass} import annotation.unchecked.uncheckedVariance import language.implicitConversions -import parsing.Scanners.Comment object Trees { @@ -30,8 +29,8 @@ object Trees { /** The total number of created tree nodes, maintained if Stats.enabled */ @sharable var ntrees = 0 - /** Attachment key for trees with documentation strings attached */ - val DocComment = new Attachment.Key[Comment] + /** Property key for trees with documentation strings attached */ + val DocComment = new Property.Key[Comment] /** Modifiers and annotations for definitions * @param flags The set flags diff --git a/src/dotty/tools/dotc/ast/untpd.scala b/src/dotty/tools/dotc/ast/untpd.scala index cef78c6e682f..092ae529631a 100644 --- a/src/dotty/tools/dotc/ast/untpd.scala +++ b/src/dotty/tools/dotc/ast/untpd.scala @@ -6,7 +6,7 @@ import core._ import util.Positions._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._ import Denotations._, SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ import Decorators._ -import util.Attachment +import util.Property import language.higherKinds import collection.mutable.ListBuffer @@ -63,6 +63,16 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { class PolyTypeDef(name: TypeName, override val tparams: List[TypeDef], rhs: Tree) extends TypeDef(name, rhs) + /** A block arising from a right-associative infix operation, where, e.g. + * + * a +: b + * + * is expanded to + * + * { val x = a; b.+:(x) } + */ + class InfixOpBlock(leftOperand: Tree, rightOp: Tree) extends Block(leftOperand :: Nil, rightOp) + // ----- TypeTrees that refer to other tree's symbols ------------------- /** A type tree that gets its type from some other tree's symbol. Enters the @@ -92,17 +102,17 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def derivedType(originalSym: Symbol)(implicit ctx: Context): Type } - /** Attachment key containing TypeTrees whose type is computed + /** Property key containing TypeTrees whose type is computed * from the symbol in this type. These type trees have marker trees * TypeRefOfSym or InfoOfSym as their originals. */ - val References = new Attachment.Key[List[Tree]] + val References = new Property.Key[List[Tree]] - /** Attachment key for TypeTrees marked with TypeRefOfSym or InfoOfSym + /** Property key for TypeTrees marked with TypeRefOfSym or InfoOfSym * which contains the symbol of the original tree from which this * TypeTree is derived. */ - val OriginalSymbol = new Attachment.Key[Symbol] + val OriginalSymbol = new Property.Key[Symbol] // ------ Creation methods for untyped only ----------------- diff --git a/src/dotty/tools/dotc/core/Comments.scala b/src/dotty/tools/dotc/core/Comments.scala new file mode 100644 index 000000000000..4f36b3b6be7a --- /dev/null +++ b/src/dotty/tools/dotc/core/Comments.scala @@ -0,0 +1,423 @@ +package dotty.tools +package dotc +package core + +import dotc.ast.{ untpd, tpd } +import Decorators._ +import Symbols._ +import Contexts.Context +import Flags.EmptyFlags +import dotc.util.SourceFile +import dotc.util.Positions._ +import dotc.util.CommentParsing._ +import dotc.parsing.Parsers.Parser + +object Comments { + + abstract case class Comment(pos: Position, raw: String)(implicit ctx: Context) { self => + def isExpanded: Boolean + + def usecases: List[UseCase] + + val isDocComment = raw.startsWith("/**") + + def expand(f: String => String): Comment = new Comment(pos, f(raw)) { + val isExpanded = true + val usecases = self.usecases + } + + def withUsecases: Comment = new Comment(pos, stripUsecases) { + val isExpanded = self.isExpanded + val usecases = parseUsecases + } + + private[this] lazy val stripUsecases: String = + removeSections(raw, "@usecase", "@define") + + private[this] lazy val parseUsecases: List[UseCase] = + if (!raw.startsWith("/**")) + List.empty[UseCase] + else + tagIndex(raw) + .filter { startsWithTag(raw, _, "@usecase") } + .map { case (start, end) => decomposeUseCase(start, end) } + + /** Turns a usecase section into a UseCase, with code changed to: + * {{{ + * // From: + * def foo: A + * // To: + * def foo: A = ??? + * }}} + */ + private[this] def decomposeUseCase(start: Int, end: Int): UseCase = { + def subPos(start: Int, end: Int) = + if (pos == NoPosition) NoPosition + else { + val start1 = pos.start + start + val end1 = pos.end + end + pos withStart start1 withPoint start1 withEnd end1 + } + + val codeStart = skipWhitespace(raw, start + "@usecase".length) + val codeEnd = skipToEol(raw, codeStart) + val code = raw.substring(codeStart, codeEnd) + " = ???" + val codePos = subPos(codeStart, codeEnd) + val commentStart = skipLineLead(raw, codeEnd + 1) min end + val commentStr = "/** " + raw.substring(commentStart, end) + "*/" + val commentPos = subPos(commentStart, end) + + UseCase(Comment(commentPos, commentStr), code, codePos) + } + } + + object Comment { + def apply(pos: Position, raw: String, expanded: Boolean = false, usc: List[UseCase] = Nil)(implicit ctx: Context): Comment = + new Comment(pos, raw) { + val isExpanded = expanded + val usecases = usc + } + } + + case class UseCase(comment: Comment, code: String, codePos: Position)(implicit ctx: Context) { + /** Set by typer */ + var tpdCode: tpd.DefDef = _ + + lazy val untpdCode: untpd.Tree = { + val tree = new Parser(new SourceFile("", code)).localDef(codePos.start, EmptyFlags) + + tree match { + case tree: untpd.DefDef => + val newName = (tree.name.show + "$" + codePos + "$doc").toTermName + untpd.DefDef(newName, tree.tparams, tree.vparamss, tree.tpt, tree.rhs) + case _ => + ctx.error("proper definition was not found in `@usecase`", codePos) + tree + } + } + } + + /** + * Port of DocComment.scala from nsc + * @author Martin Odersky + * @author Felix Mulder + */ + class CommentExpander { + import dotc.config.Printers.dottydoc + import scala.collection.mutable + + def expand(sym: Symbol, site: Symbol)(implicit ctx: Context): String = { + val parent = if (site != NoSymbol) site else sym + defineVariables(parent) + expandedDocComment(sym, parent) + } + + /** The cooked doc comment of symbol `sym` after variable expansion, or "" if missing. + * + * @param sym The symbol for which doc comment is returned + * @param site The class for which doc comments are generated + * @throws ExpansionLimitExceeded when more than 10 successive expansions + * of the same string are done, which is + * interpreted as a recursive variable definition. + */ + def expandedDocComment(sym: Symbol, site: Symbol, docStr: String = "")(implicit ctx: Context): String = { + // when parsing a top level class or module, use the (module-)class itself to look up variable definitions + val parent = if ((sym.is(Flags.Module) || sym.isClass) && site.is(Flags.Package)) sym + else site + expandVariables(cookedDocComment(sym, docStr), sym, parent) + } + + private def template(raw: String): String = + removeSections(raw, "@define") + + private def defines(raw: String): List[String] = { + val sections = tagIndex(raw) + val defines = sections filter { startsWithTag(raw, _, "@define") } + val usecases = sections filter { startsWithTag(raw, _, "@usecase") } + val end = startTag(raw, (defines ::: usecases).sortBy(_._1)) + + defines map { case (start, end) => raw.substring(start, end) } + } + + private def replaceInheritDocToInheritdoc(docStr: String): String = + docStr.replaceAll("""\{@inheritDoc\p{Zs}*\}""", "@inheritdoc") + + /** The cooked doc comment of an overridden symbol */ + protected def superComment(sym: Symbol)(implicit ctx: Context): Option[String] = + allInheritedOverriddenSymbols(sym).iterator map (x => cookedDocComment(x)) find (_ != "") + + private val cookedDocComments = mutable.HashMap[Symbol, String]() + + /** The raw doc comment of symbol `sym`, minus usecase and define sections, augmented by + * missing sections of an inherited doc comment. + * If a symbol does not have a doc comment but some overridden version of it does, + * the doc comment of the overridden version is copied instead. + */ + def cookedDocComment(sym: Symbol, docStr: String = "")(implicit ctx: Context): String = cookedDocComments.getOrElseUpdate(sym, { + var ownComment = + if (docStr.length == 0) ctx.getDocbase.flatMap(_.docstring(sym).map(c => template(c.raw))).getOrElse("") + else template(docStr) + ownComment = replaceInheritDocToInheritdoc(ownComment) + + superComment(sym) match { + case None => + // SI-8210 - The warning would be false negative when this symbol is a setter + if (ownComment.indexOf("@inheritdoc") != -1 && ! sym.isSetter) + dottydoc.println(s"${sym.pos}: the comment for ${sym} contains @inheritdoc, but no parent comment is available to inherit from.") + ownComment.replaceAllLiterally("@inheritdoc", "") + case Some(sc) => + if (ownComment == "") sc + else expandInheritdoc(sc, merge(sc, ownComment, sym), sym) + } + }) + + private def isMovable(str: String, sec: (Int, Int)): Boolean = + startsWithTag(str, sec, "@param") || + startsWithTag(str, sec, "@tparam") || + startsWithTag(str, sec, "@return") + + def merge(src: String, dst: String, sym: Symbol, copyFirstPara: Boolean = false): String = { + val srcSections = tagIndex(src) + val dstSections = tagIndex(dst) + val srcParams = paramDocs(src, "@param", srcSections) + val dstParams = paramDocs(dst, "@param", dstSections) + val srcTParams = paramDocs(src, "@tparam", srcSections) + val dstTParams = paramDocs(dst, "@tparam", dstSections) + val out = new StringBuilder + var copied = 0 + var tocopy = startTag(dst, dstSections dropWhile (!isMovable(dst, _))) + + if (copyFirstPara) { + val eop = // end of comment body (first para), which is delimited by blank line, or tag, or end of comment + (findNext(src, 0)(src.charAt(_) == '\n')) min startTag(src, srcSections) + out append src.substring(0, eop).trim + copied = 3 + tocopy = 3 + } + + def mergeSection(srcSec: Option[(Int, Int)], dstSec: Option[(Int, Int)]) = dstSec match { + case Some((start, end)) => + if (end > tocopy) tocopy = end + case None => + srcSec match { + case Some((start1, end1)) => { + out append dst.substring(copied, tocopy).trim + out append "\n" + copied = tocopy + out append src.substring(start1, end1).trim + } + case None => + } + } + + //TODO: enable this once you know how to get `sym.paramss` + /* + for (params <- sym.paramss; param <- params) + mergeSection(srcParams get param.name.toString, dstParams get param.name.toString) + for (tparam <- sym.typeParams) + mergeSection(srcTParams get tparam.name.toString, dstTParams get tparam.name.toString) + + mergeSection(returnDoc(src, srcSections), returnDoc(dst, dstSections)) + mergeSection(groupDoc(src, srcSections), groupDoc(dst, dstSections)) + */ + + if (out.length == 0) dst + else { + out append dst.substring(copied) + out.toString + } + } + + /** + * Expand inheritdoc tags + * - for the main comment we transform the inheritdoc into the super variable, + * and the variable expansion can expand it further + * - for the param, tparam and throws sections we must replace comments on the spot + * + * This is done separately, for two reasons: + * 1. It takes longer to run compared to merge + * 2. The inheritdoc annotation should not be used very often, as building the comment from pieces severely + * impacts performance + * + * @param parent The source (or parent) comment + * @param child The child (overriding member or usecase) comment + * @param sym The child symbol + * @return The child comment with the inheritdoc sections expanded + */ + def expandInheritdoc(parent: String, child: String, sym: Symbol): String = + if (child.indexOf("@inheritdoc") == -1) + child + else { + val parentSections = tagIndex(parent) + val childSections = tagIndex(child) + val parentTagMap = sectionTagMap(parent, parentSections) + val parentNamedParams = Map() + + ("@param" -> paramDocs(parent, "@param", parentSections)) + + ("@tparam" -> paramDocs(parent, "@tparam", parentSections)) + + ("@throws" -> paramDocs(parent, "@throws", parentSections)) + + val out = new StringBuilder + + def replaceInheritdoc(childSection: String, parentSection: => String) = + if (childSection.indexOf("@inheritdoc") == -1) + childSection + else + childSection.replaceAllLiterally("@inheritdoc", parentSection) + + def getParentSection(section: (Int, Int)): String = { + + def getSectionHeader = extractSectionTag(child, section) match { + case param@("@param"|"@tparam"|"@throws") => param + " " + extractSectionParam(child, section) + case other => other + } + + def sectionString(param: String, paramMap: Map[String, (Int, Int)]): String = + paramMap.get(param) match { + case Some(section) => + // Cleanup the section tag and parameter + val sectionTextBounds = extractSectionText(parent, section) + cleanupSectionText(parent.substring(sectionTextBounds._1, sectionTextBounds._2)) + case None => + dottydoc.println(s"""${sym.pos}: the """" + getSectionHeader + "\" annotation of the " + sym + + " comment contains @inheritdoc, but the corresponding section in the parent is not defined.") + "" + } + + child.substring(section._1, section._1 + 7) match { + case param@("@param "|"@tparam"|"@throws") => + sectionString(extractSectionParam(child, section), parentNamedParams(param.trim)) + case _ => + sectionString(extractSectionTag(child, section), parentTagMap) + } + } + + def mainComment(str: String, sections: List[(Int, Int)]): String = + if (str.trim.length > 3) + str.trim.substring(3, startTag(str, sections)) + else + "" + + // Append main comment + out.append("/**") + out.append(replaceInheritdoc(mainComment(child, childSections), mainComment(parent, parentSections))) + + // Append sections + for (section <- childSections) + out.append(replaceInheritdoc(child.substring(section._1, section._2), getParentSection(section))) + + out.append("*/") + out.toString + } + + protected def expandVariables(initialStr: String, sym: Symbol, site: Symbol)(implicit ctx: Context): String = { + val expandLimit = 10 + + def expandInternal(str: String, depth: Int): String = { + if (depth >= expandLimit) + throw new ExpansionLimitExceeded(str) + + val out = new StringBuilder + var copied, idx = 0 + // excluding variables written as \$foo so we can use them when + // necessary to document things like Symbol#decode + def isEscaped = idx > 0 && str.charAt(idx - 1) == '\\' + while (idx < str.length) { + if ((str charAt idx) != '$' || isEscaped) + idx += 1 + else { + val vstart = idx + idx = skipVariable(str, idx + 1) + def replaceWith(repl: String) = { + out append str.substring(copied, vstart) + out append repl + copied = idx + } + variableName(str.substring(vstart + 1, idx)) match { + case "super" => + superComment(sym) foreach { sc => + val superSections = tagIndex(sc) + replaceWith(sc.substring(3, startTag(sc, superSections))) + for (sec @ (start, end) <- superSections) + if (!isMovable(sc, sec)) out append sc.substring(start, end) + } + case "" => idx += 1 + case vname => + lookupVariable(vname, site) match { + case Some(replacement) => replaceWith(replacement) + case None => + dottydoc.println(s"Variable $vname undefined in comment for $sym in $site") + } + } + } + } + if (out.length == 0) str + else { + out append str.substring(copied) + expandInternal(out.toString, depth + 1) + } + } + + // We suppressed expanding \$ throughout the recursion, and now we + // need to replace \$ with $ so it looks as intended. + expandInternal(initialStr, 0).replaceAllLiterally("""\$""", "$") + } + + def defineVariables(sym: Symbol)(implicit ctx: Context) = { + val Trim = "(?s)^[\\s&&[^\n\r]]*(.*?)\\s*$".r + + val raw = ctx.getDocbase.flatMap(_.docstring(sym).map(_.raw)).getOrElse("") + defs(sym) ++= defines(raw).map { + str => { + val start = skipWhitespace(str, "@define".length) + val (key, value) = str.splitAt(skipVariable(str, start)) + key.drop(start) -> value + } + } map { + case (key, Trim(value)) => + variableName(key) -> value.replaceAll("\\s+\\*+$", "") + } + } + + /** Maps symbols to the variable -> replacement maps that are defined + * in their doc comments + */ + private val defs = mutable.HashMap[Symbol, Map[String, String]]() withDefaultValue Map() + + /** Lookup definition of variable. + * + * @param vble The variable for which a definition is searched + * @param site The class for which doc comments are generated + */ + def lookupVariable(vble: String, site: Symbol)(implicit ctx: Context): Option[String] = site match { + case NoSymbol => None + case _ => + val searchList = + if (site.flags.is(Flags.Module)) site :: site.info.baseClasses + else site.info.baseClasses + + searchList collectFirst { case x if defs(x) contains vble => defs(x)(vble) } match { + case Some(str) if str startsWith "$" => lookupVariable(str.tail, site) + case res => res orElse lookupVariable(vble, site.owner) + } + } + + /** The position of the raw doc comment of symbol `sym`, or NoPosition if missing + * If a symbol does not have a doc comment but some overridden version of it does, + * the position of the doc comment of the overridden version is returned instead. + */ + def docCommentPos(sym: Symbol)(implicit ctx: Context): Position = + ctx.getDocbase.flatMap(_.docstring(sym).map(_.pos)).getOrElse(NoPosition) + + /** A version which doesn't consider self types, as a temporary measure: + * an infinite loop has broken out between superComment and cookedDocComment + * since r23926. + */ + private def allInheritedOverriddenSymbols(sym: Symbol)(implicit ctx: Context): List[Symbol] = { + if (!sym.owner.isClass) Nil + else sym.allOverriddenSymbols.toList.filter(_ != NoSymbol) //TODO: could also be `sym.owner.allOverrid..` + //else sym.owner.ancestors map (sym overriddenSymbol _) filter (_ != NoSymbol) + } + + class ExpansionLimitExceeded(str: String) extends Exception + } +} diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index 436b035dcc3f..99b4af0a9a11 100644 --- a/src/dotty/tools/dotc/core/Constraint.scala +++ b/src/dotty/tools/dotc/core/Constraint.scala @@ -143,6 +143,9 @@ abstract class Constraint extends Showable { /** The uninstantiated typevars of this constraint */ def uninstVars: collection.Seq[TypeVar] + /** The weakest constraint that subsumes both this constraint and `other` */ + def & (other: Constraint)(implicit ctx: Context): Constraint + /** Check that no constrained parameter contains itself as a bound */ def checkNonCyclic()(implicit ctx: Context): Unit diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index cd76fe88bb45..68b7b4e77ddf 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -13,6 +13,7 @@ import Scopes._ import NameOps._ import Uniques._ import SymDenotations._ +import Comments._ import Flags.ParamAccessor import util.Positions._ import ast.Trees._ @@ -29,7 +30,7 @@ import printing._ import config.{Settings, ScalaSettings, Platform, JavaPlatform, SJSPlatform} import language.implicitConversions import DenotTransformers.DenotTransformer -import parsing.Scanners.Comment +import util.Property.Key import xsbti.AnalysisCallback object Contexts { @@ -68,6 +69,9 @@ object Contexts { /** The context base at the root */ val base: ContextBase + /** Documentation base */ + def getDocbase = property(DocContext) + /** All outer contexts, ending in `base.initialCtx` and then `NoContext` */ def outersIterator = new Iterator[Context] { var current = thiscontext @@ -177,9 +181,12 @@ object Contexts { def freshName(prefix: Name): String = freshName(prefix.toString) /** A map in which more contextual properties can be stored */ - private var _moreProperties: Map[String, Any] = _ - protected def moreProperties_=(moreProperties: Map[String, Any]) = _moreProperties = moreProperties - def moreProperties: Map[String, Any] = _moreProperties + private var _moreProperties: Map[Key[Any], Any] = _ + protected def moreProperties_=(moreProperties: Map[Key[Any], Any]) = _moreProperties = moreProperties + def moreProperties: Map[Key[Any], Any] = _moreProperties + + def property[T](key: Key[T]): Option[T] = + moreProperties.get(key).asInstanceOf[Option[T]] private var _typeComparer: TypeComparer = _ protected def typeComparer_=(typeComparer: TypeComparer) = _typeComparer = typeComparer @@ -459,9 +466,10 @@ object Contexts { def setTypeComparerFn(tcfn: Context => TypeComparer): this.type = { this.typeComparer = tcfn(this); this } def setSearchHistory(searchHistory: SearchHistory): this.type = { this.searchHistory = searchHistory; this } def setFreshNames(freshNames: FreshNameCreator): this.type = { this.freshNames = freshNames; this } - def setMoreProperties(moreProperties: Map[String, Any]): this.type = { this.moreProperties = moreProperties; this } + def setMoreProperties(moreProperties: Map[Key[Any], Any]): this.type = { this.moreProperties = moreProperties; this } - def setProperty(prop: (String, Any)): this.type = setMoreProperties(moreProperties + prop) + def setProperty[T](key: Key[T], value: T): this.type = + setMoreProperties(moreProperties.updated(key, value)) def setPhase(pid: PhaseId): this.type = setPeriod(Period(runId, pid)) def setPhase(phase: Phase): this.type = setPeriod(Period(runId, phase.start, phase.end)) @@ -532,9 +540,6 @@ object Contexts { /** The symbol loaders */ val loaders = new SymbolLoaders - /** Documentation base */ - val docbase = new DocBase - /** The platform, initialized by `initPlatform()`. */ private var _platform: Platform = _ @@ -573,10 +578,15 @@ object Contexts { } } + val DocContext = new Key[DocBase] class DocBase { private[this] val _docstrings: mutable.Map[Symbol, Comment] = mutable.Map.empty + val templateExpander = new CommentExpander + + def docstrings: Map[Symbol, Comment] = _docstrings.toMap + def docstring(sym: Symbol): Option[Comment] = _docstrings.get(sym) def addDocstring(sym: Symbol, doc: Option[Comment]): Unit = @@ -588,7 +598,7 @@ object Contexts { * map of `String -> AnyRef` */ private[this] val _packages: mutable.Map[String, AnyRef] = mutable.Map.empty - def packages[A]: mutable.Map[String, A] = _packages.asInstanceOf[mutable.Map[String, A]] + def packagesAs[A]: mutable.Map[String, A] = _packages.asInstanceOf[mutable.Map[String, A]] /** Should perhaps factorize this into caches that get flushed */ private var _defs: Map[Symbol, Set[Symbol]] = Map.empty diff --git a/src/dotty/tools/dotc/core/OrderingConstraint.scala b/src/dotty/tools/dotc/core/OrderingConstraint.scala index b0170b67c74c..e7e388be9793 100644 --- a/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -15,11 +15,13 @@ import annotation.tailrec object OrderingConstraint { + type ArrayValuedMap[T] = SimpleMap[GenericType, Array[T]] + /** The type of `OrderingConstraint#boundsMap` */ - type ParamBounds = SimpleMap[GenericType, Array[Type]] + type ParamBounds = ArrayValuedMap[Type] /** The type of `OrderingConstraint#lowerMap`, `OrderingConstraint#upperMap` */ - type ParamOrdering = SimpleMap[GenericType, Array[List[PolyParam]]] + type ParamOrdering = ArrayValuedMap[List[PolyParam]] /** A new constraint with given maps */ private def newConstraint(boundsMap: ParamBounds, lowerMap: ParamOrdering, upperMap: ParamOrdering)(implicit ctx: Context) : OrderingConstraint = { @@ -495,6 +497,44 @@ class OrderingConstraint(private val boundsMap: ParamBounds, } } + def & (other: Constraint)(implicit ctx: Context) = { + def merge[T](m1: ArrayValuedMap[T], m2: ArrayValuedMap[T], join: (T, T) => T): ArrayValuedMap[T] = { + var merged = m1 + def mergeArrays(xs1: Array[T], xs2: Array[T]) = { + val xs = xs1.clone + for (i <- xs.indices) xs(i) = join(xs1(i), xs2(i)) + xs + } + m2.foreachBinding { (poly, xs2) => + merged = merged.updated(poly, + if (m1.contains(poly)) mergeArrays(m1(poly), xs2) else xs2) + } + merged + } + + def mergeParams(ps1: List[PolyParam], ps2: List[PolyParam]) = + (ps1 /: ps2)((ps1, p2) => if (ps1.contains(p2)) ps1 else p2 :: ps1) + + def mergeEntries(e1: Type, e2: Type): Type = e1 match { + case e1: TypeBounds => + e2 match { + case e2: TypeBounds => e1 & e2 + case _ if e1 contains e2 => e2 + case _ => mergeError + } + case _ if e1 eq e2 => e1 + case _ => mergeError + } + + def mergeError = throw new AssertionError(i"cannot merge $this with $other") + + val that = other.asInstanceOf[OrderingConstraint] + new OrderingConstraint( + merge(this.boundsMap, that.boundsMap, mergeEntries), + merge(this.lowerMap, that.lowerMap, mergeParams), + merge(this.upperMap, that.upperMap, mergeParams)) + } + override def checkClosed()(implicit ctx: Context): Unit = { def isFreePolyParam(tp: Type) = tp match { case PolyParam(binder: GenericType, _) => !contains(binder) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 47ec541ab84c..abf404068616 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1515,7 +1515,9 @@ object SymDenotations { /** Enter a symbol in given `scope` without potentially replacing the old copy. */ def enterNoReplace(sym: Symbol, scope: MutableScope)(implicit ctx: Context): Unit = { - require((sym.denot.flagsUNSAFE is Private) || !(this is Frozen) || (scope ne this.unforcedDecls) || sym.hasAnnotation(defn.ScalaStaticAnnot)) + def isUsecase = ctx.property(DocContext).isDefined && sym.name.show.takeRight(4) == "$doc" + + require((sym.denot.flagsUNSAFE is Private) || !(this is Frozen) || (scope ne this.unforcedDecls) || sym.hasAnnotation(defn.ScalaStaticAnnot) || isUsecase) scope.enter(sym) if (myMemberFingerPrint != FingerPrint.unknown) diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 69c35faf50ce..7b8867ccc120 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -59,18 +59,10 @@ class TyperState(r: Reporter) extends DotClass with Showable { /** Commit state so that it gets propagated to enclosing context */ def commit()(implicit ctx: Context): Unit = unsupported("commit") - /** The typer state has already been committed */ - def isCommitted: Boolean = false - - /** Optionally, if this is a mutable typerstate, it's creator state */ - def parent: Option[TyperState] = None - /** The closest ancestor of this typer state (including possibly this typer state itself) * which is not yet committed, or which does not have a parent. */ - def uncommittedAncestor: TyperState = - if (!isCommitted || !parent.isDefined) this - else parent.get.uncommittedAncestor + def uncommittedAncestor: TyperState = this /** Make type variable instances permanent by assigning to `inst` field if * type variable instantiation cannot be retracted anymore. Then, remove @@ -96,7 +88,8 @@ extends TyperState(r) { override def reporter = myReporter - private var myConstraint: Constraint = previous.constraint + private val previousConstraint = previous.constraint + private var myConstraint: Constraint = previousConstraint override def constraint = myConstraint override def constraint_=(c: Constraint)(implicit ctx: Context) = { @@ -109,7 +102,6 @@ extends TyperState(r) { override def ephemeral = myEphemeral override def ephemeral_=(x: Boolean): Unit = { myEphemeral = x } - override def fresh(isCommittable: Boolean): TyperState = new MutableTyperState(this, new StoreReporter(reporter), isCommittable) @@ -120,6 +112,11 @@ extends TyperState(r) { isCommittable && (!previous.isInstanceOf[MutableTyperState] || previous.isGlobalCommittable) + private var isCommitted = false + + override def uncommittedAncestor: TyperState = + if (isCommitted) previous.uncommittedAncestor else this + /** Commit typer state so that its information is copied into current typer state * In addition (1) the owning state of undetermined or temporarily instantiated * type variables changes from this typer state to the current one. (2) Variables @@ -128,25 +125,20 @@ extends TyperState(r) { */ override def commit()(implicit ctx: Context) = { val targetState = ctx.typerState - assert(targetState eq previous) assert(isCommittable) - targetState.constraint = constraint + targetState.constraint = + if (targetState.constraint eq previousConstraint) constraint + else targetState.constraint & constraint constraint foreachTypeVar { tvar => if (tvar.owningState eq this) tvar.owningState = targetState } - targetState.ephemeral = ephemeral + targetState.ephemeral |= ephemeral targetState.gc() reporter.flush() - myIsCommitted = true + isCommitted = true } - private var myIsCommitted = false - - override def isCommitted: Boolean = myIsCommitted - - override def parent = Some(previous) - override def gc()(implicit ctx: Context): Unit = { val toCollect = new mutable.ListBuffer[GenericType] constraint foreachTypeVar { tvar => diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 87d94dcbe27c..46a63555cc6f 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -927,7 +927,7 @@ object Types { def narrow(implicit ctx: Context): TermRef = TermRef(NoPrefix, ctx.newSkolem(this)) - /** Useful for diagnsotics: The underlying type if this type is a type proxy, + /** Useful for diagnostics: The underlying type if this type is a type proxy, * otherwise NoType */ def underlyingIfProxy(implicit ctx: Context) = this match { @@ -935,6 +935,9 @@ object Types { case _ => NoType } + /** If this is a FunProto or PolyProto, WildcardType, otherwise this. */ + def notApplied: Type = this + // ----- Normalizing typerefs over refined types ---------------------------- /** If this normalizes* to a refinement type that has a refinement for `name` (which might be followed @@ -2532,8 +2535,8 @@ object Types { /** 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 = paramBoundsExp(this) - val resType = resultTypeExp(this) + val paramBounds: List[TypeBounds] = paramBoundsExp(this) + val resType: Type = resultTypeExp(this) def variances = Nil protected def computeSignature(implicit ctx: Context) = resultSignature diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 378aa6ed71f8..ddce07c73499 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -19,10 +19,10 @@ import StdNames._ import util.Positions._ import Constants._ import ScriptParsers._ +import Comments._ import scala.annotation.{tailrec, switch} import util.DotClass import rewrite.Rewrites.patch -import Scanners.Comment object Parsers { @@ -1009,9 +1009,23 @@ object Parsers { in.nextToken() expr() } else EmptyTree + + 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 _ => + } + val finalizer = - if (handler.isEmpty || in.token == FINALLY) { accept(FINALLY); expr() } - else EmptyTree + 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.") + + EmptyTree + } ParsedTry(body, handler, finalizer) } case THROW => diff --git a/src/dotty/tools/dotc/parsing/Scanners.scala b/src/dotty/tools/dotc/parsing/Scanners.scala index b46ab634877f..41ed7f0ce33e 100644 --- a/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/src/dotty/tools/dotc/parsing/Scanners.scala @@ -3,7 +3,7 @@ package dotc package parsing import core.Names._, core.Contexts._, core.Decorators._, util.Positions._ -import core.StdNames._ +import core.StdNames._, core.Comments._ import util.SourceFile import java.lang.Character.isDigit import scala.reflect.internal.Chars._ @@ -22,10 +22,6 @@ object Scanners { /** An undefined offset */ val NoOffset: Offset = -1 - case class Comment(pos: Position, chrs: String) { - def isDocComment = chrs.startsWith("/**") - } - type Token = Int trait TokenData { diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala index 6a52b128c141..60ef1b3061da 100644 --- a/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -15,7 +15,7 @@ import ast.Trees._ import SymUtils._ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Phases.Phase -import util.Attachment +import util.Property import collection.mutable /** This phase adds outer accessors to classes and traits that need them. @@ -36,7 +36,7 @@ class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransf import ExplicitOuter._ import ast.tpd._ - val Outer = new Attachment.Key[Tree] + val Outer = new Property.Key[Tree] override def phaseName: String = "explicitOuter" diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 21b56959b2ac..490feb7d0fb4 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -37,7 +37,7 @@ import scala.reflect.internal.util.Collections * elimRepeated is required * TODO: outer tests are not generated yet. */ -class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTransformer => +class PatternMatcher extends MiniPhaseTransform with DenotTransformer { import dotty.tools.dotc.ast.tpd._ override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref @@ -80,7 +80,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans ctx.newSymbol(owner, ctx.freshName(prefix + ctr).toTermName, Flags.Synthetic | Flags.Case, tp, coord = pos) } - def newSynthCaseLabel(name: String, tpe:Type, owner: Symbol = ctx.owner) = + def newSynthCaseLabel(name: String, tpe: Type, owner: Symbol = ctx.owner) = ctx.newSymbol(owner, ctx.freshName(name).toTermName, Flags.Label | Flags.Synthetic | Flags.Method, tpe).asTerm //NoSymbol.newLabel(freshName(name), NoPosition) setFlag treeInfo.SYNTH_CASE_FLAGS @@ -148,30 +148,28 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans } } + object Rebindings { + def apply(from: Symbol, to: Symbol) = new Rebindings(List(from), List(ref(to))) + // requires sameLength(from, to) + def apply(from: List[Symbol], to: List[Tree]) = + if (from nonEmpty) new Rebindings(from, to) else NoRebindings + } - object Rebindings { - def apply(from: Symbol, to: Symbol) = new Rebindings(List(from), List(ref(to))) - // requires sameLength(from, to) - def apply(from: List[Symbol], to: List[Tree]) = - if (from nonEmpty) new Rebindings(from, to) else NoRebindings - } - - class Rebindings(val lhs: List[Symbol], val rhs: List[Tree]) { - def >>(other: Rebindings) = { - if (other eq NoRebindings) this - else if (this eq NoRebindings) other - else { - assert((lhs.toSet ++ other.lhs.toSet).size == lhs.length + other.lhs.length, "no double assignments") - new Rebindings(this.lhs ++ other.lhs, this.rhs ++ other.rhs) - } - } - - def emitValDefs: List[ValDef] = { - Collections.map2(lhs, rhs)((symbol, tree) => ValDef(symbol.asTerm, tree.ensureConforms(symbol.info))) + class Rebindings(val lhs: List[Symbol], val rhs: List[Tree]) { + def >>(other: Rebindings) = { + if (other eq NoRebindings) this + else if (this eq NoRebindings) other + else { + assert((lhs.toSet ++ other.lhs.toSet).size == lhs.length + other.lhs.length, "no double assignments") + new Rebindings(this.lhs ++ other.lhs, this.rhs ++ other.rhs) } } - object NoRebindings extends Rebindings(Nil, Nil) + def emitValDefs: List[ValDef] = { + Collections.map2(lhs, rhs)((symbol, tree) => ValDef(symbol.asTerm, tree.ensureConforms(symbol.info))) + } + } + object NoRebindings extends Rebindings(Nil, Nil) trait OptimizedCodegen extends CodegenCore { override def codegen: AbsCodegen = optimizedCodegen @@ -192,12 +190,13 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans //val matchRes = ctx.newSymbol(NoSymbol, ctx.freshName("matchRes").toTermName, Flags.Synthetic | Flags.Param | Flags.Label | Flags.Method, restpe /*withoutAnnotations*/) //NoSymbol.newValueParameter(newTermName("x"), NoPosition, newFlags = SYNTHETIC) setInfo restpe.withoutAnnotations - val caseSyms = cases.scanLeft(ctx.owner.asTerm)((curOwner, nextTree) => newSynthCaseLabel(ctx.freshName("case"), MethodType(Nil, restpe), curOwner)).tail + + val caseSyms: List[TermSymbol] = cases.scanLeft(ctx.owner.asTerm)((curOwner, nextTree) => newSynthCaseLabel(ctx.freshName("case"), MethodType(Nil, restpe), curOwner)).tail // must compute catchAll after caseLabels (side-effects nextCase) // catchAll.isEmpty iff no synthetic default case needed (the (last) user-defined case is a default) // if the last user-defined case is a default, it will never jump to the next case; it will go immediately to matchEnd - val catchAllDef = matchFailGen.map { _(scrutSym)} + val catchAllDef = matchFailGen.map { _(scrutSym) } .getOrElse(Throw(New(defn.MatchErrorType, List(ref(scrutSym))))) val matchFail = newSynthCaseLabel(ctx.freshName("matchFail"), MethodType(Nil, restpe)) @@ -207,14 +206,13 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val caseDefs = (cases zip caseSyms zip nextCases).foldRight[Tree](catchAllDefBody) { // dotty deviation //case (((mkCase, sym), nextCase), acc) => - (x:(((Casegen => Tree), TermSymbol), Tree), acc: Tree) => x match { - - case ((mkCase, sym), nextCase) => - val body = mkCase(new OptimizedCasegen(nextCase)).ensureConforms(restpe) - - DefDef(sym, _ => Block(List(acc), body)) - }} + (x: (((Casegen => Tree), TermSymbol), Tree), acc: Tree) => x match { + case ((mkCase, sym), nextCase) => + val body = mkCase(new OptimizedCasegen(nextCase)).ensureConforms(restpe) + DefDef(sym, _ => Block(List(acc), body)) + } + } // scrutSym == NoSymbol when generating an alternatives matcher // val scrutDef = scrutSym.fold(List[Tree]())(ValDef(_, scrut) :: Nil) // for alternatives @@ -255,9 +253,13 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans ) } else { assert(defn.isProductSubType(prev.tpe)) - Block( - List(ValDef(b.asTerm, prev)), - next //Substitution(b, ref(prevSym))(next) + val nullCheck: Tree = prev.select(defn.Object_ne).appliedTo(Literal(Constant(null))) + ifThenElseZero( + nullCheck, + Block( + List(ValDef(b.asTerm, prev)), + next //Substitution(b, ref(prevSym))(next) + ) ) } } @@ -285,7 +287,6 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans next )) } - } } final case class Suppression(exhaustive: Boolean, unreachable: Boolean) @@ -642,7 +643,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val checkedLength: Option[Int], val prevBinder: Symbol, val ignoredSubPatBinders: Set[Symbol] - ) extends FunTreeMaker with PreserveSubPatBinders { + ) extends FunTreeMaker with PreserveSubPatBinders { def extraStoredBinders: Set[Symbol] = Set() @@ -664,9 +665,8 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans bindSubPats(next) } - if (extractorReturnsBoolean) casegen.flatMapCond(extractor, unitLiteral, nextBinder, condAndNext) - else casegen.flatMap(extractor, nextBinder, condAndNext) // getType? - + if (extractorReturnsBoolean) casegen.flatMapCond(extractor, unitLiteral, nextBinder, condAndNext) + else casegen.flatMap(extractor, nextBinder, condAndNext) // getType? } override def toString = "X" + ((extractor, nextBinder.name)) @@ -700,7 +700,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val mutableBinders: List[Symbol], binderKnownNonNull: Boolean, val ignoredSubPatBinders: Set[Symbol] - ) extends FunTreeMaker with PreserveSubPatBinders { + ) extends FunTreeMaker with PreserveSubPatBinders { val nextBinder = prevBinder // just passing through @@ -709,6 +709,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans def chainBefore(next: Tree)(casegen: Casegen): Tree = { val nullCheck: Tree = ref(prevBinder).select(defn.Object_ne).appliedTo(Literal(Constant(null))) + val cond: Option[Tree] = if (binderKnownNonNull) extraCond else extraCond.map(nullCheck.select(defn.Boolean_&&).appliedTo).orElse(Some(nullCheck)) @@ -782,9 +783,9 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val expectedClass = expectedTp.dealias.classSymbol.asClass val test = codegen._asInstanceOf(testedBinder, expectedTp) val outerAccessorTested = ctx.atPhase(ctx.explicitOuterPhase.next) { implicit ctx => - ExplicitOuter.ensureOuterAccessors(expectedClass) - test.select(ExplicitOuter.outerAccessor(expectedClass)).select(defn.Object_eq).appliedTo(expectedOuter) - } + ExplicitOuter.ensureOuterAccessors(expectedClass) + test.select(ExplicitOuter.outerAccessor(expectedClass)).select(defn.Object_eq).appliedTo(expectedOuter) + } outerAccessorTested } } @@ -848,7 +849,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val nextBinder = afterTest.asTerm - def needsOuterTest(patType: Type, selType: Type, currentOwner: Symbol) = { + def needsOuterTest(patType: Type, selType: Type, currentOwner: Symbol): Boolean = { // See the test for SI-7214 for motivation for dealias. Later `treeCondStrategy#outerTest` // generates an outer test based on `patType.prefix` with automatically dealises. patType.dealias match { @@ -866,7 +867,6 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val np = expectedTp.normalizedPrefix val ts = np.termSymbol (ts ne NoSymbol) && needsOuterTest(expectedTp, testedBinder.info, ctx.owner) - } // the logic to generate the run-time test that follows from the fact that @@ -906,7 +906,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans if (isExpectedReferenceType) mkNullTest else mkTypeTest ) - ) + ) // true when called to type-test the argument to an extractor // don't do any fancy equality checking, just test the type @@ -920,7 +920,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans and(mkEqualsTest(ref(tref.symbol.companionModule)), mkTypeTest) // must use == to support e.g. List() == Nil case ConstantType(Constant(null)) if isAnyRef => mkEqTest(expTp(Literal(Constant(null)))) case ConstantType(const) => mkEqualsTest(expTp(Literal(const))) - case t:SingletonType => mkEqTest(singleton(expectedTp)) // SI-4577, SI-4897 + case t: SingletonType => mkEqTest(singleton(expectedTp)) // SI-4577, SI-4897 //case ThisType(sym) => mkEqTest(expTp(This(sym))) case _ => mkDefault } @@ -1050,7 +1050,6 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val (cases, toHoist) = optimizeCases(scrutSym, casesRebindingPropagated, pt) - val matchRes = codegen.matcher(scrut, scrutSym, pt)(cases.map(x => combineExtractors(x) _), synthCatchAll) if (toHoist isEmpty) matchRes else Block(toHoist, matchRes) @@ -1092,7 +1091,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans def unapply(pat: Tree): Boolean = pat match { case Typed(_, arg) if arg.tpe.isRepeatedParam => true case Bind(nme.WILDCARD, WildcardPattern()) => true // don't skip when binding an interesting symbol! - case t if (tpd.isWildcardArg(t)) => true + case t if (tpd.isWildcardArg(t)) => true case x: Ident => isVarPattern(x) case Alternative(ps) => ps forall unapply case EmptyTree => true @@ -1113,7 +1112,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans object SymbolBound { def unapply(tree: Tree): Option[(Symbol, Tree)] = tree match { case Bind(_, expr) if tree.symbol.exists => Some(tree.symbol -> expr) - case _ => None + case _ => None } } @@ -1126,13 +1125,13 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans final case class BoundTree(binder: Symbol, tree: Tree) { private lazy val extractor = ExtractorCall(tree, binder) - def pos = tree.pos - def tpe = binder.info.widenDealias - def pt = unbound match { - // case Star(tpt) => this glbWith seqType(tpt.tpe) dd todo: - case TypeBound(tpe) => tpe - case tree => tree.tpe - } + def pos = tree.pos + def tpe = binder.info.widenDealias + def pt = unbound match { + // case Star(tpt) => this glbWith seqType(tpt.tpe) dd todo: + case TypeBound(tpe) => tpe + case tree => tree.tpe + } def glbWith(other: Type) = ctx.typeComparer.glb(tpe :: other :: Nil)// .normalize @@ -1200,7 +1199,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans // Statically conforms to paramType if (tpe <:< paramType) treeMaker(binder, false, pos, tpe) :: Nil else typeTest :: extraction :: Nil - ) + ) step(makers: _*)(extractor.subBoundTrees: _*) } @@ -1219,7 +1218,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans // [7] symbol-less bind patterns - this happens in certain ill-formed programs, there'll be an error later // don't fail here though (or should we?) def nextStep(): TranslationStep = tree match { - case _: UnApply | _: Apply| Typed(_: UnApply | _: Apply, _) => extractorStep() + case _: UnApply | _: Apply | Typed(_: UnApply | _: Apply, _) => extractorStep() case SymbolAndTypeBound(sym, tpe) => typeTestStep(sym, tpe) case TypeBound(tpe) => typeTestStep(binder, tpe) case SymbolBound(sym, expr) => bindingStep(sym, expr) @@ -1230,7 +1229,6 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans } def translate(): List[TreeMaker] = nextStep() merge (_.translate()) - private def concreteType = tpe.bounds.hi private def unbound = unbind(tree) private def tpe_s = if (pt <:< concreteType) "" + pt else s"$pt (binder: $tpe)" @@ -1260,7 +1258,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans * * NOTE: the resulting tree is not type checked, nor are nested pattern matches transformed * thus, you must typecheck the result (and that will in turn translate nested matches) - * this could probably optimized... (but note that the matchStrategy must be solved for each nested patternmatch) + * this could probably be optimized... (but note that the matchStrategy must be solved for each nested patternmatch) */ def translateMatch(match_ : Match): Tree = { val Match(sel, cases) = match_ @@ -1271,7 +1269,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans val (nonSyntheticCases, defaultOverride) = cases match { case init :+ last if isSyntheticDefaultCase(last) => (init, Some(((scrut: Symbol) => last.body))) - case _ => (cases, None) + case _ => (cases, None) } @@ -1331,7 +1329,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans * a function that will take care of binding and substitution of the next ast (to the right). * */ - def translateCase(scrutSym: Symbol, pt: Type)(caseDef: CaseDef) = { + def translateCase(scrutSym: Symbol, pt: Type)(caseDef: CaseDef): List[TreeMaker] = { val CaseDef(pattern, guard, body) = caseDef translatePattern(BoundTree(scrutSym, pattern)) ++ translateGuard(guard) :+ translateBody(body, pt) } @@ -1400,7 +1398,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans object ExtractorCall { // TODO: check unargs == args - def apply(tree: Tree, binder: Symbol): ExtractorCall = { + def apply(tree: Tree, binder: Symbol): ExtractorCall = { tree match { case UnApply(unfun, implicits, args) => val castedBinder = ref(binder).ensureConforms(tree.tpe) @@ -1479,8 +1477,8 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans productSelectors(binder.info) else binder.caseAccessors val res = - if (accessors.isDefinedAt(i - 1)) ref(binder).select(accessors(i - 1).name) - else codegen.tupleSel(binder)(i) // this won't type check for case classes, as they do not inherit ProductN + if (accessors.isDefinedAt(i - 1)) ref(binder).select(accessors(i - 1).name) + else codegen.tupleSel(binder)(i) // this won't type check for case classes, as they do not inherit ProductN val rsym = res.symbol // just for debugging res } @@ -1492,7 +1490,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans if (!aligner.isStar) Nil else if (expectedLength == 0) seqTree(binder) :: Nil else genDrop(binder, expectedLength) - ) + ) // this error-condition has already been checked by checkStarPatOK: // if (isSeq) assert(firstIndexingBinder + nbIndexingIndices + (if (lastIsStar) 1 else 0) == totalArity, "(resultInMonad, ts, subPatTypes, subPats)= " +(resultInMonad, ts, subPatTypes, subPats)) @@ -1503,7 +1501,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans ( productElemsToN(binder, firstIndexingBinder) ++ genTake(binder, expectedLength) ++ lastTrees - ).toList + ).toList } // the trees that select the subpatterns on the extractor's result, referenced by `binder` @@ -1511,7 +1509,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans protected def subPatRefs(binder: Symbol): List[Tree] = { val refs = if (totalArity > 0 && isSeq) subPatRefsSeq(binder) else if (binder.info.member(nme._1).exists && !isSeq) productElemsToN(binder, totalArity) - else ref(binder):: Nil + else ref(binder) :: Nil refs } @@ -1601,7 +1599,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans // can't simplify this when subPatBinders.isEmpty, since UnitTpe is definitely // wrong when isSeq, and resultInMonad should always be correct since it comes // directly from the extractor's result type - val binder = freshSym(pos, resultInMonad) + val binder = freshSym(pos, resultInMonad) val spb = subPatBinders ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder)( spb, @@ -1819,6 +1817,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans def expectedTypes = typedPatterns map (_.tpe) def unexpandedFormals = extractor.varargsTypes } + trait ScalacPatternExpander extends PatternExpander[Tree, Type] { def NoPattern = EmptyTree def NoType = core.Types.NoType @@ -1836,7 +1835,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans ( typeOfMemberNamedHead(seq) orElse typeOfMemberNamedApply(seq) orElse seq.elemType - ) + ) } def newExtractor(whole: Type, fixed: List[Type], repeated: Repeated): Extractor = { ctx.log(s"newExtractor($whole, $fixed, $repeated") @@ -1863,7 +1862,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans method.paramTypess.head match { case init :+ last if last.isRepeatedParam => newExtractor(whole, init, repeatedFromVarargs(last)) - case tps => newExtractor(whole, tps, NoRepeated) + case tps => newExtractor(whole, tps, NoRepeated) } } @@ -1874,15 +1873,14 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans * Unfortunately the MethodType does not carry the information of whether * it was unapplySeq, so we have to funnel that information in separately. */ - def unapplyMethodTypes(tree:Tree, fun: Tree, args:List[Tree], resultType:Type, isSeq: Boolean): Extractor = { + def unapplyMethodTypes(tree: Tree, fun: Tree, args: List[Tree], resultType: Type, isSeq: Boolean): Extractor = { _id = _id + 1 - val whole = tree.tpe// see scaladoc for Trees.Unapply + val whole = tree.tpe // see scaladoc for Trees.Unapply // fun.tpe.widen.paramTypess.headOption.flatMap(_.headOption).getOrElse(NoType)//firstParamType(method) val resultOfGet = extractorMemberType(resultType, nme.get) - //println(s"${_id}unapplyArgs(${result.widen}") - val expanded:List[Type] = /*( + val expanded: List[Type] = /*( if (result =:= defn.BooleanType) Nil else if (defn.isProductSubType(result)) productSelectorTypes(result) else if (result.classSymbol is Flags.CaseClass) result.decls.filter(x => x.is(Flags.CaseAccessor) && x.is(Flags.Method)).map(_.info).toList @@ -1917,7 +1915,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans def offering = extractor.offeringString def symString = tree.symbol.showLocated def offerString = if (extractor.isErroneous) "" else s" offering $offering" - def arityExpected = ( if (extractor.hasSeq) "at least " else "" ) + prodArity + def arityExpected = (if (extractor.hasSeq) "at least " else "") + prodArity def err(msg: String) = ctx.error(msg, tree.pos) def warn(msg: String) = ctx.warning(msg, tree.pos) @@ -1944,10 +1942,10 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans } } - def apply(tree:Tree, sel: Tree, args: List[Tree], resultType: Type): Aligned = { + def apply(tree: Tree, sel: Tree, args: List[Tree], resultType: Type): Aligned = { val fn = sel match { case Applied(fn) => fn - case _ => sel + case _ => sel } val patterns = newPatterns(args) val isSeq = sel.symbol.name == nme.unapplySeq @@ -1977,8 +1975,8 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans } def apply(tree: Tree, resultType: Type): Aligned = tree match { - case Typed(tree, _) => apply(tree, resultType) - case Apply(fn, args) => apply(tree, fn, args, resultType) + case Typed(tree, _) => apply(tree, resultType) + case Apply(fn, args) => apply(tree, fn, args, resultType) case UnApply(fn, implicits, args) => apply(tree, fn, args, resultType) } } diff --git a/src/dotty/tools/dotc/transform/PostTyper.scala b/src/dotty/tools/dotc/transform/PostTyper.scala index fd22a0ad9e8f..e74709282444 100644 --- a/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/src/dotty/tools/dotc/transform/PostTyper.scala @@ -93,7 +93,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran * * should behave differently. * - * O1.x should have the same effect as { println("43"; 42 } + * O1.x should have the same effect as { println("43"); 42 } * * whereas * @@ -103,10 +103,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran * purity of the prefix unless the selection goes to an inline val. */ private def normalizeTree(tree: Tree)(implicit ctx: Context): Tree = tree match { - case tree: TypeTree => tree - case TypeApply(fn, args) => - Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType]) - tree + case _: TypeTree | _: TypeApply => tree case _ => if (tree.isType) { Checking.typeChecker.traverse(tree) diff --git a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 6de2bf44c3e8..3774127fad8f 100644 --- a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -100,7 +100,7 @@ trait TypeTestsCasts { * The transform happens before erasure of `argType`, thus cannot be merged * with `transformIsInstanceOf`, which depends on erased type of `argType`. */ - def transformOrTypeTest(qual: Tree, argType: Type): Tree = argType match { + def transformOrTypeTest(qual: Tree, argType: Type): Tree = argType.dealias match { case OrType(tp1, tp2) => evalOnce(qual) { fun => transformOrTypeTest(fun, tp1) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 099105de359a..20850e21d4bb 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -31,7 +31,7 @@ import language.implicitConversions object Applications { import tpd._ - def extractorMemberType(tp: Type, name: Name, errorPos: Position = NoPosition)(implicit ctx:Context) = { + def extractorMemberType(tp: Type, name: Name, errorPos: Position = NoPosition)(implicit ctx: Context) = { val ref = tp.member(name).suchThat(_.info.isParameterless) if (ref.isOverloaded) errorType(i"Overloaded reference to $ref is not allowed in extractor", errorPos) @@ -41,12 +41,12 @@ object Applications { ref.info.widenExpr.dealias } - def productSelectorTypes(tp: Type, errorPos: Position = NoPosition)(implicit ctx:Context): List[Type] = { + def productSelectorTypes(tp: Type, errorPos: Position = NoPosition)(implicit ctx: Context): List[Type] = { val sels = for (n <- Iterator.from(0)) yield extractorMemberType(tp, nme.selectorName(n), errorPos) sels.takeWhile(_.exists).toList } - def productSelectors(tp: Type)(implicit ctx:Context): List[Symbol] = { + def productSelectors(tp: Type)(implicit ctx: Context): List[Symbol] = { val sels = for (n <- Iterator.from(0)) yield tp.member(nme.selectorName(n)).symbol sels.takeWhile(_.exists).toList } @@ -58,7 +58,7 @@ object Applications { else tp :: Nil } else tp :: Nil - def unapplyArgs(unapplyResult: Type, unapplyFn:Tree, args:List[untpd.Tree], pos: Position = NoPosition)(implicit ctx: Context): List[Type] = { + def unapplyArgs(unapplyResult: Type, unapplyFn: Tree, args: List[untpd.Tree], pos: Position = NoPosition)(implicit ctx: Context): List[Type] = { def seqSelector = defn.RepeatedParamType.appliedTo(unapplyResult.elemType :: Nil) def getTp = extractorMemberType(unapplyResult, nme.get, pos) @@ -591,13 +591,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => fun1.tpe match { case ErrorType => tree.withType(ErrorType) - case TryDynamicCallType => - tree match { - case tree @ Apply(Select(qual, name), args) if !isDynamicMethod(name) => - typedDynamicApply(qual, name, args, pt)(tree) - case _ => - handleUnexpectedFunType(tree, fun1) - } + case TryDynamicCallType => typedDynamicApply(tree, pt) case _ => tryEither { implicit ctx => simpleApply(fun1, proto) @@ -658,7 +652,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** Overridden in ReTyper to handle primitive operations that can be generated after erasure */ protected def handleUnexpectedFunType(tree: untpd.Apply, fun: Tree)(implicit ctx: Context): Tree = - throw new Error(s"unexpected type.\n fun = $fun,\n methPart(fun) = ${methPart(fun)},\n methPart(fun).tpe = ${methPart(fun).tpe},\n tpe = ${fun.tpe}") + throw new Error(i"unexpected type.\n fun = $fun,\n methPart(fun) = ${methPart(fun)},\n methPart(fun).tpe = ${methPart(fun).tpe},\n tpe = ${fun.tpe}") def typedNamedArgs(args: List[untpd.Tree])(implicit ctx: Context) = for (arg @ NamedArg(id, argtpt) <- args) yield { @@ -679,7 +673,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } case _ => } - assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs) + def tryDynamicTypeApply(): Tree = typedFn match { + case typedFn: Select if !pt.isInstanceOf[FunProto] => typedDynamicSelect(typedFn, typedArgs, pt) + case _ => tree.withType(TryDynamicCallType) + } + if (typedFn.tpe eq TryDynamicCallType) tryDynamicTypeApply() + else assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs) } /** Rewrite `new Array[T](....)` if T is an unbounded generic to calls to newGenericArray. diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index d77520c778cb..101974b32b2f 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -37,7 +37,7 @@ object Checking { * well as for AppliedTypeTree nodes. Also checks that type arguments to * *-type parameters are fully applied. */ - def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context) = { + def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context): Unit = { (args, boundss).zipped.foreach { (arg, bound) => if (!bound.isHK && arg.tpe.isHK) ctx.error(ex"missing type parameter(s) for $arg", arg.pos) diff --git a/src/dotty/tools/dotc/typer/Dynamic.scala b/src/dotty/tools/dotc/typer/Dynamic.scala index aeb3cca8ccfe..b5ace87d38b4 100644 --- a/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/src/dotty/tools/dotc/typer/Dynamic.scala @@ -2,15 +2,14 @@ package dotty.tools package dotc package typer -import dotty.tools.dotc.ast.Trees.NamedArg -import dotty.tools.dotc.ast.tpd._ +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.untpd import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Names.Name import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Types._ -import dotty.tools.dotc.core.Mode import dotty.tools.dotc.core.Decorators._ object Dynamic { @@ -28,44 +27,78 @@ object Dynamic { * The first matching rule of is applied. */ trait Dynamic { self: Typer with Applications => + import Dynamic._ + import tpd._ /** Translate selection that does not typecheck according to the normal rules into a applyDynamic/applyDynamicNamed. - * foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...) - * foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...) + * foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...) + * foo.bar[T0, ...](baz0, baz1, ...) ~~> foo.applyDynamic[T0, ...](bar)(baz0, baz1, ...) + * foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...) + * foo.bar[T0, ...](x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed[T0, ...]("bar")(("x", bazX), ("y", bazY), ("", baz), ...) */ - def typedDynamicApply(qual: untpd.Tree, name: Name, args: List[untpd.Tree], pt: Type)(original: untpd.Apply)( - implicit ctx: Context): Tree = { - def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true; case _ => false } - val dynName = if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic - if (dynName == nme.applyDynamicNamed && untpd.isWildcardStarArgList(args)) { - ctx.error("applyDynamicNamed does not support passing a vararg parameter", original.pos) - original.withType(ErrorType) - } else { - def namedArgTuple(name: String, arg: untpd.Tree) = untpd.Tuple(List(Literal(Constant(name)), arg)) - def namedArgs = args.map { - case NamedArg(argName, arg) => namedArgTuple(argName.toString, arg) - case arg => namedArgTuple("", arg) + def typedDynamicApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { + def typedDynamicApply(qual: untpd.Tree, name: Name, targs: List[untpd.Tree]): Tree = { + def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true; case _ => false } + val args = tree.args + val dynName = if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic + if (dynName == nme.applyDynamicNamed && untpd.isWildcardStarArgList(args)) { + ctx.error("applyDynamicNamed does not support passing a vararg parameter", tree.pos) + tree.withType(ErrorType) + } else { + def namedArgTuple(name: String, arg: untpd.Tree) = untpd.Tuple(List(Literal(Constant(name)), arg)) + def namedArgs = args.map { + case NamedArg(argName, arg) => namedArgTuple(argName.toString, arg) + case arg => namedArgTuple("", arg) + } + val args1 = if (dynName == nme.applyDynamic) args else namedArgs + typedApply(untpd.Apply(coreDynamic(qual, dynName, name, targs), args1), pt) } - val args1 = if (dynName == nme.applyDynamic) args else namedArgs - typedApply(untpd.Apply(coreDynamic(qual, dynName, name), args1), pt) + } + + tree.fun match { + case Select(qual, name) if !isDynamicMethod(name) => + typedDynamicApply(qual, name, Nil) + case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) => + typedDynamicApply(qual, name, targs) + case TypeApply(fun, targs) => + typedDynamicApply(fun, nme.apply, targs) + case fun => + typedDynamicApply(fun, nme.apply, Nil) } } /** Translate selection that does not typecheck according to the normal rules into a selectDynamic. - * foo.bar ~~> foo.selectDynamic(bar) + * foo.bar ~~> foo.selectDynamic(bar) + * foo.bar[T0, ...] ~~> foo.selectDynamic[T0, ...](bar) * * Note: inner part of translation foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux) is achieved * through an existing transformation of in typedAssign [foo.bar(baz) = quux ~~> foo.bar.update(baz, quux)]. */ - def typedDynamicSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = - typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name), pt) + def typedDynamicSelect(tree: untpd.Select, targs: List[Tree], pt: Type)(implicit ctx: Context): Tree = + typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name, targs), pt) /** Translate selection that does not typecheck according to the normal rules into a updateDynamic. * foo.bar = baz ~~> foo.updateDynamic(bar)(baz) */ - def typedDynamicAssign(qual: untpd.Tree, name: Name, rhs: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = - typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name), rhs), pt) + def typedDynamicAssign(tree: untpd.Assign, pt: Type)(implicit ctx: Context): Tree = { + def typedDynamicAssign(qual: untpd.Tree, name: Name, targs: List[untpd.Tree]): Tree = + typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name, targs), tree.rhs), pt) + tree.lhs match { + case Select(qual, name) if !isDynamicMethod(name) => + typedDynamicAssign(qual, name, Nil) + case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) => + typedDynamicAssign(qual, name, targs) + case _ => + ctx.error("reassignment to val", tree.pos) + tree.withType(ErrorType) + } + } - private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name)(implicit ctx: Context): untpd.Apply = - untpd.Apply(untpd.Select(qual, dynName), Literal(Constant(name.toString))) + private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name, targs: List[untpd.Tree])(implicit ctx: Context): untpd.Apply = { + val select = untpd.Select(qual, dynName) + val selectWithTypes = + if (targs.isEmpty) select + else untpd.TypeApply(select, targs) + untpd.Apply(selectWithTypes, Literal(Constant(name.toString))) + } } diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index 0a3307140d2e..2a1c18f7d295 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -801,14 +801,15 @@ class SearchHistory(val searchDepth: Int, val seen: Map[ClassSymbol, Int]) { def updateMap(csyms: List[ClassSymbol], seen: Map[ClassSymbol, Int]): SearchHistory = csyms match { case csym :: csyms1 => seen get csym match { + // proto complexity is >= than the last time it was seen → diverge case Some(prevSize) if size >= prevSize => this case _ => updateMap(csyms1, seen.updated(csym, size)) } - case nil => - if (csyms.isEmpty) this - else new SearchHistory(searchDepth + 1, seen) + case _ => + new SearchHistory(searchDepth + 1, seen) } - updateMap(proto.classSymbols, seen) + if (proto.classSymbols.isEmpty) this + else updateMap(proto.classSymbols, seen) } } } diff --git a/src/dotty/tools/dotc/typer/ImportInfo.scala b/src/dotty/tools/dotc/typer/ImportInfo.scala index 2ca90311feba..2105d9ccc9de 100644 --- a/src/dotty/tools/dotc/typer/ImportInfo.scala +++ b/src/dotty/tools/dotc/typer/ImportInfo.scala @@ -64,6 +64,7 @@ class ImportInfo(symf: => Symbol, val selectors: List[untpd.Tree], val isRootImp myExcluded += name case Pair(Ident(from: TermName), Ident(to: TermName)) => myMapped = myMapped.updated(to, from) + myExcluded += from myOriginals += from case Ident(nme.WILDCARD) => myWildcardImport = true diff --git a/src/dotty/tools/dotc/typer/Inferencing.scala b/src/dotty/tools/dotc/typer/Inferencing.scala index 7c61f8c2348a..719e8d7fc01e 100644 --- a/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/src/dotty/tools/dotc/typer/Inferencing.scala @@ -78,7 +78,8 @@ object Inferencing { def apply(x: Boolean, tp: Type): Boolean = tp.dealias match { case _: WildcardType | _: ProtoType => false - case tvar: TypeVar if !tvar.isInstantiated => + case tvar: TypeVar + if !tvar.isInstantiated && ctx.typerState.constraint.contains(tvar) => force.appliesTo(tvar) && { val direction = instDirection(tvar.origin) if (direction != 0) { diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index d90f37860545..f031dd9df1c8 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -4,12 +4,12 @@ package typer import core._ import ast._ -import Trees._, Constants._, StdNames._, Scopes._, Denotations._ +import Trees._, Constants._, StdNames._, Scopes._, Denotations._, Comments._ import Contexts._, Symbols._, Types._, SymDenotations._, Names._, NameOps._, Flags._, Decorators._ import ast.desugar, ast.desugar._ import ProtoTypes._ import util.Positions._ -import util.{Attachment, SourcePosition, DotClass} +import util.{Property, SourcePosition, DotClass} import collection.mutable import annotation.tailrec import ErrorReporting._ @@ -160,9 +160,9 @@ class Namer { typer: Typer => import untpd._ - val TypedAhead = new Attachment.Key[tpd.Tree] - val ExpandedTree = new Attachment.Key[Tree] - val SymOfTree = new Attachment.Key[Symbol] + val TypedAhead = new Property.Key[tpd.Tree] + val ExpandedTree = new Property.Key[Tree] + val SymOfTree = new Property.Key[Symbol] /** A partial map from unexpanded member and pattern defs and to their expansions. * Populated during enterSyms, emptied during typer. @@ -454,7 +454,8 @@ class Namer { typer: Typer => } def setDocstring(sym: Symbol, tree: Tree)(implicit ctx: Context) = tree match { - case t: MemberDef => ctx.docbase.addDocstring(sym, t.rawComment) + case t: MemberDef if t.rawComment.isDefined => + ctx.getDocbase.foreach(_.addDocstring(sym, t.rawComment)) case _ => () } diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index f209c99bee53..41628f0dcd0f 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -182,6 +182,8 @@ object ProtoTypes { if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this else new FunProto(args, resultType, typer) + override def notApplied = WildcardType + /** Forget the types of any arguments that have been typed producing a constraint in a * typer state that is not yet committed into the one of the current context `ctx`. * This is necessary to avoid "orphan" PolyParams that are referred to from @@ -319,6 +321,8 @@ object ProtoTypes { if ((targs eq this.targs) && (resType eq this.resType)) this else PolyProto(targs, resType) + override def notApplied = WildcardType + def map(tm: TypeMap)(implicit ctx: Context): PolyProto = derivedPolyProto(targs mapConserve tm, tm(resultType)) diff --git a/src/dotty/tools/dotc/typer/RefChecks.scala b/src/dotty/tools/dotc/typer/RefChecks.scala index 2838866fd55c..1f150c5192c7 100644 --- a/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/src/dotty/tools/dotc/typer/RefChecks.scala @@ -525,7 +525,7 @@ object RefChecks { subclassMsg(concreteSym, abstractSym) else "" - undefined(s"\n(Note that $pa does not match $pc$addendum)") + undefined(s"\n(Note that ${pa.show} does not match ${pc.show}$addendum)") case xs => undefined(s"\n(The class implements a member with a different type: ${concrete.showDcl})") } diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 36404a68fc65..e1c9850d95d4 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -168,7 +168,9 @@ trait TypeAssigner { val d2 = pre.nonPrivateMember(name) if (reallyExists(d2) && firstTry) test(tpe.shadowed.withDenot(d2), false) - else { + else if (pre.derivesFrom(defn.DynamicClass)) { + TryDynamicCallType + } else { val alts = tpe.denot.alternatives.map(_.symbol).filter(_.exists) val what = alts match { case Nil => @@ -321,21 +323,30 @@ trait TypeAssigner { case pt: PolyType => val paramNames = pt.paramNames if (hasNamedArg(args)) { - val argMap = new mutable.HashMap[Name, Type] + // Type arguments which are specified by name (immutable after this first loop) + val namedArgMap = new mutable.HashMap[Name, Type] for (NamedArg(name, arg) <- args) - if (argMap.contains(name)) + if (namedArgMap.contains(name)) ctx.error("duplicate name", arg.pos) else if (!paramNames.contains(name)) ctx.error(s"undefined parameter name, required: ${paramNames.mkString(" or ")}", arg.pos) else - argMap(name) = arg.tpe + namedArgMap(name) = arg.tpe + + // Holds indexes of non-named typed arguments in paramNames val gapBuf = new mutable.ListBuffer[Int] - def nextPoly = { - val idx = gapBuf.length + def nextPoly(idx: Int) = { + val newIndex = gapBuf.length gapBuf += idx - PolyParam(pt, idx) + // Re-index unassigned type arguments that remain after transformation + PolyParam(pt, newIndex) } - val normArgs = paramNames.map(pname => argMap.getOrElse(pname, nextPoly)) + + // Type parameters after naming assignment, conserving paramNames order + val normArgs: List[Type] = paramNames.zipWithIndex.map { case (pname, idx) => + namedArgMap.getOrElse(pname, nextPoly(idx)) + } + val transform = new TypeMap { def apply(t: Type) = t match { case PolyParam(`pt`, idx) => normArgs(idx) @@ -347,19 +358,20 @@ trait TypeAssigner { else { val gaps = gapBuf.toList pt.derivedPolyType( - gaps.map(paramNames.filterNot(argMap.contains)), + gaps.map(paramNames), gaps.map(idx => transform(pt.paramBounds(idx)).bounds), resultType1) } } else { val argTypes = args.tpes - if (sameLength(argTypes, paramNames)|| ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes) + if (sameLength(argTypes, paramNames) || ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes) else wrongNumberOfArgs(fn.tpe, "type ", pt.paramNames.length, tree.pos) } case _ => errorType(i"${err.exprStr(fn)} does not take type parameters", tree.pos) } + tree.withType(ownType) } @@ -383,8 +395,8 @@ trait TypeAssigner { def assignType(tree: untpd.Closure, meth: Tree, target: Tree)(implicit ctx: Context) = tree.withType( - if (target.isEmpty) meth.tpe.widen.toFunctionType(tree.env.length) - else target.tpe) + if (target.isEmpty) meth.tpe.widen.toFunctionType(tree.env.length) + else target.tpe) def assignType(tree: untpd.CaseDef, body: Tree)(implicit ctx: Context) = tree.withType(body.tpe) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index f0086b0ab892..eeb1934dc8f2 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -11,6 +11,7 @@ import Scopes._ import Denotations._ import ProtoTypes._ import Contexts._ +import Comments._ import Symbols._ import Types._ import SymDenotations._ @@ -72,6 +73,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit */ private var importedFromRoot: Set[Symbol] = Set() + /** Temporary data item for single call to typed ident: + * This symbol would be found under Scala2 mode, but is not + * in dotty (because dotty conforms to spec section 2 + * wrt to package member resolution but scalac doe not). + */ + private var foundUnderScala2: Type = NoType + def newLikeThis: Typer = new Typer /** Attribute an identifier consisting of a simple name or wildcard @@ -133,14 +141,20 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit * imported by * or defined in */ - def bindingString(prec: Int, whereFound: Context, qualifier: String = "") = + 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}" /** Check that any previously found result from an inner context * does properly shadow the new one from an outer context. + * @param found The newly found result + * @param newPrec Its precedence + * @param scala2pkg Special mode where we check members of the same package, but defined + * in different compilation units under Scala2. If set, and the + * previous and new contexts do not have the same scope, we select + * the previous (inner) definition. This models what scalac does. */ - def checkNewOrShadowed(found: Type, newPrec: Int): Type = + def checkNewOrShadowed(found: Type, newPrec: Int, scala2pkg: Boolean = false)(implicit ctx: Context): Type = if (!previous.exists || ctx.typeComparer.isSameRef(previous, found)) found else if ((prevCtx.scope eq ctx.scope) && (newPrec == definition || @@ -150,7 +164,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit found } else { - if (!previous.isError && !found.isError) { + if (!scala2pkg && !previous.isError && !found.isError) { error( ex"""reference to $name is ambiguous; |it is both ${bindingString(newPrec, ctx, "")} @@ -163,7 +177,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit /** The type representing a named import with enclosing name when imported * from given `site` and `selectors`. */ - def namedImportRef(site: Type, selectors: List[untpd.Tree]): Type = { + def namedImportRef(site: Type, selectors: List[untpd.Tree])(implicit ctx: Context): Type = { def checkUnambiguous(found: Type) = { val other = namedImportRef(site, selectors.tail) if (other.exists && found.exists && (found != other)) @@ -190,7 +204,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit /** The type representing a wildcard import with enclosing name when imported * from given import info */ - def wildImportRef(imp: ImportInfo): Type = { + def wildImportRef(imp: ImportInfo)(implicit ctx: Context): Type = { if (imp.isWildcardImport) { val pre = imp.site if (!isDisabled(imp, pre) && !(imp.excluded contains name.toTermName) && name != nme.CONSTRUCTOR) { @@ -204,54 +218,71 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit /** Is (some alternative of) the given predenotation `denot` * defined in current compilation unit? */ - def isDefinedInCurrentUnit(denot: Denotation): Boolean = denot match { + def isDefinedInCurrentUnit(denot: Denotation)(implicit ctx: Context): Boolean = denot match { case MultiDenotation(d1, d2) => isDefinedInCurrentUnit(d1) || isDefinedInCurrentUnit(d2) case denot: SingleDenotation => denot.symbol.sourceFile == ctx.source.file } /** Is `denot` the denotation of a self symbol? */ - def isSelfDenot(denot: Denotation) = denot match { + def isSelfDenot(denot: Denotation)(implicit ctx: Context) = denot match { case denot: SymDenotation => denot is SelfName case _ => false } - // begin findRef - if (ctx.scope == null) previous - else { - val outer = ctx.outer - if ((ctx.scope ne outer.scope) || (ctx.owner ne outer.owner)) { - val defDenot = ctx.denotNamed(name) - if (qualifies(defDenot)) { - val curOwner = ctx.owner - val found = - if (isSelfDenot(defDenot)) curOwner.enclosingClass.thisType - else curOwner.thisType.select(name, defDenot) - if (!(curOwner is Package) || isDefinedInCurrentUnit(defDenot)) - return checkNewOrShadowed(found, definition) // no need to go further out, we found highest prec entry - else if (defDenot.symbol is Package) - return checkNewOrShadowed(previous orElse found, packageClause) - else if (prevPrec < packageClause) - return findRef(found, packageClause, ctx)(outer) + /** Would import of kind `prec` be not shadowed by a nested higher-precedence definition? */ + def isPossibleImport(prec: Int)(implicit ctx: Context) = + prevPrec < prec || prevPrec == prec && (prevCtx.scope eq ctx.scope) + + @tailrec def loop(implicit ctx: Context): Type = { + if (ctx.scope == null) previous + else { + val outer = ctx.outer + var result: Type = NoType + + // find definition + if ((ctx.scope ne outer.scope) || (ctx.owner ne outer.owner)) { + val defDenot = ctx.denotNamed(name) + if (qualifies(defDenot)) { + val curOwner = ctx.owner + val found = + if (isSelfDenot(defDenot)) curOwner.enclosingClass.thisType + else curOwner.thisType.select(name, defDenot) + if (!(curOwner is Package) || isDefinedInCurrentUnit(defDenot)) + result = checkNewOrShadowed(found, definition) // no need to go further out, we found highest prec entry + else { + if (ctx.scala2Mode && !foundUnderScala2.exists) + foundUnderScala2 = checkNewOrShadowed(found, definition, scala2pkg = true) + if (defDenot.symbol is Package) + result = checkNewOrShadowed(previous orElse found, packageClause) + else if (prevPrec < packageClause) + result = findRef(found, packageClause, ctx)(outer) + } + } } - } - val curImport = ctx.importInfo - if (ctx.owner.is(Package) && curImport != null && curImport.isRootImport && previous.exists) - return previous // no more conflicts possible in this case - // would import of kind `prec` be not shadowed by a nested higher-precedence definition? - def isPossibleImport(prec: Int) = - prevPrec < prec || prevPrec == prec && (prevCtx.scope eq ctx.scope) - if (isPossibleImport(namedImport) && (curImport ne outer.importInfo) && !curImport.sym.isCompleting) { - val namedImp = namedImportRef(curImport.site, curImport.selectors) - if (namedImp.exists) - return findRef(checkNewOrShadowed(namedImp, namedImport), namedImport, ctx)(outer) - if (isPossibleImport(wildImport)) { - val wildImp = wildImportRef(curImport) - if (wildImp.exists) - return findRef(checkNewOrShadowed(wildImp, wildImport), wildImport, ctx)(outer) + + if (result.exists) result + else { // find import + val curImport = ctx.importInfo + if (ctx.owner.is(Package) && curImport != null && curImport.isRootImport && previous.exists) + previous // no more conflicts possible in this case + else if (isPossibleImport(namedImport) && (curImport ne outer.importInfo) && !curImport.sym.isCompleting) { + val namedImp = namedImportRef(curImport.site, curImport.selectors) + if (namedImp.exists) + findRef(checkNewOrShadowed(namedImp, namedImport), namedImport, ctx)(outer) + else if (isPossibleImport(wildImport)) { + val wildImp = wildImportRef(curImport) + if (wildImp.exists) + findRef(checkNewOrShadowed(wildImp, wildImport), wildImport, ctx)(outer) + else loop(outer) + } + else loop(outer) + } + else loop(outer) } } - findRef(previous, prevPrec, prevCtx)(outer) } + + loop } // begin typedIdent @@ -264,12 +295,28 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit return typed(desugar.patternVar(tree), pt) } - val saved = importedFromRoot - importedFromRoot = Set.empty - val rawType = - try findRef(NoType, BindingPrec.nothingBound, NoContext) - finally importedFromRoot = saved + val rawType = { + val saved1 = importedFromRoot + val saved2 = foundUnderScala2 + importedFromRoot = Set.empty + foundUnderScala2 = NoType + try { + var found = findRef(NoType, BindingPrec.nothingBound, NoContext) + if (foundUnderScala2.exists && !(foundUnderScala2 =:= found)) { + ctx.migrationWarning( + ex"""Name resolution will change. + | currently selected : $foundUnderScala2 + | in the future, without -language:Scala2: $found""", tree.pos) + found = foundUnderScala2 + } + found + } + finally { + importedFromRoot = saved1 + foundUnderScala2 = saved2 + } + } val ownType = if (rawType.exists) @@ -317,12 +364,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos) val select = typedSelect(tree, pt, qual1) - pt match { - case _: FunProto | AssignProto => select - case _ => - if (select.tpe eq TryDynamicCallType) typedDynamicSelect(tree, pt) - else select - } + if (select.tpe ne TryDynamicCallType) select + else if (pt.isInstanceOf[PolyProto] || pt.isInstanceOf[FunProto] || pt == AssignProto) select + else typedDynamicSelect(tree, Nil, pt) } def asJavaSelectFromTypeTree(implicit ctx: Context): Tree = { @@ -518,11 +562,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit reassignmentToVal } case TryDynamicCallType => - tree match { - case Assign(Select(qual, name), rhs) if !isDynamicMethod(name) => - typedDynamicAssign(qual, name, rhs, pt) - case _ => reassignmentToVal - } + typedDynamicAssign(tree, pt) case tpe => reassignmentToVal } @@ -533,7 +573,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context) = track("typedBlock") { val exprCtx = index(tree.stats) val stats1 = typedStats(tree.stats, ctx.owner) - val expr1 = typedExpr(tree.expr, pt)(exprCtx) + val ept = + if (tree.isInstanceOf[untpd.InfixOpBlock]) + // Right-binding infix operations are expanded to InfixBlocks, which may be followed by arguments. + // Example: `(a /: bs)(op)` expands to `{ val x = a; bs./:(x) } (op)` where `{...}` is an InfixBlock. + pt + else pt.notApplied + val expr1 = typedExpr(tree.expr, ept)(exprCtx) ensureNoLocalRefs( assignType(cpy.Block(tree)(stats1, expr1), stats1, expr1), pt, localSyms(stats1)) } @@ -580,8 +626,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = track("typedIf") { val cond1 = typed(tree.cond, defn.BooleanType) - val thenp1 = typed(tree.thenp, pt) - val elsep1 = typed(tree.elsep orElse (untpd.unitLiteral withPos tree.pos), pt) + val thenp1 = typed(tree.thenp, pt.notApplied) + val elsep1 = typed(tree.elsep orElse (untpd.unitLiteral withPos tree.pos), pt.notApplied) val thenp2 :: elsep2 :: Nil = harmonize(thenp1 :: elsep1 :: Nil) assignType(cpy.If(tree)(cond1, thenp2, elsep2), thenp2, elsep2) } @@ -754,7 +800,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val selType = widenForMatchSelector( fullyDefinedType(sel1.tpe, "pattern selector", tree.pos)) - val cases1 = typedCases(tree.cases, selType, pt) + val cases1 = typedCases(tree.cases, selType, pt.notApplied) val cases2 = harmonize(cases1).asInstanceOf[List[CaseDef]] assignType(cpy.Match(tree)(sel1, cases2), cases2) } @@ -881,8 +927,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } def typedTry(tree: untpd.Try, pt: Type)(implicit ctx: Context): Try = track("typedTry") { - val expr1 = typed(tree.expr, pt) - val cases1 = typedCases(tree.cases, defn.ThrowableType, pt) + val expr1 = typed(tree.expr, pt.notApplied) + val cases1 = typedCases(tree.cases, defn.ThrowableType, pt.notApplied) val finalizer1 = typed(tree.finalizer, defn.UnitType) val expr2 :: cases2x = harmonize(expr1 :: cases1) val cases2 = cases2x.asInstanceOf[List[CaseDef]] @@ -895,7 +941,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(implicit ctx: Context): SeqLiteral = track("typedSeqLiteral") { - val proto1 = pt.elemType orElse WildcardType + val proto1 = pt.elemType match { + case NoType => WildcardType + case bounds: TypeBounds => WildcardType(bounds) + case elemtp => elemtp + } val elems1 = tree.elems mapconserve (typed(_, proto1)) val proto2 = // the computed type of the `elemtpt` field if (!tree.elemtpt.isEmpty) WildcardType @@ -1127,6 +1177,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit // function types so no dependencies on parameters are allowed. tpt1 = tpt1.withType(avoid(tpt1.tpe, vparamss1.flatMap(_.map(_.symbol)))) } + assignType(cpy.DefDef(ddef)(name, tparams1, vparamss1, tpt1, rhs1), sym) //todo: make sure dependent method types do not depend on implicits or by-name params } @@ -1190,6 +1241,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val self1 = typed(self)(ctx.outer).asInstanceOf[ValDef] // outer context where class members are not visible val dummy = localDummy(cls, impl) val body1 = typedStats(impl.body, dummy)(inClassContext(self1.symbol)) + + if (ctx.property(DocContext).isDefined) + typedUsecases(body1.map(_.symbol), self1.symbol)(localContext(cdef, cls).setNewScope) + checkNoDoubleDefs(cls) val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) .withType(dummy.nonMemberTermRef) @@ -1456,6 +1511,45 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit traverse(stats) } + private def typedUsecases(syms: List[Symbol], owner: Symbol)(implicit ctx: Context): Unit = + ctx.getDocbase.foreach { docbase => + val relevantSyms = syms.filter(docbase.docstring(_).isDefined) + relevantSyms.foreach { sym => + expandParentDocs(sym) + val usecases = docbase.docstring(sym).map(_.usecases).getOrElse(Nil) + + usecases.foreach { usecase => + enterSymbol(createSymbol(usecase.untpdCode)) + + typedStats(usecase.untpdCode :: Nil, owner) match { + case List(df: tpd.DefDef) => usecase.tpdCode = df + case _ => ctx.error("`@usecase` was not a valid definition", usecase.codePos) + } + } + } + } + + private def expandParentDocs(sym: Symbol)(implicit ctx: Context): Unit = + ctx.getDocbase.foreach { docbase => + docbase.docstring(sym).foreach { cmt => + def expandDoc(owner: Symbol): Unit = if (!cmt.isExpanded) { + val tplExp = docbase.templateExpander + tplExp.defineVariables(sym) + + val newCmt = cmt + .expand(tplExp.expandedDocComment(sym, owner, _)) + .withUsecases + + docbase.addDocstring(sym, Some(newCmt)) + } + + if (sym ne NoSymbol) { + expandParentDocs(sym.owner) + expandDoc(sym.owner) + } + } + } + def typedExpr(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = typed(tree, pt)(ctx retractMode Mode.PatternOrType) def typedType(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = // todo: retract mode between Type and Pattern? @@ -1492,8 +1586,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } /** If this tree is a select node `qual.name`, try to insert an implicit conversion - * `c` around `qual` so that `c(qual).name` conforms to `pt`. If that fails - * return `tree` itself. + * `c` around `qual` so that `c(qual).name` conforms to `pt`. */ def tryInsertImplicitOnQualifier(tree: Tree, pt: Type)(implicit ctx: Context): Option[Tree] = ctx.traceIndented(i"try insert impl on qualifier $tree $pt") { tree match { diff --git a/src/dotty/tools/dotc/util/Attachment.scala b/src/dotty/tools/dotc/util/Attachment.scala index 8088b4cd09c5..20facfd97544 100644 --- a/src/dotty/tools/dotc/util/Attachment.scala +++ b/src/dotty/tools/dotc/util/Attachment.scala @@ -4,9 +4,7 @@ package dotty.tools.dotc.util * adding, removing and lookup of attachments. Attachments are typed key/value pairs. */ object Attachment { - - /** The class of keys for attachments yielding values of type V */ - class Key[+V] + import Property.Key /** An implementation trait for attachments. * Clients should inherit from Container instead. diff --git a/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentUtils.scala b/src/dotty/tools/dotc/util/CommentParsing.scala similarity index 94% rename from dottydoc/src/dotty/tools/dottydoc/model/comment/CommentUtils.scala rename to src/dotty/tools/dotc/util/CommentParsing.scala index e5307bd3c559..077776b5d286 100644 --- a/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentUtils.scala +++ b/src/dotty/tools/dotc/util/CommentParsing.scala @@ -3,15 +3,10 @@ * @author Martin Odersky * @author Felix Mulder */ +package dotty.tools.dotc.util -package dotty.tools -package dottydoc -package model -package comment - -import scala.reflect.internal.Chars._ - -object CommentUtils { +object CommentParsing { + import scala.reflect.internal.Chars._ /** Returns index of string `str` following `start` skipping longest * sequence of whitespace characters characters (but no newlines) @@ -221,4 +216,17 @@ object CommentUtils { result } + + def removeSections(raw: String, xs: String*): String = { + val sections = tagIndex(raw) + + val toBeRemoved = for { + section <- xs + lines = sections filter { startsWithTag(raw, _, section) } + } yield lines + + val end = startTag(raw, toBeRemoved.flatten.sortBy(_._1).toList) + + if (end == raw.length - 2) raw else raw.substring(0, end) + "*/" + } } diff --git a/src/dotty/tools/dotc/util/Property.scala b/src/dotty/tools/dotc/util/Property.scala new file mode 100644 index 000000000000..608fc88e63f0 --- /dev/null +++ b/src/dotty/tools/dotc/util/Property.scala @@ -0,0 +1,10 @@ +package dotty.tools.dotc.util + +/** Defines a key type with which to tag properties, such as attachments + * or context properties + */ +object Property { + + /** The class of keys for properties of type V */ + class Key[+V] +} \ No newline at end of file diff --git a/test/test/DottyDocParsingTests.scala b/test/test/DottyDocParsingTests.scala index ed89c611482a..8522cdae32f9 100644 --- a/test/test/DottyDocParsingTests.scala +++ b/test/test/DottyDocParsingTests.scala @@ -14,7 +14,7 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(c: TypeDef)) => - assert(c.rawComment.map(_.chrs) == None, "Should not have a comment, mainly used for exhaustive tests") + assert(c.rawComment.map(_.raw) == None, "Should not have a comment, mainly used for exhaustive tests") } } @@ -29,7 +29,7 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(t @ TypeDef(name, _))) if name.toString == "Class" => - checkDocString(t.rawComment.map(_.chrs), "/** Hello world! */") + checkDocString(t.rawComment.map(_.raw), "/** Hello world! */") } } @@ -44,7 +44,7 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(t @ TypeDef(name, _))) if name.toString == "Class" => - checkDocString(t.rawComment.map(_.chrs), "/** Hello /* multiple open */ world! */") + checkDocString(t.rawComment.map(_.raw), "/** Hello /* multiple open */ world! */") } } @Test def multipleClassesInPackage = { @@ -62,8 +62,8 @@ class DottyDocParsingTests extends DottyDocTest { checkCompile("frontend", source) { (_, ctx) => ctx.compilationUnit.untpdTree match { case PackageDef(_, Seq(c1 @ TypeDef(_,_), c2 @ TypeDef(_,_))) => { - checkDocString(c1.rawComment.map(_.chrs), "/** Class1 docstring */") - checkDocString(c2.rawComment.map(_.chrs), "/** Class2 docstring */") + checkDocString(c1.rawComment.map(_.raw), "/** Class1 docstring */") + checkDocString(c2.rawComment.map(_.raw), "/** Class2 docstring */") } } } @@ -77,7 +77,7 @@ class DottyDocParsingTests extends DottyDocTest { """.stripMargin checkFrontend(source) { - case PackageDef(_, Seq(t @ TypeDef(_,_))) => checkDocString(t.rawComment.map(_.chrs), "/** Class without package */") + case PackageDef(_, Seq(t @ TypeDef(_,_))) => checkDocString(t.rawComment.map(_.raw), "/** Class without package */") } } @@ -85,7 +85,7 @@ class DottyDocParsingTests extends DottyDocTest { val source = "/** Trait docstring */\ntrait Trait" checkFrontend(source) { - case PackageDef(_, Seq(t @ TypeDef(_,_))) => checkDocString(t.rawComment.map(_.chrs), "/** Trait docstring */") + case PackageDef(_, Seq(t @ TypeDef(_,_))) => checkDocString(t.rawComment.map(_.raw), "/** Trait docstring */") } } @@ -101,8 +101,8 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(t1 @ TypeDef(_,_), t2 @ TypeDef(_,_))) => { - checkDocString(t1.rawComment.map(_.chrs), "/** Trait1 docstring */") - checkDocString(t2.rawComment.map(_.chrs), "/** Trait2 docstring */") + checkDocString(t1.rawComment.map(_.raw), "/** Trait1 docstring */") + checkDocString(t2.rawComment.map(_.raw), "/** Trait2 docstring */") } } } @@ -127,10 +127,10 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(t1 @ TypeDef(_,_), c2 @ TypeDef(_,_), cc3 @ TypeDef(_,_), _, ac4 @ TypeDef(_,_))) => { - checkDocString(t1.rawComment.map(_.chrs), "/** Trait1 docstring */") - checkDocString(c2.rawComment.map(_.chrs), "/** Class2 docstring */") - checkDocString(cc3.rawComment.map(_.chrs), "/** CaseClass3 docstring */") - checkDocString(ac4.rawComment.map(_.chrs), "/** AbstractClass4 docstring */") + checkDocString(t1.rawComment.map(_.raw), "/** Trait1 docstring */") + checkDocString(c2.rawComment.map(_.raw), "/** Class2 docstring */") + checkDocString(cc3.rawComment.map(_.raw), "/** CaseClass3 docstring */") + checkDocString(ac4.rawComment.map(_.raw), "/** AbstractClass4 docstring */") } } } @@ -147,9 +147,9 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(outer @ TypeDef(_, tpl @ Template(_,_,_,_)))) => { - checkDocString(outer.rawComment.map(_.chrs), "/** Outer docstring */") + checkDocString(outer.rawComment.map(_.raw), "/** Outer docstring */") tpl.body match { - case (inner @ TypeDef(_,_)) :: _ => checkDocString(inner.rawComment.map(_.chrs), "/** Inner docstring */") + case (inner @ TypeDef(_,_)) :: _ => checkDocString(inner.rawComment.map(_.raw), "/** Inner docstring */") case _ => assert(false, "Couldn't find inner class") } } @@ -171,10 +171,10 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(o1 @ TypeDef(_, tpl @ Template(_,_,_,_)), o2 @ TypeDef(_,_))) => { - checkDocString(o1.rawComment.map(_.chrs), "/** Outer1 docstring */") - checkDocString(o2.rawComment.map(_.chrs), "/** Outer2 docstring */") + checkDocString(o1.rawComment.map(_.raw), "/** Outer1 docstring */") + checkDocString(o2.rawComment.map(_.raw), "/** Outer2 docstring */") tpl.body match { - case (inner @ TypeDef(_,_)) :: _ => checkDocString(inner.rawComment.map(_.chrs), "/** Inner docstring */") + case (inner @ TypeDef(_,_)) :: _ => checkDocString(inner.rawComment.map(_.raw), "/** Inner docstring */") case _ => assert(false, "Couldn't find inner class") } } @@ -196,9 +196,9 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case p @ PackageDef(_, Seq(o1: MemberDef[Untyped], o2: MemberDef[Untyped])) => { assertEquals(o1.name.toString, "Object1") - checkDocString(o1.rawComment.map(_.chrs), "/** Object1 docstring */") + checkDocString(o1.rawComment.map(_.raw), "/** Object1 docstring */") assertEquals(o2.name.toString, "Object2") - checkDocString(o2.rawComment.map(_.chrs), "/** Object2 docstring */") + checkDocString(o2.rawComment.map(_.raw), "/** Object2 docstring */") } } } @@ -223,12 +223,12 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case p @ PackageDef(_, Seq(o1: ModuleDef, o2: ModuleDef)) => { assert(o1.name.toString == "Object1") - checkDocString(o1.rawComment.map(_.chrs), "/** Object1 docstring */") + checkDocString(o1.rawComment.map(_.raw), "/** Object1 docstring */") assert(o2.name.toString == "Object2") - checkDocString(o2.rawComment.map(_.chrs), "/** Object2 docstring */") + checkDocString(o2.rawComment.map(_.raw), "/** Object2 docstring */") o2.impl.body match { - case _ :: (inner @ TypeDef(_,_)) :: _ => checkDocString(inner.rawComment.map(_.chrs), "/** Inner docstring */") + case _ :: (inner @ TypeDef(_,_)) :: _ => checkDocString(inner.rawComment.map(_.raw), "/** Inner docstring */") case _ => assert(false, "Couldn't find inner class") } } @@ -257,14 +257,14 @@ class DottyDocParsingTests extends DottyDocTest { import dotty.tools.dotc.ast.untpd._ checkFrontend(source) { case PackageDef(_, Seq(p: ModuleDef)) => { - checkDocString(p.rawComment.map(_.chrs), "/** Package object docstring */") + checkDocString(p.rawComment.map(_.raw), "/** Package object docstring */") p.impl.body match { case (b: TypeDef) :: (t: TypeDef) :: (o: ModuleDef) :: Nil => { - checkDocString(b.rawComment.map(_.chrs), "/** Boo docstring */") - checkDocString(t.rawComment.map(_.chrs), "/** Trait docstring */") - checkDocString(o.rawComment.map(_.chrs), "/** InnerObject docstring */") - checkDocString(o.impl.body.head.asInstanceOf[TypeDef].rawComment.map(_.chrs), "/** InnerClass docstring */") + checkDocString(b.rawComment.map(_.raw), "/** Boo docstring */") + checkDocString(t.rawComment.map(_.raw), "/** Trait docstring */") + checkDocString(o.rawComment.map(_.raw), "/** InnerObject docstring */") + checkDocString(o.impl.body.head.asInstanceOf[TypeDef].rawComment.map(_.raw), "/** InnerClass docstring */") } case _ => assert(false, "Incorrect structure inside package object") } @@ -284,7 +284,7 @@ class DottyDocParsingTests extends DottyDocTest { import dotty.tools.dotc.ast.untpd._ checkFrontend(source) { case PackageDef(_, Seq(c: TypeDef)) => - checkDocString(c.rawComment.map(_.chrs), "/** Real comment */") + checkDocString(c.rawComment.map(_.raw), "/** Real comment */") } } @@ -303,7 +303,7 @@ class DottyDocParsingTests extends DottyDocTest { import dotty.tools.dotc.ast.untpd._ checkFrontend(source) { case PackageDef(_, Seq(c: TypeDef)) => - checkDocString(c.rawComment.map(_.chrs), "/** Real comment */") + checkDocString(c.rawComment.map(_.raw), "/** Real comment */") } } @@ -329,9 +329,9 @@ class DottyDocParsingTests extends DottyDocTest { case PackageDef(_, Seq(o: ModuleDef)) => { o.impl.body match { case (v1: MemberDef) :: (v2: MemberDef) :: (v3: MemberDef) :: Nil => { - checkDocString(v1.rawComment.map(_.chrs), "/** val1 */") - checkDocString(v2.rawComment.map(_.chrs), "/** val2 */") - checkDocString(v3.rawComment.map(_.chrs), "/** val3 */") + checkDocString(v1.rawComment.map(_.raw), "/** val1 */") + checkDocString(v2.rawComment.map(_.raw), "/** val2 */") + checkDocString(v3.rawComment.map(_.raw), "/** val3 */") } case _ => assert(false, "Incorrect structure inside object") } @@ -361,9 +361,9 @@ class DottyDocParsingTests extends DottyDocTest { case PackageDef(_, Seq(o: ModuleDef)) => { o.impl.body match { case (v1: MemberDef) :: (v2: MemberDef) :: (v3: MemberDef) :: Nil => { - checkDocString(v1.rawComment.map(_.chrs), "/** var1 */") - checkDocString(v2.rawComment.map(_.chrs), "/** var2 */") - checkDocString(v3.rawComment.map(_.chrs), "/** var3 */") + checkDocString(v1.rawComment.map(_.raw), "/** var1 */") + checkDocString(v2.rawComment.map(_.raw), "/** var2 */") + checkDocString(v3.rawComment.map(_.raw), "/** var3 */") } case _ => assert(false, "Incorrect structure inside object") } @@ -393,9 +393,9 @@ class DottyDocParsingTests extends DottyDocTest { case PackageDef(_, Seq(o: ModuleDef)) => { o.impl.body match { case (v1: MemberDef) :: (v2: MemberDef) :: (v3: MemberDef) :: Nil => { - checkDocString(v1.rawComment.map(_.chrs), "/** def1 */") - checkDocString(v2.rawComment.map(_.chrs), "/** def2 */") - checkDocString(v3.rawComment.map(_.chrs), "/** def3 */") + checkDocString(v1.rawComment.map(_.raw), "/** def1 */") + checkDocString(v2.rawComment.map(_.raw), "/** def2 */") + checkDocString(v3.rawComment.map(_.raw), "/** def3 */") } case _ => assert(false, "Incorrect structure inside object") } @@ -425,9 +425,9 @@ class DottyDocParsingTests extends DottyDocTest { case PackageDef(_, Seq(o: ModuleDef)) => { o.impl.body match { case (v1: MemberDef) :: (v2: MemberDef) :: (v3: MemberDef) :: Nil => { - checkDocString(v1.rawComment.map(_.chrs), "/** type1 */") - checkDocString(v2.rawComment.map(_.chrs), "/** type2 */") - checkDocString(v3.rawComment.map(_.chrs), "/** type3 */") + checkDocString(v1.rawComment.map(_.raw), "/** type1 */") + checkDocString(v2.rawComment.map(_.raw), "/** type2 */") + checkDocString(v3.rawComment.map(_.raw), "/** type3 */") } case _ => assert(false, "Incorrect structure inside object") } @@ -451,7 +451,7 @@ class DottyDocParsingTests extends DottyDocTest { case PackageDef(_, Seq(o: ModuleDef)) => o.impl.body match { case (foo: MemberDef) :: Nil => - expectNoDocString(foo.rawComment.map(_.chrs)) + expectNoDocString(foo.rawComment.map(_.raw)) case _ => assert(false, "Incorrect structure inside object") } } @@ -468,7 +468,7 @@ class DottyDocParsingTests extends DottyDocTest { import dotty.tools.dotc.ast.untpd._ checkFrontend(source) { case p @ PackageDef(_, Seq(_, c: TypeDef)) => - checkDocString(c.rawComment.map(_.chrs), "/** Class1 */") + checkDocString(c.rawComment.map(_.raw), "/** Class1 */") } } @@ -483,7 +483,7 @@ class DottyDocParsingTests extends DottyDocTest { import dotty.tools.dotc.ast.untpd._ checkFrontend(source) { case p @ PackageDef(_, Seq(c: TypeDef)) => - checkDocString(c.rawComment.map(_.chrs), "/** Class1 */") + checkDocString(c.rawComment.map(_.raw), "/** Class1 */") } } } /* End class */ diff --git a/tests/pending/hkt/compiler.error b/tests/disabled/not-representable/hkt/compiler.error similarity index 100% rename from tests/pending/hkt/compiler.error rename to tests/disabled/not-representable/hkt/compiler.error diff --git a/tests/pending/hkt/hkt.scala b/tests/disabled/not-representable/hkt/hkt.scala similarity index 68% rename from tests/pending/hkt/hkt.scala rename to tests/disabled/not-representable/hkt/hkt.scala index 34858cd95009..1a9932d7348d 100644 --- a/tests/pending/hkt/hkt.scala +++ b/tests/disabled/not-representable/hkt/hkt.scala @@ -1,3 +1,6 @@ +// This one is unavoidable. Dotty does not allow several overloaded +// parameterless methods, so it picks the one in the subclass. + import scala.language.higherKinds // Minimal reproduction for: // scala.collection.mutable.ArrayStack.empty[Int] diff --git a/tests/pending/run/t2337.scala b/tests/disabled/not-representable/t2337.scala similarity index 91% rename from tests/pending/run/t2337.scala rename to tests/disabled/not-representable/t2337.scala index edb574cba4de..9e3b8c55503e 100644 --- a/tests/pending/run/t2337.scala +++ b/tests/disabled/not-representable/t2337.scala @@ -1,4 +1,4 @@ - +// Failure of autotupling in the presence of overloaded functions. object Test { def compare(first: Any, second: Any): Any = { diff --git a/tests/neg/applydynamic_sip.check b/tests/neg/applydynamic_sip.check new file mode 100644 index 000000000000..1bd8304bfce2 --- /dev/null +++ b/tests/neg/applydynamic_sip.check @@ -0,0 +1,52 @@ +tests/neg/applydynamic_sip.scala:8: error: value applyDynamic is not a member of Dynamic(Test.qual) +possible cause: maybe a wrong Dynamic method signature? + qual.sel(a, a2: _*) // error + ^ +tests/neg/applydynamic_sip.scala:9: error: applyDynamicNamed does not support passing a vararg parameter + qual.sel(arg = a, a2: _*) // error + ^ +tests/neg/applydynamic_sip.scala:10: error: applyDynamicNamed does not support passing a vararg parameter + qual.sel(arg, arg2 = "a2", a2: _*) // error + ^ +tests/neg/applydynamic_sip.scala:20: error: type mismatch: + found : String("sel") + required: Int + bad1.sel // error + ^ +tests/neg/applydynamic_sip.scala:21: error: type mismatch: + found : String("sel") + required: Int + bad1.sel(1) // error // error + ^ +tests/neg/applydynamic_sip.scala:21: error: method applyDynamic in class Bad1 does not take more parameters + bad1.sel(1) // error // error + ^ +tests/neg/applydynamic_sip.scala:22: error: type mismatch: + found : String("sel") + required: Int + bad1.sel(a = 1) // error // error + ^ +tests/neg/applydynamic_sip.scala:22: error: method applyDynamicNamed in class Bad1 does not take more parameters + bad1.sel(a = 1) // error // error + ^ +tests/neg/applydynamic_sip.scala:23: error: type mismatch: + found : String("sel") + required: Int + bad1.sel = 1 // error // error + ^ +tests/neg/applydynamic_sip.scala:23: error: method updateDynamic in class Bad1 does not take more parameters + bad1.sel = 1 // error // error + ^ +tests/neg/applydynamic_sip.scala:32: error: method selectDynamic in class Bad2 does not take parameters + bad2.sel // error + ^ +tests/neg/applydynamic_sip.scala:33: error: method applyDynamic in class Bad2 does not take parameters + bad2.sel(1) // error + ^ +tests/neg/applydynamic_sip.scala:34: error: method applyDynamicNamed in class Bad2 does not take parameters + bad2.sel(a = 1) // error + ^ +tests/neg/applydynamic_sip.scala:35: error: method updateDynamic in class Bad2 does not take parameters + bad2.sel = 1 // error + ^ +14 errors found diff --git a/tests/untried/neg/applydynamic_sip.flags b/tests/neg/applydynamic_sip.flags similarity index 100% rename from tests/untried/neg/applydynamic_sip.flags rename to tests/neg/applydynamic_sip.flags diff --git a/tests/neg/applydynamic_sip.scala b/tests/neg/applydynamic_sip.scala new file mode 100644 index 000000000000..7b131e7ff908 --- /dev/null +++ b/tests/neg/applydynamic_sip.scala @@ -0,0 +1,36 @@ +import scala.language.dynamics +object Test extends App { + val qual: Dynamic = ??? + val expr = "expr" + val a = "a" + val a2 = "a2" + + qual.sel(a, a2: _*) // error + qual.sel(arg = a, a2: _*) // error + qual.sel(arg, arg2 = "a2", a2: _*) // error + + class Bad1 extends Dynamic { + def selectDynamic(n: Int) = n + def applyDynamic(n: Int) = n + def applyDynamicNamed(n: Int) = n + def updateDynamic(n: Int) = n + + } + val bad1 = new Bad1 + bad1.sel // error + bad1.sel(1) // error // error + bad1.sel(a = 1) // error // error + bad1.sel = 1 // error // error + + class Bad2 extends Dynamic { + def selectDynamic = 1 + def applyDynamic = 1 + def applyDynamicNamed = 1 + def updateDynamic = 1 + } + val bad2 = new Bad2 + bad2.sel // error + bad2.sel(1) // error + bad2.sel(a = 1) // error + bad2.sel = 1 // error +} diff --git a/tests/neg/emptyCatch.scala b/tests/neg/emptyCatch.scala new file mode 100644 index 000000000000..60951d27aa28 --- /dev/null +++ b/tests/neg/emptyCatch.scala @@ -0,0 +1,3 @@ +object Test { + try {} catch {} // error: `catch` block does not contain a valid expression, try adding a case like - `case e: Exception =>` to the block +} diff --git a/tests/neg/i1503.scala b/tests/neg/i1503.scala new file mode 100644 index 000000000000..8e5dc53c6813 --- /dev/null +++ b/tests/neg/i1503.scala @@ -0,0 +1,14 @@ +object Test { + + val cond = true + def foo1() = println("hi") + def bar1() = println("there") + + def foo2(x: Int) = println("hi") + def bar2(x: Int) = println("there") + + def main(args: Array[String]) = { + (if (cond) foo1 else bar1)() // error: Unit does not take parameters + (if (cond) foo2 else bar2)(22) // error: missing arguments // error: missing arguments + } +} diff --git a/tests/untried/neg/t6355b.check b/tests/neg/t6355b.check similarity index 85% rename from tests/untried/neg/t6355b.check rename to tests/neg/t6355b.check index f827f07e53bd..fb73b9c429f8 100644 --- a/tests/untried/neg/t6355b.check +++ b/tests/neg/t6355b.check @@ -1,11 +1,11 @@ t6355b.scala:14: error: value applyDynamic is not a member of A error after rewriting to x.("bippy") possible cause: maybe a wrong Dynamic method signature? - println(x.bippy(42)) + println(x.bippy(42)) // error ^ t6355b.scala:15: error: value applyDynamic is not a member of A error after rewriting to x.("bippy") possible cause: maybe a wrong Dynamic method signature? - println(x.bippy("42")) + println(x.bippy("42")) // error ^ two errors found diff --git a/tests/untried/neg/t6355b.scala b/tests/neg/t6355b.scala similarity index 83% rename from tests/untried/neg/t6355b.scala rename to tests/neg/t6355b.scala index 5f3c97cb0c68..bba3c4fdce3d 100644 --- a/tests/untried/neg/t6355b.scala +++ b/tests/neg/t6355b.scala @@ -11,7 +11,7 @@ class B(method: String) { object Test { def main(args: Array[String]): Unit = { val x = new A - println(x.bippy(42)) - println(x.bippy("42")) + println(x.bippy(42)) // error + println(x.bippy("42")) // error } } diff --git a/tests/untried/neg/t6663.check b/tests/neg/t6663.check similarity index 100% rename from tests/untried/neg/t6663.check rename to tests/neg/t6663.check diff --git a/tests/untried/neg/t6663.scala b/tests/neg/t6663.scala similarity index 90% rename from tests/untried/neg/t6663.scala rename to tests/neg/t6663.scala index 4a358dfbc5c4..aa4ab08ed216 100644 --- a/tests/untried/neg/t6663.scala +++ b/tests/neg/t6663.scala @@ -13,7 +13,7 @@ object Test extends App { // but, before fixing SI-6663, became // C(42).selectDynamic("foo").get, ignoring // the [String] type parameter - var v = new C(42).foo[String].get :Int + var v = new C(42).foo[String].get :Int // error println(v) } diff --git a/tests/untried/neg/t6920.check b/tests/neg/t6920.check similarity index 87% rename from tests/untried/neg/t6920.check rename to tests/neg/t6920.check index ee4eafb83ec3..8bfd16a5f940 100644 --- a/tests/untried/neg/t6920.check +++ b/tests/neg/t6920.check @@ -1,6 +1,6 @@ t6920.scala:9: error: too many arguments for method applyDynamicNamed: (values: Seq[(String, Any)])String error after rewriting to CompilerError.this.test.applyDynamicNamed("crushTheCompiler")(scala.Tuple2("a", 1), scala.Tuple2("b", 2)) possible cause: maybe a wrong Dynamic method signature? - test.crushTheCompiler(a = 1, b = 2) + test.crushTheCompiler(a = 1, b = 2) // error ^ one error found diff --git a/tests/untried/neg/t6920.scala b/tests/neg/t6920.scala similarity index 80% rename from tests/untried/neg/t6920.scala rename to tests/neg/t6920.scala index 25dc7b3b6bfd..9601ed8d27e6 100644 --- a/tests/untried/neg/t6920.scala +++ b/tests/neg/t6920.scala @@ -6,5 +6,5 @@ class DynTest extends Dynamic { class CompilerError { val test = new DynTest - test.crushTheCompiler(a = 1, b = 2) + test.crushTheCompiler(a = 1, b = 2) // error } diff --git a/tests/untried/neg/t8006.check b/tests/neg/t8006.check similarity index 76% rename from tests/untried/neg/t8006.check rename to tests/neg/t8006.check index fbac26e3ad9c..98207ba30f4d 100644 --- a/tests/untried/neg/t8006.check +++ b/tests/neg/t8006.check @@ -1,6 +1,6 @@ t8006.scala:3: error: too many arguments for method applyDynamicNamed: (value: (String, Any))String error after rewriting to X.this.d.applyDynamicNamed("meth")(scala.Tuple2("value1", 10), scala.Tuple2("value2", 100)) possible cause: maybe a wrong Dynamic method signature? - d.meth(value1 = 10, value2 = 100) // two arguments here, but only one is allowed + d.meth(value1 = 10, value2 = 100) // error: two arguments here, but only one is allowed ^ one error found diff --git a/tests/untried/neg/t8006.scala b/tests/neg/t8006.scala similarity index 62% rename from tests/untried/neg/t8006.scala rename to tests/neg/t8006.scala index 8dc60697dcb9..34946a659ba6 100644 --- a/tests/untried/neg/t8006.scala +++ b/tests/neg/t8006.scala @@ -1,6 +1,6 @@ object X { val d = new D - d.meth(value1 = 10, value2 = 100) // two arguments here, but only one is allowed + d.meth(value1 = 10, value2 = 100) // error: two arguments here, but only one is allowed } import language.dynamics class D extends Dynamic { diff --git a/tests/pending/import-rewrite/compiler.error b/tests/pending/import-rewrite/compiler.error deleted file mode 100644 index 0832d33bb914..000000000000 --- a/tests/pending/import-rewrite/compiler.error +++ /dev/null @@ -1,6 +0,0 @@ -$ scalac tests/pending/import-rewrite/*.scala -$ ./bin/dotc tests/pending/import-rewrite/*.scala -tests/pending/import-rewrite/rewrite.scala:5: error: value apply is not a member of java.io.File.type - Seq("").map(File.apply) - ^ -one error found diff --git a/tests/pending/pos/isApplicableSafe.scala b/tests/pending/pos/isApplicableSafe.scala deleted file mode 100644 index b4cacbf28620..000000000000 --- a/tests/pending/pos/isApplicableSafe.scala +++ /dev/null @@ -1,8 +0,0 @@ -class A { - // Any of Array[List[Symbol]], List[Array[Symbol]], or List[List[Symbol]] compile. - var xs: Array[Array[Symbol]] = _ - var ys: Array[Map[Symbol, Set[Symbol]]] = _ - - xs = Array(Array()) - ys = Array(Map(), Map()) -} diff --git a/tests/pending/run/t2337.check b/tests/pending/run/t2337.check deleted file mode 100644 index 18f1f66fc371..000000000000 --- a/tests/pending/run/t2337.check +++ /dev/null @@ -1,4 +0,0 @@ -(Both Int,-1,-1) -(Both Float,1,1) -(Float then Int,0,0) -(Int then Float,0,0) diff --git a/tests/pending/run/t3150.scala b/tests/pending/run/t3150.scala index 034703b5f7f0..dc95af37384b 100644 --- a/tests/pending/run/t3150.scala +++ b/tests/pending/run/t3150.scala @@ -1,10 +1,26 @@ -object Test { - case object Bob { override def equals(other: Any) = true } - def f(x: Any) = x match { case Bob => Bob } - - def main(args: Array[String]): Unit = { - assert(f(Bob) eq Bob) - assert(f(0) eq Bob) - assert(f(Nil) eq Bob) - } -} + object Test { + case object Bob { override def equals(other: Any) = true } + + class Bob2 { + override def equals(other: Any) = true + } + val Bob2 = new Bob2 + + def f0(x: Any) = x match { case Bob2 => Bob2 } // class cast exception at runtime, dotc only + def f1(x: Any) = x match { case Bob => Bob } // class cast exception at runtime, dotc only + def f2(x: Any): Bob.type = x match { case x @ Bob => x } // class cast exception at runtime, dotc and javac. + + def main(args: Array[String]): Unit = { + assert(f0(Bob2) eq Bob2) + assert(f0(0) eq Bob2) // only dotty fails here + assert(f0(Nil) eq Bob2) + + assert(f1(Bob) eq Bob) + assert(f1(0) eq Bob) // only dotty fails here + assert(f1(Nil) eq Bob) + + assert(f2(Bob) eq Bob) + assert(f2(0) eq Bob) // both dotty and scalac fail here + assert(f2(Nil) eq Bob) + } + } diff --git a/tests/pending/run/unapply.check b/tests/pending/run/unapply.check deleted file mode 100644 index 847e3b381e00..000000000000 --- a/tests/pending/run/unapply.check +++ /dev/null @@ -1,3 +0,0 @@ -unapply.scala:57: warning: comparing values of types Null and Null using `==' will always yield true - assert(doMatch2(b) == null) - ^ diff --git a/tests/pos-scala2/naming-resolution/callsite.scala b/tests/pos-scala2/naming-resolution/callsite.scala new file mode 100644 index 000000000000..036803a26930 --- /dev/null +++ b/tests/pos-scala2/naming-resolution/callsite.scala @@ -0,0 +1,10 @@ +// This one should be rejected according to spec. The import takes precedence +// over the type in the same package because the typeis declared in a +// different compilation unit. scalac does not conform to spec here. +package naming.resolution + +import java.nio.file._ // Imports `Files` + +object Resolution { + def gimmeFiles: Files = Files.list(Paths.get(".")) +} diff --git a/tests/pos-scala2/naming-resolution/package.scala b/tests/pos-scala2/naming-resolution/package.scala new file mode 100644 index 000000000000..f0e26ee95ad7 --- /dev/null +++ b/tests/pos-scala2/naming-resolution/package.scala @@ -0,0 +1,5 @@ +package naming + +package object resolution { + type Files = java.util.stream.Stream[java.nio.file.Path] +} diff --git a/tests/pending/run/t3050.scala b/tests/pos-scala2/t3050.scala similarity index 100% rename from tests/pending/run/t3050.scala rename to tests/pos-scala2/t3050.scala diff --git a/tests/pending/import-rewrite/file.scala b/tests/pos/import-rewrite/file.scala similarity index 100% rename from tests/pending/import-rewrite/file.scala rename to tests/pos/import-rewrite/file.scala diff --git a/tests/pending/import-rewrite/rewrite.scala b/tests/pos/import-rewrite/rewrite.scala similarity index 100% rename from tests/pending/import-rewrite/rewrite.scala rename to tests/pos/import-rewrite/rewrite.scala diff --git a/tests/pos/isApplicableSafe.scala b/tests/pos/isApplicableSafe.scala new file mode 100644 index 000000000000..c54df1f22985 --- /dev/null +++ b/tests/pos/isApplicableSafe.scala @@ -0,0 +1,54 @@ +import reflect.ClassTag + +// The same problems arise in real arrays. +class A { + + class Array[T] + object Array { + def apply[T: ClassTag](xs: T*): Array[T] = ??? + def apply(x: Int, xs: Int*): Array[Int] = ??? + } + + // Any of Array[List[Symbol]], List[Array[Symbol]], or List[List[Symbol]] compile. + var xs: Array[Array[Symbol]] = _ + var ys: Array[Map[Symbol, Set[Symbol]]] = _ + + //xs = Array(Array()) + // gives: + // + // isApplicableSafe.scala:15: error: type mismatch: + // found : A.this.Array[Nothing] + // required: A.this.Array[Symbol] + // xs = Array(Array()) + // + // Here's the sequence of events that leads to this problem: + // + // 1. the outer Array.apply is overloaded, so we need to typecheck the inner one + // without an expected prototype + // + // 2. The inner Array.apply needs a ClassTag, so we need to instantiate + // its type variable, and the best instantiation is Nothing. + // + // To prevent this, we'd need to do several things: + // + // 1. Pass argument types lazily into the isApplicable call in resolveOverloaded, + // so that we can call constrainResult before any arguments are evaluated. + // + // 2. This is still not enough because the result type is initially an IgnoredProto. + // (because an implicit might have to be inserted around the call, so we cannot + // automatically assume that the call result is a subtype of the expected type). + // Hence, we need to somehow create a closure in constrainResult that does the + // comparison with the real expected result type "on demand". + // + // 3. When instantiating a type variable we need to categorize that some instantiations + // are suspicous (e.g. scalac avoids instantiating to Nothing). In these + // circumstances we should try to excute the delayed constrainResult closures + // in order to get a better instance type. + // + // Quite a lot of work. It's looking really complicated to fix this. + + + ys = Array(Map(), Map()) + + val zs = Array(Map()) +} diff --git a/tests/pos/t1500a.scala b/tests/pos/t1500a.scala new file mode 100644 index 000000000000..adf46329aaa2 --- /dev/null +++ b/tests/pos/t1500a.scala @@ -0,0 +1,28 @@ +trait Step0 +trait Step1 +trait Step2 +trait Step3 +trait Step4 +trait Step5 +trait Step6 + +object Steps { + implicit val Step0: Step0 = new Step0 {} + implicit def Step1(implicit p: Step0): Step1 = new Step1 {} + implicit def Step2(implicit p: Step1): Step2 = new Step2 {} + implicit def Step3(implicit p: Step2): Step3 = new Step3 {} + implicit def Step4(implicit p: Step3): Step4 = new Step4 {} + implicit def Step5(implicit p: Step4): Step5 = new Step5 {} + implicit def Step6(implicit p: Step5): Step6 = new Step6 {} +} + +object StepsTest { + import Steps._ + + implicitly[Step0] + implicitly[Step1] + implicitly[Step2] + implicitly[Step3] + implicitly[Step4] + implicitly[Step6] +} diff --git a/tests/pos/t1513a.scala b/tests/pos/t1513a.scala new file mode 100644 index 000000000000..3c4c023764bb --- /dev/null +++ b/tests/pos/t1513a.scala @@ -0,0 +1,36 @@ +object Test { + // Heterogeneous lists and natural numbers as defined in shapeless. + + sealed trait HList + sealed trait ::[H, T <: HList] extends HList + sealed trait HNil extends HList + + sealed trait Nat + sealed trait Succ[P <: Nat] extends Nat + sealed trait Zero extends Nat + + // Accessor type class to compute the N'th element of an HList L. + + trait Accessor[L <: HList, N <: Nat] { type Out } + object Accessor { + type Aux[L <: HList, N <: Nat, O] = Accessor[L, N] { type Out = O } + + // (H :: T).At[Zero] = H + implicit def caseZero[H, T <: HList]: Aux[H :: T, Zero, H] = ??? + + // T.At[N] = O => (H :: T).At[Succ[N]] = O + implicit def caseN[H, T <: HList, N <: Nat, O] + (implicit a: Aux[T, N, O]): Aux[H :: T, Succ[N], O] = ??? + } + + case class Proxy[T]() + + def at1[NN <: Nat, OO] (implicit e: Accessor.Aux[String :: HNil, NN, OO]): OO = ??? + def at2[NN <: Nat, OO](p: Proxy[NN])(implicit e: Accessor.Aux[String :: HNil, NN, OO]): OO = ??? + + // N is fixed by a value + at2(Proxy[Zero]): String + + // N is fixed as a type parameter (by name) + at1[NN = Zero]: String +} diff --git a/tests/pos/t1513b.scala b/tests/pos/t1513b.scala new file mode 100644 index 000000000000..546649383890 --- /dev/null +++ b/tests/pos/t1513b.scala @@ -0,0 +1,25 @@ +object Test { + def f[ + T1 <: String, + T2 <: Int, + T3 <: Boolean + ](a1: T1, a2: T2, a3: T3) = () + + f ("", 1, true) + f[T1 = String] ("", 1, true) + f[T2 = Int] ("", 1, true) + f[T3 = Boolean] ("", 1, true) + f[T1 = String, T2 = Int] ("", 1, true) + f[T1 = String, T3 = Boolean] ("", 1, true) + f[T2 = Int, T1 = String] ("", 1, true) + f[T2 = Int, T3 = Boolean] ("", 1, true) + f[T3 = Boolean, T2 = Int] ("", 1, true) + f[T3 = Boolean, T1 = String] ("", 1, true) + f[T1 = String, T2 = Int, T3 = Boolean]("", 1, true) + f[T1 = String, T3 = Boolean, T2 = Int] ("", 1, true) + f[T2 = Int, T1 = String, T3 = Boolean]("", 1, true) + f[T2 = Int, T3 = Boolean, T1 = String] ("", 1, true) + f[T3 = Boolean, T1 = String, T2 = Int] ("", 1, true) + f[T3 = Boolean, T2 = Int, T1 = String] ("", 1, true) + f[String, Int, Boolean] ("", 1, true) +} diff --git a/tests/pos/tryWithoutHandler.scala b/tests/pos/tryWithoutHandler.scala new file mode 100644 index 000000000000..ffe334984184 --- /dev/null +++ b/tests/pos/tryWithoutHandler.scala @@ -0,0 +1,7 @@ +object Test { + def main(args: Array[String]): Unit = { + try { + println("hello") + } + } +} diff --git a/tests/pending/run/applydynamic_sip.check b/tests/run/applydynamic_sip.check similarity index 100% rename from tests/pending/run/applydynamic_sip.check rename to tests/run/applydynamic_sip.check diff --git a/tests/pending/run/applydynamic_sip.flags b/tests/run/applydynamic_sip.flags similarity index 100% rename from tests/pending/run/applydynamic_sip.flags rename to tests/run/applydynamic_sip.flags diff --git a/tests/pending/run/applydynamic_sip.scala b/tests/run/applydynamic_sip.scala similarity index 98% rename from tests/pending/run/applydynamic_sip.scala rename to tests/run/applydynamic_sip.scala index a163ab960771..7f81a644a60a 100644 --- a/tests/pending/run/applydynamic_sip.scala +++ b/tests/run/applydynamic_sip.scala @@ -1,3 +1,4 @@ +import scala.language.dynamics object Test extends dotty.runtime.LegacyApp { object stubUpdate { def update(as: Any*) = println(".update"+as.toList.mkString("(",", ", ")")) diff --git a/tests/pending/run/dynamic-anyval.check b/tests/run/dynamic-anyval.check similarity index 100% rename from tests/pending/run/dynamic-anyval.check rename to tests/run/dynamic-anyval.check diff --git a/tests/pending/run/dynamic-anyval.scala b/tests/run/dynamic-anyval.scala similarity index 100% rename from tests/pending/run/dynamic-anyval.scala rename to tests/run/dynamic-anyval.scala diff --git a/tests/run/dynamicDynamicTests.scala b/tests/run/dynamicDynamicTests.scala index 3f8da8298720..05b878f1c8c2 100644 --- a/tests/run/dynamicDynamicTests.scala +++ b/tests/run/dynamicDynamicTests.scala @@ -23,7 +23,16 @@ class Baz extends scala.Dynamic { def updateDynamic(name: String)(value: String): String = "updateDynamic(" + name + ")(" + value + ")" } +class Qux extends scala.Dynamic { + def selectDynamic[T](name: String): String = "selectDynamic(" + name + ")" + def applyDynamic[T](name: String)(args: String*): String = "applyDynamic(" + name + ")" + args.mkString("(", ", ", ")") + def applyDynamicNamed[T](name: String)(args: (String, Any)*): String = "applyDynamicNamed(" + name + ")" + args.mkString("(", ", ", ")") + def updateDynamic[T](name: String)(value: T): String = "updateDynamic(" + name + ")(" + value + ")" +} + object Test { + val qux = new Qux + implicit class StringUpdater(str: String) { def update(name: String, v: String) = s"$str.update(" + name + ", " + v + ")" } @@ -42,6 +51,7 @@ object Test { runFooTests2() runBarTests() runBazTests() + runQuxTests() assert(!failed) } @@ -161,4 +171,35 @@ object Test { assertEquals("selectDynamic(bazSelectUpdate).update(7, value)", baz.bazSelectUpdate(7) = "value") assertEquals("selectDynamic(bazSelectUpdate).update(7, 10)", baz.bazSelectUpdate(7) = 10) } + + /** Test correct lifting of type parameters */ + def runQuxTests() = { + implicit def intToString(n: Int): String = n.toString + + val qux = new Qux + + assertEquals("selectDynamic(quxSelect)", qux.quxSelect) + assertEquals("selectDynamic(quxSelect)", qux.quxSelect[Int]) + + assertEquals("applyDynamic(quxApply)()", qux.quxApply()) + assertEquals("applyDynamic(quxApply)()", qux.quxApply[Int]()) + assertEquals("applyDynamic(quxApply)(1)", qux.quxApply(1)) + assertEquals("applyDynamic(quxApply)(1)", qux.quxApply[Int](1)) + assertEquals("applyDynamic(quxApply)(1, 2, 3)", qux.quxApply(1, 2, 3)) + assertEquals("applyDynamic(quxApply)(1, 2, 3)", qux.quxApply[Int](1, 2, 3)) + assertEquals("applyDynamic(quxApply)(1, 2, a)", qux.quxApply(1, 2, "a")) + assertEquals("applyDynamic(quxApply)(1, 2, a)", qux.quxApply[Int](1, 2, "a")) + + assertEquals("applyDynamicNamed(quxApplyNamed)((a,1))", qux.quxApplyNamed(a = 1)) + assertEquals("applyDynamicNamed(quxApplyNamed)((a,1))", qux.quxApplyNamed[Int](a = 1)) + assertEquals("applyDynamicNamed(quxApplyNamed)((a,1), (b,2))", qux.quxApplyNamed(a = 1, b = "2")) + assertEquals("applyDynamicNamed(quxApplyNamed)((a,1), (b,2))", qux.quxApplyNamed[Int](a = 1, b = "2")) + assertEquals("applyDynamicNamed(quxApplyNamed)((a,1), (,abc))", qux.quxApplyNamed(a = 1, "abc")) + assertEquals("applyDynamicNamed(quxApplyNamed)((a,1), (,abc))", qux.quxApplyNamed[Int](a = 1, "abc")) + + assertEquals("updateDynamic(quxUpdate)(abc)", qux.quxUpdate = "abc") + + assertEquals("selectDynamic(quxSelectUpdate).update(key, value)", qux.quxSelectUpdate("key") = "value") + assertEquals("selectDynamic(quxSelectUpdate).update(key, value)", qux.quxSelectUpdate[Int]("key") = "value") + } } diff --git a/tests/run/i1490.check b/tests/run/i1490.check new file mode 100644 index 000000000000..9e8a46acf90a --- /dev/null +++ b/tests/run/i1490.check @@ -0,0 +1,3 @@ +true +true +false diff --git a/tests/run/i1490.scala b/tests/run/i1490.scala new file mode 100644 index 000000000000..554bc3940c17 --- /dev/null +++ b/tests/run/i1490.scala @@ -0,0 +1,13 @@ +class Base { + type T = Int | Boolean + def test(x: Object) = x.isInstanceOf[T] +} + +object Test { + def main(args: Array[String]) = { + val b = new Base + println(b.test(Int.box(3))) + println(b.test(Boolean.box(false))) + println(b.test(Double.box(3.4))) + } +} \ No newline at end of file diff --git a/tests/run/i1503.check b/tests/run/i1503.check new file mode 100644 index 000000000000..8cc0be027139 --- /dev/null +++ b/tests/run/i1503.check @@ -0,0 +1,5 @@ +hello +hi +33 +hi +hi diff --git a/tests/run/i1503.scala b/tests/run/i1503.scala new file mode 100644 index 000000000000..56bb9af0cf0b --- /dev/null +++ b/tests/run/i1503.scala @@ -0,0 +1,38 @@ +object Test { + + def test1() = + (new Function0[Unit] { + def apply() = println("hello") + })() + + val cond = true + val foo = () => println("hi") + val bar = () => println("there") + + val baz = (x: Int) => println(x) + + def test2() = + (if (cond) foo else bar)() + + def test2a() = + (if (cond) baz else baz)(33) + + def test3() = + (try foo + catch { case ex: Exception => bar } + finally ())() + + def test4() = + (cond match { + case true => foo + case false => bar + })() + + def main(args: Array[String]) = { + test1() + test2() + test2a() + test3() + test4() + } +} diff --git a/tests/run/t1335.scala b/tests/run/t1335.scala new file mode 100644 index 000000000000..047f7b566a70 --- /dev/null +++ b/tests/run/t1335.scala @@ -0,0 +1,11 @@ +case class MyTuple(a: Int, b: Int) + +object Test { + def main(args: Array[String]): Unit = + try { + val mt: MyTuple = null + val MyTuple(a, b) = mt + } catch { + case e: MatchError => () + } +} diff --git a/tests/run/t1500b.scala b/tests/run/t1500b.scala new file mode 100644 index 000000000000..8b52731a597a --- /dev/null +++ b/tests/run/t1500b.scala @@ -0,0 +1,21 @@ +sealed trait Nat +sealed trait Succ[Prev <: Nat] extends Nat +sealed trait Zero extends Nat + +case class ToInt[N <: Nat](value: Int) + +object ToInt { + implicit val caseZero: ToInt[Zero] = ToInt(0) + implicit def caseSucc[Prev <: Nat](implicit e: ToInt[Prev]): ToInt[Succ[Prev]] = ToInt(e.value + 1) +} + +object Test { + def main(args: Array[String]): Unit = { + assert(implicitly[ToInt[Zero]].value == 0) + assert(implicitly[ToInt[Succ[Zero]]].value == 1) + assert(implicitly[ToInt[Succ[Succ[Zero]]]].value == 2) + assert(implicitly[ToInt[Succ[Succ[Succ[Zero]]]]].value == 3) + assert(implicitly[ToInt[Succ[Succ[Succ[Succ[Zero]]]]]].value == 4) + assert(implicitly[ToInt[Succ[Succ[Succ[Succ[Succ[Zero]]]]]]].value == 5) + } +} diff --git a/tests/run/t1500c.scala b/tests/run/t1500c.scala new file mode 100644 index 000000000000..5c33b7a2f3d8 --- /dev/null +++ b/tests/run/t1500c.scala @@ -0,0 +1,19 @@ +sealed trait HList +sealed trait HNil extends HList +sealed trait ::[H, T <: HList] extends HList + +case class Size[L <: HList](value: Int) + +object Size { + implicit val caseHNil: Size[HNil] = Size(0) + implicit def caseHCons[H, T <: HList](implicit e: Size[T]): Size[H :: T] = Size(e.value + 1) +} + +object Test { + def main(args: Array[String]): Unit = { + assert(implicitly[Size[HNil]].value == 0) + assert(implicitly[Size[Int :: HNil]].value == 1) + assert(implicitly[Size[Int :: Int :: HNil]].value == 2) + assert(implicitly[Size[Int :: Int :: Int :: HNil]].value == 3) + } +} diff --git a/tests/pending/run/t298.check b/tests/run/t298.check similarity index 100% rename from tests/pending/run/t298.check rename to tests/run/t298.check diff --git a/tests/pending/run/t298.scala b/tests/run/t298.scala similarity index 100% rename from tests/pending/run/t298.scala rename to tests/run/t298.scala diff --git a/tests/pending/run/t3026.check b/tests/run/t3026.check similarity index 100% rename from tests/pending/run/t3026.check rename to tests/run/t3026.check diff --git a/tests/pending/run/t3026.scala b/tests/run/t3026.scala similarity index 100% rename from tests/pending/run/t3026.scala rename to tests/run/t3026.scala diff --git a/tests/pending/run/t3353.check b/tests/run/t3353.check similarity index 100% rename from tests/pending/run/t3353.check rename to tests/run/t3353.check diff --git a/tests/pending/run/t3353.scala b/tests/run/t3353.scala similarity index 100% rename from tests/pending/run/t3353.scala rename to tests/run/t3353.scala diff --git a/tests/pending/run/t4536.check b/tests/run/t4536.check similarity index 100% rename from tests/pending/run/t4536.check rename to tests/run/t4536.check diff --git a/tests/pending/run/t4536.flags b/tests/run/t4536.flags similarity index 100% rename from tests/pending/run/t4536.flags rename to tests/run/t4536.flags diff --git a/tests/pending/run/t4536.scala b/tests/run/t4536.scala similarity index 91% rename from tests/pending/run/t4536.scala rename to tests/run/t4536.scala index 6661eae6a75f..89a93a5e06dc 100644 --- a/tests/pending/run/t4536.scala +++ b/tests/run/t4536.scala @@ -1,8 +1,4 @@ - - - - - +import scala.language.dynamics object dynamicObject extends Dynamic { def applyDynamic(m: String)() = println("obj: " + m); @@ -38,7 +34,7 @@ object dynamicMixin extends dynamicAbstractClass with dynamicTrait { object Test { - def main(args: Array[String]) { + def main(args: Array[String]) = { val cls = new dynamicClass dynamicMixin } diff --git a/tests/pending/run/t5040.check b/tests/run/t5040.check similarity index 100% rename from tests/pending/run/t5040.check rename to tests/run/t5040.check diff --git a/tests/pending/run/t5040.flags b/tests/run/t5040.flags similarity index 100% rename from tests/pending/run/t5040.flags rename to tests/run/t5040.flags diff --git a/tests/pending/run/t5040.scala b/tests/run/t5040.scala similarity index 71% rename from tests/pending/run/t5040.scala rename to tests/run/t5040.scala index 6813c1b27d89..58d054412928 100644 --- a/tests/pending/run/t5040.scala +++ b/tests/run/t5040.scala @@ -1,3 +1,4 @@ +import scala.language.dynamics // originaly used the flag -language:dynamics in t5040.flags, .flags are currently ignored abstract class Prova2 extends Dynamic { def applyDynamic(m: String)(): Unit private def privateMethod() = println("private method") diff --git a/tests/pending/run/t5733.check b/tests/run/t5733.check similarity index 100% rename from tests/pending/run/t5733.check rename to tests/run/t5733.check diff --git a/tests/pending/run/t5733.scala b/tests/run/t5733.scala similarity index 100% rename from tests/pending/run/t5733.scala rename to tests/run/t5733.scala diff --git a/tests/pending/run/t6353.check b/tests/run/t6353.check similarity index 100% rename from tests/pending/run/t6353.check rename to tests/run/t6353.check diff --git a/tests/pending/run/t6353.scala b/tests/run/t6353.scala similarity index 100% rename from tests/pending/run/t6353.scala rename to tests/run/t6353.scala diff --git a/tests/pending/run/t6355.check b/tests/run/t6355.check similarity index 100% rename from tests/pending/run/t6355.check rename to tests/run/t6355.check diff --git a/tests/pending/run/t6355.scala b/tests/run/t6355.scala similarity index 100% rename from tests/pending/run/t6355.scala rename to tests/run/t6355.scala diff --git a/tests/pending/run/t6663.check b/tests/run/t6663.check similarity index 100% rename from tests/pending/run/t6663.check rename to tests/run/t6663.check diff --git a/tests/pending/run/t6663.flags b/tests/run/t6663.flags similarity index 100% rename from tests/pending/run/t6663.flags rename to tests/run/t6663.flags diff --git a/tests/pending/run/t6663.scala b/tests/run/t6663.scala similarity index 100% rename from tests/pending/run/t6663.scala rename to tests/run/t6663.scala diff --git a/tests/pending/run/unapply.scala b/tests/run/unapply.scala similarity index 98% rename from tests/pending/run/unapply.scala rename to tests/run/unapply.scala index 43f02b9f3985..7b10030ba76d 100644 --- a/tests/pending/run/unapply.scala +++ b/tests/run/unapply.scala @@ -87,8 +87,8 @@ object Mas { object LisSeqArr { def run(): Unit = { - assert((1,2) == ((List(1,2,3): Any) match { case List(x,y,_*) => (x,y)})) - assert((1,2) == ((List(1,2,3): Any) match { case Seq(x,y,_*) => (x,y)})) + assert((1,2) == ((List(1,2,3): Any) match { case List(x,y,_: _*) => (x,y)})) + assert((1,2) == ((List(1,2,3): Any) match { case Seq(x,y,_: _*) => (x,y)})) } } diff --git a/tests/untried/neg/applydynamic_sip.check b/tests/untried/neg/applydynamic_sip.check deleted file mode 100644 index 2cb2e7f095ee..000000000000 --- a/tests/untried/neg/applydynamic_sip.check +++ /dev/null @@ -1,73 +0,0 @@ -applydynamic_sip.scala:7: error: applyDynamic does not support passing a vararg parameter - qual.sel(a, a2: _*) - ^ -applydynamic_sip.scala:8: error: applyDynamicNamed does not support passing a vararg parameter - qual.sel(arg = a, a2: _*) - ^ -applydynamic_sip.scala:8: error: not found: value arg - qual.sel(arg = a, a2: _*) - ^ -applydynamic_sip.scala:9: error: applyDynamicNamed does not support passing a vararg parameter - qual.sel(arg, arg2 = "a2", a2: _*) - ^ -applydynamic_sip.scala:9: error: not found: value arg - qual.sel(arg, arg2 = "a2", a2: _*) - ^ -applydynamic_sip.scala:9: error: not found: value arg2 - qual.sel(arg, arg2 = "a2", a2: _*) - ^ -applydynamic_sip.scala:18: error: type mismatch; - found : String("sel") - required: Int -error after rewriting to Test.this.bad1.selectDynamic("sel") -possible cause: maybe a wrong Dynamic method signature? - bad1.sel - ^ -applydynamic_sip.scala:19: error: type mismatch; - found : String("sel") - required: Int -error after rewriting to Test.this.bad1.applyDynamic("sel") -possible cause: maybe a wrong Dynamic method signature? - bad1.sel(1) - ^ -applydynamic_sip.scala:20: error: type mismatch; - found : String("sel") - required: Int -error after rewriting to Test.this.bad1.applyDynamicNamed("sel") -possible cause: maybe a wrong Dynamic method signature? - bad1.sel(a = 1) - ^ -applydynamic_sip.scala:20: error: reassignment to val - bad1.sel(a = 1) - ^ -applydynamic_sip.scala:21: error: type mismatch; - found : String("sel") - required: Int -error after rewriting to Test.this.bad1.updateDynamic("sel") -possible cause: maybe a wrong Dynamic method signature? - bad1.sel = 1 - ^ -applydynamic_sip.scala:29: error: Int does not take parameters -error after rewriting to Test.this.bad2.selectDynamic("sel") -possible cause: maybe a wrong Dynamic method signature? - bad2.sel - ^ -applydynamic_sip.scala:30: error: Int does not take parameters -error after rewriting to Test.this.bad2.applyDynamic("sel") -possible cause: maybe a wrong Dynamic method signature? - bad2.sel(1) - ^ -applydynamic_sip.scala:31: error: Int does not take parameters -error after rewriting to Test.this.bad2.applyDynamicNamed("sel") -possible cause: maybe a wrong Dynamic method signature? - bad2.sel(a = 1) - ^ -applydynamic_sip.scala:31: error: reassignment to val - bad2.sel(a = 1) - ^ -applydynamic_sip.scala:32: error: Int does not take parameters -error after rewriting to Test.this.bad2.updateDynamic("sel") -possible cause: maybe a wrong Dynamic method signature? - bad2.sel = 1 - ^ -16 errors found diff --git a/tests/untried/neg/applydynamic_sip.scala b/tests/untried/neg/applydynamic_sip.scala deleted file mode 100644 index ee4432ebe604..000000000000 --- a/tests/untried/neg/applydynamic_sip.scala +++ /dev/null @@ -1,33 +0,0 @@ -object Test extends App { - val qual: Dynamic = ??? - val expr = "expr" - val a = "a" - val a2 = "a2" - - qual.sel(a, a2: _*) - qual.sel(arg = a, a2: _*) - qual.sel(arg, arg2 = "a2", a2: _*) - - val bad1 = new Dynamic { - def selectDynamic(n: Int) = n - def applyDynamic(n: Int) = n - def applyDynamicNamed(n: Int) = n - def updateDynamic(n: Int) = n - - } - bad1.sel - bad1.sel(1) - bad1.sel(a = 1) - bad1.sel = 1 - - val bad2 = new Dynamic { - def selectDynamic = 1 - def applyDynamic = 1 - def applyDynamicNamed = 1 - def updateDynamic = 1 - } - bad2.sel - bad2.sel(1) - bad2.sel(a = 1) - bad2.sel = 1 -}