From 563b8ab6c712bfbae28aacfbd8323a46f4fe1bae Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 24 Aug 2016 17:20:09 +0200 Subject: [PATCH 01/51] Add symbols to Doc AST, needed for `@usecase` Also eliminates the need for comment processing to be part of the `DocASTPhase`, so this should be put into a DocMiniPhase --- .../dotty/tools/dottydoc/core/DocASTPhase.scala | 14 ++++++++------ .../tools/dottydoc/core/MiniPhaseTransform.scala | 7 +++++++ .../src/dotty/tools/dottydoc/model/entities.scala | 4 ++++ .../src/dotty/tools/dottydoc/model/internal.scala | 8 ++++++++ dottydoc/test/ConstructorTest.scala | 14 +++++++------- dottydoc/test/PackageStructure.scala | 7 ++++--- 6 files changed, 38 insertions(+), 16 deletions(-) diff --git a/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala index 7744752ce530..8df00d894174 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala @@ -60,6 +60,7 @@ class DocASTPhase extends Phase { .map { meth => track(meth.symbol, ctx, tree.symbol) { DefImpl( + meth.symbol, meth.symbol.name.show, Nil, path(meth.symbol), @@ -74,6 +75,7 @@ class DocASTPhase extends Phase { val vals = sym.info.fields.filterNot(_.symbol.is(Flags.Private | Flags.Synthetic)).map { value => track(value.symbol, ctx, tree.symbol) { ValImpl( + value.symbol, value.symbol.name.show, Nil, path(value.symbol), returnType(value.info), @@ -90,38 +92,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}") diff --git a/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala b/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala index 2690ac7b77c9..fc0b40955b1c 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala @@ -77,6 +77,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, @@ -90,6 +91,7 @@ object transform { } case c: Class => transformEntity(c, _.classTransformation) { cls => ClassImpl( + cls.symbol, cls.name, cls.members.map(traverse), cls.modifiers, @@ -102,6 +104,7 @@ object transform { } case cc: CaseClass => transformEntity(cc, _.caseClassTransformation) { cc => CaseClassImpl( + cc.symbol, cc.name, cc.members.map(traverse), cc.modifiers, @@ -114,6 +117,7 @@ object transform { } case trt: Trait => transformEntity(trt, _.traitTransformation) { trt => TraitImpl( + trt.symbol, trt.name, trt.members.map(traverse), trt.modifiers, @@ -126,6 +130,7 @@ object transform { } case obj: Object => transformEntity(obj, _.objectTransformation) { obj => ObjectImpl( + obj.symbol, obj.name, obj.members.map(traverse), obj.modifiers, @@ -136,6 +141,7 @@ object transform { } case df: Def => transformEntity(df, _.defTransformation) { df => DefImpl( + df.symbol, df.name, df.modifiers, df.path, @@ -148,6 +154,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/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/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}'" From e8c9277fc5eca532b0cbd945e7f5e874f72e09bd Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 24 Aug 2016 17:23:32 +0200 Subject: [PATCH 02/51] Add `Comments` object instead of `Scanners.Comment` case class --- .../model/comment/CommentExpander.scala | 4 +- src/dotty/tools/dotc/ast/Trees.scala | 3 +- src/dotty/tools/dotc/core/Comments.scala | 77 ++++++++++++++++ src/dotty/tools/dotc/core/Contexts.scala | 2 +- src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- src/dotty/tools/dotc/parsing/Scanners.scala | 6 +- test/test/DottyDocParsingTests.scala | 90 +++++++++---------- 7 files changed, 128 insertions(+), 56 deletions(-) create mode 100644 src/dotty/tools/dotc/core/Comments.scala diff --git a/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala b/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala index 32a0d8128b55..7e213c2f3556 100644 --- a/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala +++ b/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala @@ -77,7 +77,7 @@ trait CommentExpander { */ 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("") + if (docStr.length == 0) ctx.docbase.docstring(sym).map(c => template(c.raw)).getOrElse("") else template(docStr) ownComment = replaceInheritDocToInheritdoc(ownComment) @@ -287,7 +287,7 @@ trait CommentExpander { def defineVariables(sym: Symbol)(implicit ctx: Context) = { val Trim = "(?s)^[\\s&&[^\n\r]]*(.*?)\\s*$".r - val raw = ctx.docbase.docstring(sym).map(_.chrs).getOrElse("") + val raw = ctx.docbase.docstring(sym).map(_.raw).getOrElse("") defs(sym) ++= defines(raw).map { str => { val start = skipWhitespace(str, "@define".length) diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index cf11c27faa12..e8b4fd994946 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 @@ -15,7 +15,6 @@ import printing.Printer import util.{Stats, Attachment, DotClass} import annotation.unchecked.uncheckedVariance import language.implicitConversions -import parsing.Scanners.Comment object Trees { diff --git a/src/dotty/tools/dotc/core/Comments.scala b/src/dotty/tools/dotc/core/Comments.scala new file mode 100644 index 000000000000..bf1f2553702f --- /dev/null +++ b/src/dotty/tools/dotc/core/Comments.scala @@ -0,0 +1,77 @@ +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.Position +import dotc.parsing.Parsers.Parser +import dotty.tools.dottydoc.model.comment.CommentUtils._ + +object Comments { + + case class Comment(pos: Position, raw: String)(implicit ctx: Context) { + val isDocComment = raw.startsWith("/**") + + private[this] lazy val sections = tagIndex(raw) + + private def fold[A](z: A)(op: => A) = if (!isDocComment) z else op + + lazy val usecases = fold(List.empty[UseCase]) { + sections + .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 def decomposeUseCase(start: Int, end: Int): UseCase = { + val codeStart = skipWhitespace(raw, start + "@usecase".length) + val codeEnd = skipToEol(raw, codeStart) + val code = raw.substring(codeStart, codeEnd) + val codePos = Position(codeStart, codeEnd) + val commentStart = skipLineLead(raw, codeEnd + 1) min end + val comment = "/** " + raw.substring(commentStart, end) + "*/" + val commentPos = Position(commentStart, end) + + UseCase(Comment(commentPos, comment), code + " = ???", codePos) + } + } + + case class UseCase(comment: Comment, code: String, codePos: Position)(implicit ctx: Context) { + /** Entered by Namer */ + var symbol: Symbol = _ + + lazy val untpdCode: untpd.Tree = { + val tree = new Parser(new SourceFile("", code)).localDef(codePos.start, EmptyFlags) + + tree match { + case tree: untpd.DefDef => tree + case _ => + ctx.error("proper def was not found in `@usecase`", codePos) + tree + } + } + + /** Set by typer calling `typeTree` */ + var tpdCode: tpd.DefDef = _ + + def typeTree()(implicit ctx: Context): Unit = untpdCode match { + case df: untpd.DefDef => ctx.typer.typedDefDef(df, symbol) match { + case tree: tpd.DefDef => tpdCode = tree + case _ => ctx.error("proper def was not found in `@usecase`", codePos) + } + case _ => ctx.error("proper def was not found in `@usecase`", codePos) + } + } +} diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index cd76fe88bb45..9b130851d037 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,6 @@ import printing._ import config.{Settings, ScalaSettings, Platform, JavaPlatform, SJSPlatform} import language.implicitConversions import DenotTransformers.DenotTransformer -import parsing.Scanners.Comment import xsbti.AnalysisCallback object Contexts { diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 378aa6ed71f8..ede616cf9d5a 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 { 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/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 */ From 1caf3c3cfd74110ef30361e035448019d9b24713 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 24 Aug 2016 17:24:48 +0200 Subject: [PATCH 03/51] Typecheck usecases --- src/dotty/tools/dotc/typer/Namer.scala | 9 ++++++++- src/dotty/tools/dotc/typer/Typer.scala | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index d90f37860545..1be15857b191 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -454,7 +454,14 @@ 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 => + val cmt = t.rawComment + ctx.docbase.addDocstring(sym, cmt) + + cmt.get.usecases.foreach { usecase => + usecase.symbol = enterSymbol(createSymbol(usecase.untpdCode)) + } + case _ => () } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index f0086b0ab892..f514abfb5032 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1127,6 +1127,10 @@ 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)))) } + + /** Type usecases */ + ctx.docbase.docstring(sym).map(_.usecases.map(_.typeTree())) + 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 } From c99ff831e4e1afd732f325fcc7720dcdd0426ca0 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 24 Aug 2016 17:25:11 +0200 Subject: [PATCH 04/51] Add `UsecasePhase` to dottydoc --- .../src/dotty/tools/dottydoc/DottyDoc.scala | 3 +- .../tools/dottydoc/core/UsecasePhase.scala | 26 ++++++++++ dottydoc/test/UsecaseTest.scala | 52 +++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala create mode 100644 dottydoc/test/UsecaseTest.scala diff --git a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala index 2d4c7abcf3ee..dd0bb0ec4094 100644 --- a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala +++ b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala @@ -31,7 +31,8 @@ class DocCompiler extends Compiler { List(new DocFrontEnd), List(new DocImplicitsPhase), List(new DocASTPhase), - List(DocMiniTransformations(new LinkReturnTypes, + List(DocMiniTransformations(new UsecasePhase, + new LinkReturnTypes, new LinkParamListTypes, new LinkImplicitlyAddedTypes, new LinkSuperTypes, 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..758d65e92880 --- /dev/null +++ b/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala @@ -0,0 +1,26 @@ +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 + +class UsecasePhase extends DocMiniPhase { + private def defdefToDef(d: tpd.DefDef, sym: Symbol)(implicit ctx: Context) = DefImpl( + sym, + d.name.decode.toString, + flags(d), path(d.symbol), + 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/test/UsecaseTest.scala b/dottydoc/test/UsecaseTest.scala new file mode 100644 index 000000000000..e2190e70930d --- /dev/null +++ b/dottydoc/test/UsecaseTest.scala @@ -0,0 +1,52 @@ +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._ + +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(map: Def) = trt.members + + val returnValue = map.returnValue match { + case ref: TypeReference => ref.title + case _ => + assert( + false, + "Incorrect return value after usecase transformation" + ) + "" + } + + assert( + map.typeParams.isEmpty, + "Type parameters were not stripped by usecase" + ) + assert(returnValue == "A", "Incorrect return type after usecase") + } + } + } +} From d26b59d26fc4ef9fd1e088691d61fcd9954e2872 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 25 Aug 2016 14:07:07 +0200 Subject: [PATCH 05/51] Fix name clashes because of `@usecase` --- .../tools/dottydoc/core/UsecasePhase.scala | 2 +- dottydoc/test/UsecaseTest.scala | 17 +++++++-- src/dotty/tools/dotc/core/Comments.scala | 37 +++++++++++++------ 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala index 758d65e92880..4d9c0abbdd48 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala @@ -13,7 +13,7 @@ import dotty.tools.dotc.core.Symbols.Symbol class UsecasePhase extends DocMiniPhase { private def defdefToDef(d: tpd.DefDef, sym: Symbol)(implicit ctx: Context) = DefImpl( sym, - d.name.decode.toString, + d.name.show.split("\\$").head, // UseCase defs get $pos appended to their names flags(d), path(d.symbol), returnType(d.tpt.tpe), typeParams(d.symbol), diff --git a/dottydoc/test/UsecaseTest.scala b/dottydoc/test/UsecaseTest.scala index e2190e70930d..d5f3388929da 100644 --- a/dottydoc/test/UsecaseTest.scala +++ b/dottydoc/test/UsecaseTest.scala @@ -29,9 +29,9 @@ class UsecaseTest extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { case PackageImpl(_, _, List(trt: Trait), _, _) => - val List(map: Def) = trt.members + val List(foo: Def) = trt.members - val returnValue = map.returnValue match { + val returnValue = foo.returnValue match { case ref: TypeReference => ref.title case _ => assert( @@ -42,11 +42,22 @@ class UsecaseTest extends DottyTest { } assert( - map.typeParams.isEmpty, + 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 checkIterator = { + val sources = + "./scala-scala/src/library/scala/collection/Iterator.scala" :: Nil + + checkFiles(sources) { packages => + // success if typer throws no errors! :) + } + } } diff --git a/src/dotty/tools/dotc/core/Comments.scala b/src/dotty/tools/dotc/core/Comments.scala index bf1f2553702f..0a57d331ab7d 100644 --- a/src/dotty/tools/dotc/core/Comments.scala +++ b/src/dotty/tools/dotc/core/Comments.scala @@ -8,7 +8,7 @@ import Symbols._ import Contexts.Context import Flags.EmptyFlags import dotc.util.SourceFile -import dotc.util.Positions.Position +import dotc.util.Positions._ import dotc.parsing.Parsers.Parser import dotty.tools.dottydoc.model.comment.CommentUtils._ @@ -38,14 +38,22 @@ object Comments { private def decomposeUseCase(start: Int, end: Int): UseCase = { val codeStart = skipWhitespace(raw, start + "@usecase".length) val codeEnd = skipToEol(raw, codeStart) - val code = raw.substring(codeStart, codeEnd) - val codePos = Position(codeStart, codeEnd) + val code = raw.substring(codeStart, codeEnd) + " = ???" + val codePos = subPos(codeStart, codeEnd) val commentStart = skipLineLead(raw, codeEnd + 1) min end val comment = "/** " + raw.substring(commentStart, end) + "*/" - val commentPos = Position(commentStart, end) + val commentPos = subPos(commentStart, end) - UseCase(Comment(commentPos, comment), code + " = ???", codePos) + UseCase(Comment(commentPos, comment), code, codePos) } + + private 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 + } } case class UseCase(comment: Comment, code: String, codePos: Position)(implicit ctx: Context) { @@ -56,9 +64,11 @@ object Comments { val tree = new Parser(new SourceFile("", code)).localDef(codePos.start, EmptyFlags) tree match { - case tree: untpd.DefDef => tree + case tree: untpd.DefDef => + val newName = (tree.name.show + "$" + codePos.start).toTermName + untpd.DefDef(newName, tree.tparams, tree.vparamss, tree.tpt, tree.rhs) case _ => - ctx.error("proper def was not found in `@usecase`", codePos) + ctx.error("proper definition was not found in `@usecase`", codePos) tree } } @@ -67,11 +77,14 @@ object Comments { var tpdCode: tpd.DefDef = _ def typeTree()(implicit ctx: Context): Unit = untpdCode match { - case df: untpd.DefDef => ctx.typer.typedDefDef(df, symbol) match { - case tree: tpd.DefDef => tpdCode = tree - case _ => ctx.error("proper def was not found in `@usecase`", codePos) - } - case _ => ctx.error("proper def was not found in `@usecase`", codePos) + case df: untpd.DefDef => + ctx.typer.typedDefDef(df, symbol) match { + case tree: tpd.DefDef => tpdCode = tree + case _ => + ctx.error("proper def could not be typed from `@usecase`", codePos) + } + case _ => + ctx.error("proper def was not found in `@usecase`", codePos) } } } From 32c6bb7c6720b84814ff0fe9f4929aad496c00f3 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 25 Aug 2016 16:27:06 +0200 Subject: [PATCH 06/51] Fix type parameters not getting properly typed --- dottydoc/test/UsecaseTest.scala | 89 ++++++++++++++++++++++++ src/dotty/tools/dotc/core/Comments.scala | 17 +---- src/dotty/tools/dotc/typer/Typer.scala | 19 +++-- 3 files changed, 107 insertions(+), 18 deletions(-) diff --git a/dottydoc/test/UsecaseTest.scala b/dottydoc/test/UsecaseTest.scala index d5f3388929da..9be6882973f4 100644 --- a/dottydoc/test/UsecaseTest.scala +++ b/dottydoc/test/UsecaseTest.scala @@ -52,6 +52,95 @@ class UsecaseTest extends DottyTest { } } + @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 checkIterator = { val sources = "./scala-scala/src/library/scala/collection/Iterator.scala" :: Nil diff --git a/src/dotty/tools/dotc/core/Comments.scala b/src/dotty/tools/dotc/core/Comments.scala index 0a57d331ab7d..1d9d53bac78e 100644 --- a/src/dotty/tools/dotc/core/Comments.scala +++ b/src/dotty/tools/dotc/core/Comments.scala @@ -60,6 +60,9 @@ object Comments { /** Entered by Namer */ var symbol: Symbol = _ + /** Set by typer */ + var tpdCode: tpd.DefDef = _ + lazy val untpdCode: untpd.Tree = { val tree = new Parser(new SourceFile("", code)).localDef(codePos.start, EmptyFlags) @@ -72,19 +75,5 @@ object Comments { tree } } - - /** Set by typer calling `typeTree` */ - var tpdCode: tpd.DefDef = _ - - def typeTree()(implicit ctx: Context): Unit = untpdCode match { - case df: untpd.DefDef => - ctx.typer.typedDefDef(df, symbol) match { - case tree: tpd.DefDef => tpdCode = tree - case _ => - ctx.error("proper def could not be typed from `@usecase`", codePos) - } - case _ => - ctx.error("proper def was not found in `@usecase`", codePos) - } } } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index f514abfb5032..4aad7de0e161 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1128,9 +1128,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit tpt1 = tpt1.withType(avoid(tpt1.tpe, vparamss1.flatMap(_.map(_.symbol)))) } - /** Type usecases */ - ctx.docbase.docstring(sym).map(_.usecases.map(_.typeTree())) - 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 } @@ -1455,11 +1452,25 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit buf += typed(stat)(ctx.exprContext(stat, exprOwner)) traverse(rest) case nil => - buf.toList + val tpdStats = buf.toList + typedUsecases(tpdStats.map(_.symbol), exprOwner) + tpdStats } traverse(stats) } + def typedUsecases(syms: List[Symbol], owner: Symbol)(implicit ctx: Context): Unit = + for { + sym <- syms + usecase <- ctx.docbase.docstring(sym).map(_.usecases).getOrElse(Nil) + List(tpdTree) = typedStats(usecase.untpdCode :: Nil, owner) + } yield { + if (tpdTree.isInstanceOf[tpd.DefDef]) + usecase.tpdCode = tpdTree.asInstanceOf[tpd.DefDef] + else + ctx.error("Couldn't compile `@usecase`", usecase.codePos) + } + 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? From f65e64b6563b2849117bc78782d2732eaa460ec8 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 25 Aug 2016 19:43:19 +0200 Subject: [PATCH 07/51] Move typing of usecases to `typedClassDef` --- dottydoc/test/UsecaseTest.scala | 11 ++++++----- src/dotty/tools/dotc/typer/Typer.scala | 7 ++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/dottydoc/test/UsecaseTest.scala b/dottydoc/test/UsecaseTest.scala index 9be6882973f4..2effb77fa805 100644 --- a/dottydoc/test/UsecaseTest.scala +++ b/dottydoc/test/UsecaseTest.scala @@ -141,12 +141,13 @@ class UsecaseTest extends DottyTest { } } - @Test def checkIterator = { - val sources = - "./scala-scala/src/library/scala/collection/Iterator.scala" :: Nil + @Test def checkIterator = + checkFiles("./scala-scala/src/library/scala/collection/Iterator.scala" :: Nil) { _ => + // success if typer throws no errors! :) + } - checkFiles(sources) { packages => + @Test def checkIterableLike = + checkFiles("./scala-scala/src/library/scala/collection/IterableLike.scala" :: Nil) { _ => // success if typer throws no errors! :) } - } } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 4aad7de0e161..ddef2574fe77 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1191,6 +1191,9 @@ 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)) + + typedUsecases(body1.map(_.symbol), self1.symbol) + checkNoDoubleDefs(cls) val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) .withType(dummy.nonMemberTermRef) @@ -1452,9 +1455,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit buf += typed(stat)(ctx.exprContext(stat, exprOwner)) traverse(rest) case nil => - val tpdStats = buf.toList - typedUsecases(tpdStats.map(_.symbol), exprOwner) - tpdStats + buf.toList } traverse(stats) } From eef3de6121aa284c85d9dc0d2d8b8133ec6763af Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Sat, 27 Aug 2016 00:30:07 +0200 Subject: [PATCH 08/51] Fix cooking of docstrings --- .../src/dotty/tools/dottydoc/DottyDoc.scala | 2 +- .../model/comment/CommentExpander.scala | 2 +- dottydoc/test/BaseTest.scala | 2 +- dottydoc/test/UsecaseTest.scala | 43 ++++++++++++++++++ .../tools/dotc/config/ScalaSettings.scala | 1 + src/dotty/tools/dotc/core/Comments.scala | 10 ++--- src/dotty/tools/dotc/core/Contexts.scala | 2 + .../tools/dotc/core/SymDenotations.scala | 4 +- src/dotty/tools/dotc/parsing/Scanners.scala | 2 +- src/dotty/tools/dotc/typer/Namer.scala | 11 +---- src/dotty/tools/dotc/typer/Typer.scala | 45 ++++++++++++++----- 11 files changed, 93 insertions(+), 31 deletions(-) diff --git a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala index dd0bb0ec4094..dc051279a20c 100644 --- a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala +++ b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala @@ -54,7 +54,7 @@ abstract class DocDriver extends Driver { val summary = CompilerCommand.distill(args)(ctx) ctx.setSettings(summary.sstate) - ctx.setSetting(ctx.settings.YkeepComments, true) + ctx.setSetting(ctx.settings.Ydocrun, true) val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)(ctx) (fileNames, ctx) diff --git a/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala b/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala index 7e213c2f3556..ece0d101813f 100644 --- a/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala +++ b/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala @@ -47,7 +47,7 @@ trait CommentExpander { val defines = sections filter { startsWithTag(raw, _, "@define") } val usecases = sections filter { startsWithTag(raw, _, "@usecase") } - val end = startTag(raw, (defines ::: usecases).sortBy(_._1)) + val end = startTag(raw, (defines /*::: usecases*/).sortBy(_._1)) if (end == raw.length - 2) raw else raw.substring(0, end) + "*/" } diff --git a/dottydoc/test/BaseTest.scala b/dottydoc/test/BaseTest.scala index 2233d03c8b53..1dfc83c4efd8 100644 --- a/dottydoc/test/BaseTest.scala +++ b/dottydoc/test/BaseTest.scala @@ -17,7 +17,7 @@ trait DottyTest { import base.settings._ val ctx = base.initialCtx.fresh ctx.setSetting(ctx.settings.language, List("Scala2")) - ctx.setSetting(ctx.settings.YkeepComments, true) + ctx.setSetting(ctx.settings.Ydocrun, true) base.initialize()(ctx) ctx } diff --git a/dottydoc/test/UsecaseTest.scala b/dottydoc/test/UsecaseTest.scala index 2effb77fa805..05bc8663f684 100644 --- a/dottydoc/test/UsecaseTest.scala +++ b/dottydoc/test/UsecaseTest.scala @@ -141,6 +141,49 @@ class UsecaseTest extends DottyTest { } } + @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 checkIterator = checkFiles("./scala-scala/src/library/scala/collection/Iterator.scala" :: Nil) { _ => // success if typer throws no errors! :) diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index c090a551563f..51412e20c09a 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -171,6 +171,7 @@ class ScalaSettings extends Settings.SettingGroup { val Ybuildmanagerdebug = BooleanSetting("-Ybuild-manager-debug", "Generate debug information for the Refined Build Manager compiler.") val Ycompletion = BooleanSetting("-Ycompletion-debug", "Trace all tab completion activity.") val Ydocdebug = BooleanSetting("-Ydoc-debug", "Trace all scaladoc activity.") + val Ydocrun = BooleanSetting("-Ydoc-run", "Current run should be treated as a docrun, enables `@usecase` annotations in comments") val Yidedebug = BooleanSetting("-Yide-debug", "Generate, validate and output trees using the interactive compiler.") val Yinferdebug = BooleanSetting("-Yinfer-debug", "Trace type inference and implicit search.") val Yissuedebug = BooleanSetting("-Yissue-debug", "Print stack traces when a context issues an error.") diff --git a/src/dotty/tools/dotc/core/Comments.scala b/src/dotty/tools/dotc/core/Comments.scala index 1d9d53bac78e..ea5726fa03b7 100644 --- a/src/dotty/tools/dotc/core/Comments.scala +++ b/src/dotty/tools/dotc/core/Comments.scala @@ -15,6 +15,7 @@ import dotty.tools.dottydoc.model.comment.CommentUtils._ object Comments { case class Comment(pos: Position, raw: String)(implicit ctx: Context) { + val isDocComment = raw.startsWith("/**") private[this] lazy val sections = tagIndex(raw) @@ -41,10 +42,10 @@ object Comments { val code = raw.substring(codeStart, codeEnd) + " = ???" val codePos = subPos(codeStart, codeEnd) val commentStart = skipLineLead(raw, codeEnd + 1) min end - val comment = "/** " + raw.substring(commentStart, end) + "*/" + val commentStr = "/** " + raw.substring(commentStart, end) + "*/" val commentPos = subPos(commentStart, end) - UseCase(Comment(commentPos, comment), code, codePos) + UseCase(Comment(commentPos, commentStr), code, codePos) } private def subPos(start: Int, end: Int) = @@ -57,9 +58,6 @@ object Comments { } case class UseCase(comment: Comment, code: String, codePos: Position)(implicit ctx: Context) { - /** Entered by Namer */ - var symbol: Symbol = _ - /** Set by typer */ var tpdCode: tpd.DefDef = _ @@ -68,7 +66,7 @@ object Comments { tree match { case tree: untpd.DefDef => - val newName = (tree.name.show + "$" + codePos.start).toTermName + 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) diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index 9b130851d037..305ac4d8d3c1 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -577,6 +577,8 @@ object Contexts { private[this] val _docstrings: mutable.Map[Symbol, Comment] = mutable.Map.empty + def docstrings: Map[Symbol, Comment] = _docstrings.toMap + def docstring(sym: Symbol): Option[Comment] = _docstrings.get(sym) def addDocstring(sym: Symbol, doc: Option[Comment]): Unit = diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 47ec541ab84c..539f3ba73620 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)) + lazy val isUsecase = ctx.settings.Ydocrun.value && 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/parsing/Scanners.scala b/src/dotty/tools/dotc/parsing/Scanners.scala index 41ed7f0ce33e..2bb7bbfb1cf4 100644 --- a/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/src/dotty/tools/dotc/parsing/Scanners.scala @@ -171,7 +171,7 @@ object Scanners { } class Scanner(source: SourceFile, override val startFrom: Offset = 0)(implicit ctx: Context) extends ScannerCommon(source)(ctx) { - val keepComments = ctx.settings.YkeepComments.value + val keepComments = ctx.settings.YkeepComments.value || ctx.settings.Ydocrun.value /** All doc comments as encountered, each list contains doc comments from * the same block level. Starting with the deepest level and going upward diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 1be15857b191..13d881790d80 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -4,7 +4,7 @@ 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._ @@ -454,14 +454,7 @@ class Namer { typer: Typer => } def setDocstring(sym: Symbol, tree: Tree)(implicit ctx: Context) = tree match { - case t: MemberDef if t.rawComment.isDefined => - val cmt = t.rawComment - ctx.docbase.addDocstring(sym, cmt) - - cmt.get.usecases.foreach { usecase => - usecase.symbol = enterSymbol(createSymbol(usecase.untpdCode)) - } - + case t: MemberDef if t.rawComment.isDefined => ctx.docbase.addDocstring(sym, t.rawComment) case _ => () } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index ddef2574fe77..fd397edbd97d 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._ @@ -1192,7 +1193,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val dummy = localDummy(cls, impl) val body1 = typedStats(impl.body, dummy)(inClassContext(self1.symbol)) - typedUsecases(body1.map(_.symbol), self1.symbol) + if (ctx.settings.Ydocrun.value) + typedUsecases(body1.map(_.symbol), self1.symbol) checkNoDoubleDefs(cls) val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) @@ -1460,16 +1462,37 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit traverse(stats) } - def typedUsecases(syms: List[Symbol], owner: Symbol)(implicit ctx: Context): Unit = - for { - sym <- syms - usecase <- ctx.docbase.docstring(sym).map(_.usecases).getOrElse(Nil) - List(tpdTree) = typedStats(usecase.untpdCode :: Nil, owner) - } yield { - if (tpdTree.isInstanceOf[tpd.DefDef]) - usecase.tpdCode = tpdTree.asInstanceOf[tpd.DefDef] - else - ctx.error("Couldn't compile `@usecase`", usecase.codePos) + def typedUsecases(syms: List[Symbol], owner: Symbol)(implicit ctx: Context): Unit = { + val relevantSyms = syms.filter(ctx.docbase.docstring(_).isDefined) + relevantSyms.foreach { sym => + expandParentDocs(sym) + val usecases = ctx.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) + } + } + } + } + + import dotty.tools.dottydoc.model.comment.CommentExpander + val expander = new CommentExpander {} + def expandParentDocs(sym: Symbol)(implicit ctx: Context): Unit = + ctx.docbase.docstring(sym).foreach { cmt => + def expandDoc(owner: Symbol): Unit = { + expander.defineVariables(sym) + val newCmt = Comment(cmt.pos, expander.expandedDocComment(sym, owner, cmt.raw)) + ctx.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 = From a95777042f29fe3bfe41c0fb6cf24951f09e6679 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Sat, 27 Aug 2016 15:37:11 +0200 Subject: [PATCH 09/51] Move docstring parser to dottydoc miniphase --- .../src/dotty/tools/dottydoc/DottyDoc.scala | 1 + .../tools/dottydoc/core/DocASTPhase.scala | 62 ++++-------- .../tools/dottydoc/core/DocstringPhase.scala | 50 ++++++++++ .../dotty/tools/dottydoc/model/parsers.scala | 98 ------------------- 4 files changed, 70 insertions(+), 141 deletions(-) create mode 100644 dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala delete mode 100644 dottydoc/src/dotty/tools/dottydoc/model/parsers.scala diff --git a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala index dc051279a20c..380d8fda2bb9 100644 --- a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala +++ b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala @@ -32,6 +32,7 @@ class DocCompiler extends Compiler { List(new DocImplicitsPhase), List(new DocASTPhase), List(DocMiniTransformations(new UsecasePhase, + new DocstringPhase, new LinkReturnTypes, new LinkParamListTypes, new LinkImplicitlyAddedTypes, diff --git a/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala index 8df00d894174..617afec514fe 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala @@ -14,7 +14,6 @@ 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._ @@ -23,20 +22,8 @@ class DocASTPhase extends Phase { 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,30 +45,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, - 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, - 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 @@ -177,14 +160,7 @@ 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 + // (3) Update Doc AST in ctx.base for (kv <- packages) ctx.docbase.packages += kv // Return super's result 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..830336ba18c6 --- /dev/null +++ b/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala @@ -0,0 +1,50 @@ +package dotty.tools +package dottydoc +package core + +import dotc.core.Contexts.Context +import dotc.ast.tpd + +import transform.DocMiniPhase +import model._ +import model.internal._ +import model.factories._ +import model.comment._ +import dotty.tools.dotc.core.Symbols.Symbol +import BodyParsers._ + +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[Package].toMap, 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/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 - } -} From e1745ec69520b1f71561ba992353ea76a4b387c0 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Sat, 27 Aug 2016 15:41:32 +0200 Subject: [PATCH 10/51] Move docstring cooking to dotty --- .../tools/dottydoc/core/DocstringPhase.scala | 4 - .../tools/dottydoc/core/UsecasePhase.scala | 20 +- .../model/comment/CommentExpander.scala | 344 ---------------- dottydoc/test/UsecaseTest.scala | 37 ++ src/dotty/tools/dotc/core/Comments.scala | 374 +++++++++++++++++- src/dotty/tools/dotc/core/Contexts.scala | 2 + src/dotty/tools/dotc/typer/Typer.scala | 17 +- .../tools/dotc/util/CommentParsing.scala | 24 +- 8 files changed, 437 insertions(+), 385 deletions(-) delete mode 100644 dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala rename dottydoc/src/dotty/tools/dottydoc/model/comment/CommentUtils.scala => src/dotty/tools/dotc/util/CommentParsing.scala (94%) diff --git a/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala index 830336ba18c6..93d51503fbc0 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala @@ -3,14 +3,10 @@ package dottydoc package core import dotc.core.Contexts.Context -import dotc.ast.tpd - import transform.DocMiniPhase import model._ import model.internal._ -import model.factories._ import model.comment._ -import dotty.tools.dotc.core.Symbols.Symbol import BodyParsers._ class DocstringPhase extends DocMiniPhase with CommentParser with CommentCleaner { diff --git a/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala index 4d9c0abbdd48..8e9e1fd57ae3 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala @@ -11,14 +11,18 @@ import model.factories._ import dotty.tools.dotc.core.Symbols.Symbol class UsecasePhase extends DocMiniPhase { - private def defdefToDef(d: tpd.DefDef, sym: Symbol)(implicit ctx: Context) = DefImpl( - sym, - d.name.show.split("\\$").head, // UseCase defs get $pos appended to their names - flags(d), path(d.symbol), - returnType(d.tpt.tpe), - typeParams(d.symbol), - paramLists(d.symbol.info) - ) + 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 ece0d101813f..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.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.docbase.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.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/test/UsecaseTest.scala b/dottydoc/test/UsecaseTest.scala index 05bc8663f684..c7b398d1f6f4 100644 --- a/dottydoc/test/UsecaseTest.scala +++ b/dottydoc/test/UsecaseTest.scala @@ -31,6 +31,8 @@ class UsecaseTest extends DottyTest { 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 _ => @@ -184,6 +186,41 @@ class UsecaseTest extends DottyTest { } } + @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! :) diff --git a/src/dotty/tools/dotc/core/Comments.scala b/src/dotty/tools/dotc/core/Comments.scala index ea5726fa03b7..3e562baff1b2 100644 --- a/src/dotty/tools/dotc/core/Comments.scala +++ b/src/dotty/tools/dotc/core/Comments.scala @@ -9,24 +9,38 @@ import Contexts.Context import Flags.EmptyFlags import dotc.util.SourceFile import dotc.util.Positions._ +import dotc.util.CommentParsing._ import dotc.parsing.Parsers.Parser -import dotty.tools.dottydoc.model.comment.CommentUtils._ object Comments { - case class Comment(pos: Position, raw: String)(implicit ctx: Context) { + abstract case class Comment(pos: Position, raw: String)(implicit ctx: Context) { self => + def isExpanded: Boolean + + def usecases: List[UseCase] val isDocComment = raw.startsWith("/**") - private[this] lazy val sections = tagIndex(raw) + def expand(f: String => String): Comment = new Comment(pos, f(raw)) { + val isExpanded = true + val usecases = self.usecases + } - private def fold[A](z: A)(op: => A) = if (!isDocComment) z else op + def withUsecases: Comment = new Comment(pos, stripUsecases) { + val isExpanded = self.isExpanded + val usecases = parseUsecases + } - lazy val usecases = fold(List.empty[UseCase]) { - sections + 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: * {{{ @@ -36,7 +50,15 @@ object Comments { * def foo: A = ??? * }}} */ - private def decomposeUseCase(start: Int, end: Int): UseCase = { + 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) + " = ???" @@ -47,13 +69,13 @@ object Comments { UseCase(Comment(commentPos, commentStr), code, codePos) } + } - private 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 + 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 } } @@ -74,4 +96,328 @@ object Comments { } } } + + /** + * 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.docbase.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.docbase.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.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/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index 305ac4d8d3c1..f99e5b7937d3 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -577,6 +577,8 @@ object Contexts { 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) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index fd397edbd97d..dad4f3552073 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1462,7 +1462,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit traverse(stats) } - def typedUsecases(syms: List[Symbol], owner: Symbol)(implicit ctx: Context): Unit = { + private def typedUsecases(syms: List[Symbol], owner: Symbol)(implicit ctx: Context): Unit = { val relevantSyms = syms.filter(ctx.docbase.docstring(_).isDefined) relevantSyms.foreach { sym => expandParentDocs(sym) @@ -1479,13 +1479,16 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } - import dotty.tools.dottydoc.model.comment.CommentExpander - val expander = new CommentExpander {} - def expandParentDocs(sym: Symbol)(implicit ctx: Context): Unit = + private def expandParentDocs(sym: Symbol)(implicit ctx: Context): Unit = ctx.docbase.docstring(sym).foreach { cmt => - def expandDoc(owner: Symbol): Unit = { - expander.defineVariables(sym) - val newCmt = Comment(cmt.pos, expander.expandedDocComment(sym, owner, cmt.raw)) + def expandDoc(owner: Symbol): Unit = if (!cmt.isExpanded) { + val tplExp = ctx.docbase.templateExpander + tplExp.defineVariables(sym) + + val newCmt = cmt + .expand(tplExp.expandedDocComment(sym, owner, _)) + .withUsecases + ctx.docbase.addDocstring(sym, Some(newCmt)) } 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) + "*/" + } } From 10a397024642e4f6ed702801b543fc011f260a37 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Sun, 4 Sep 2016 19:27:09 +0200 Subject: [PATCH 11/51] Typecheck usecases in fresh local scope --- dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala | 2 +- dottydoc/test/BaseTest.scala | 2 +- src/dotty/tools/dotc/config/ScalaSettings.scala | 1 - src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- src/dotty/tools/dotc/parsing/Scanners.scala | 2 +- src/dotty/tools/dotc/typer/Typer.scala | 3 +-- 6 files changed, 5 insertions(+), 7 deletions(-) diff --git a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala index 380d8fda2bb9..77ceb179d1f2 100644 --- a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala +++ b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala @@ -55,7 +55,7 @@ abstract class DocDriver extends Driver { val summary = CompilerCommand.distill(args)(ctx) ctx.setSettings(summary.sstate) - ctx.setSetting(ctx.settings.Ydocrun, true) + ctx.setSetting(ctx.settings.YkeepComments, true) val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)(ctx) (fileNames, ctx) diff --git a/dottydoc/test/BaseTest.scala b/dottydoc/test/BaseTest.scala index 1dfc83c4efd8..2233d03c8b53 100644 --- a/dottydoc/test/BaseTest.scala +++ b/dottydoc/test/BaseTest.scala @@ -17,7 +17,7 @@ trait DottyTest { import base.settings._ val ctx = base.initialCtx.fresh ctx.setSetting(ctx.settings.language, List("Scala2")) - ctx.setSetting(ctx.settings.Ydocrun, true) + ctx.setSetting(ctx.settings.YkeepComments, true) base.initialize()(ctx) ctx } diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index 51412e20c09a..c090a551563f 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -171,7 +171,6 @@ class ScalaSettings extends Settings.SettingGroup { val Ybuildmanagerdebug = BooleanSetting("-Ybuild-manager-debug", "Generate debug information for the Refined Build Manager compiler.") val Ycompletion = BooleanSetting("-Ycompletion-debug", "Trace all tab completion activity.") val Ydocdebug = BooleanSetting("-Ydoc-debug", "Trace all scaladoc activity.") - val Ydocrun = BooleanSetting("-Ydoc-run", "Current run should be treated as a docrun, enables `@usecase` annotations in comments") val Yidedebug = BooleanSetting("-Yide-debug", "Generate, validate and output trees using the interactive compiler.") val Yinferdebug = BooleanSetting("-Yinfer-debug", "Trace type inference and implicit search.") val Yissuedebug = BooleanSetting("-Yissue-debug", "Print stack traces when a context issues an error.") diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 539f3ba73620..c76e06ac352a 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1515,7 +1515,7 @@ object SymDenotations { /** Enter a symbol in given `scope` without potentially replacing the old copy. */ def enterNoReplace(sym: Symbol, scope: MutableScope)(implicit ctx: Context): Unit = { - lazy val isUsecase = ctx.settings.Ydocrun.value && sym.name.show.takeRight(4) == "$doc" + def isUsecase = 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) diff --git a/src/dotty/tools/dotc/parsing/Scanners.scala b/src/dotty/tools/dotc/parsing/Scanners.scala index 2bb7bbfb1cf4..41ed7f0ce33e 100644 --- a/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/src/dotty/tools/dotc/parsing/Scanners.scala @@ -171,7 +171,7 @@ object Scanners { } class Scanner(source: SourceFile, override val startFrom: Offset = 0)(implicit ctx: Context) extends ScannerCommon(source)(ctx) { - val keepComments = ctx.settings.YkeepComments.value || ctx.settings.Ydocrun.value + val keepComments = ctx.settings.YkeepComments.value /** All doc comments as encountered, each list contains doc comments from * the same block level. Starting with the deepest level and going upward diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index dad4f3552073..ba31eaf3fece 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1193,8 +1193,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val dummy = localDummy(cls, impl) val body1 = typedStats(impl.body, dummy)(inClassContext(self1.symbol)) - if (ctx.settings.Ydocrun.value) - typedUsecases(body1.map(_.symbol), self1.symbol) + typedUsecases(body1.map(_.symbol), self1.symbol)(localContext(cdef, cls).setNewScope) checkNoDoubleDefs(cls) val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) From 28edec93ba0cd38c7db7ee05f344d82239f156cc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Sep 2016 15:59:52 +0200 Subject: [PATCH 12/51] Make Context#moreProperties strongly typed To do this, factor out Key from Attachment into a new type, Property.Key. --- src/dotty/tools/dotc/ast/Desugar.scala | 4 +- src/dotty/tools/dotc/ast/Trees.scala | 6 +- src/dotty/tools/dotc/ast/untpd.scala | 10 +- src/dotty/tools/dotc/core/Contexts.scala | 16 +- .../tools/dotc/transform/ExplicitOuter.scala | 4 +- src/dotty/tools/dotc/typer/Inliner.scala | 200 ++++++++++++++++++ src/dotty/tools/dotc/typer/Namer.scala | 8 +- src/dotty/tools/dotc/util/Attachment.scala | 4 +- src/dotty/tools/dotc/util/Property.scala | 10 + 9 files changed, 238 insertions(+), 24 deletions(-) create mode 100644 src/dotty/tools/dotc/typer/Inliner.scala create mode 100644 src/dotty/tools/dotc/util/Property.scala diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 500b28233973..6dbf5b2186e6 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,7 +21,7 @@ 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) diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index e8b4fd994946..e4323e7ad9b0 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -12,7 +12,7 @@ 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 @@ -29,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..78d55c0f414d 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 @@ -92,17 +92,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/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index f99e5b7937d3..6e7cd64fcb8a 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -30,6 +30,8 @@ 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 { @@ -177,9 +179,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 +464,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)) 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/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala new file mode 100644 index 000000000000..4eb8588a68df --- /dev/null +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -0,0 +1,200 @@ +package dotty.tools +package dotc +package typer + +import dotty.tools.dotc.ast.Trees.NamedArg +import dotty.tools.dotc.ast.{Trees, untpd, tpd, TreeTypeMap} +import Trees._ +import core._ +import Flags._ +import Symbols._ +import Types._ +import Decorators._ +import StdNames.nme +import Contexts.Context +import Names.Name +import SymDenotations.SymDenotation +import Annotations.Annotation +import transform.ExplicitOuter +import config.Printers.inlining +import ErrorReporting.errorTree +import util.Property +import collection.mutable + +object Inliner { + import tpd._ + + private class InlinedBody(tree: => Tree) { + lazy val body = tree + } + + private val InlinedBody = new Property.Key[InlinedBody] // to be used as attachment + + def attachBody(inlineAnnot: Annotation, tree: => Tree)(implicit ctx: Context): Unit = + inlineAnnot.tree.putAttachment(InlinedBody, new InlinedBody(tree)) + + def inlinedBody(sym: SymDenotation)(implicit ctx: Context): Tree = + sym.getAnnotation(defn.InlineAnnot).get.tree + .attachment(InlinedBody).body + + private class Typer extends ReTyper { + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { + val acc = tree.symbol + super.typedSelect(tree, pt) match { + case res @ Select(qual, name) => + if (name.endsWith(nme.OUTER)) { + val outerAcc = tree.symbol + println(i"selecting $tree / ${acc} / ${qual.tpe.normalizedPrefix}") + res.withType(qual.tpe.widen.normalizedPrefix) + } + else { + ensureAccessible(res.tpe, qual.isInstanceOf[Super], tree.pos) + res + } + case res => res + } + } + } + + def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { + if (ctx.inlineCount < ctx.settings.xmaxInlines.value) { + ctx.inlineCount += 1 + val rhs = inlinedBody(tree.symbol) + val inlined = new Inliner(tree, rhs).inlined + try new Typer().typedUnadapted(inlined, pt) + finally ctx.inlineCount -= 1 + } else errorTree(tree, + i"""Maximal number of successive inlines (${ctx.settings.xmaxInlines.value}) exceeded, + | Maybe this is caused by a recursive inline method? + | You can use -Xmax:inlines to change the limit.""") + } +} + +class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { + import tpd._ + + private val meth = call.symbol + + private def decomposeCall(tree: Tree): (Tree, List[Tree], List[List[Tree]]) = tree match { + case Apply(fn, args) => + val (meth, targs, argss) = decomposeCall(fn) + (meth, targs, argss :+ args) + case TypeApply(fn, targs) => + val (meth, Nil, Nil) = decomposeCall(fn) + (meth, targs, Nil) + case _ => + (tree, Nil, Nil) + } + + private val (methPart, targs, argss) = decomposeCall(call) + + private lazy val prefix = methPart match { + case Select(qual, _) => qual + case _ => tpd.This(ctx.owner.enclosingClass.asClass) + } + + private val replacement = new mutable.HashMap[Type, NamedType] + + private val paramBindings = paramBindingsOf(meth.info, targs, argss) + + private def paramBindingsOf(tp: Type, targs: List[Tree], argss: List[List[Tree]]): List[MemberDef] = tp match { + case tp: PolyType => + val bindings = + (tp.paramNames, targs).zipped.map { (name, arg) => + val tparam = newSym(name, EmptyFlags, TypeAlias(arg.tpe.stripTypeVar)).asType + TypeDef(tparam) + } + bindings ::: paramBindingsOf(tp.resultType, Nil, argss) + case tp: MethodType => + val bindings = + (tp.paramNames, tp.paramTypes, argss.head).zipped.map { (name, paramtp, arg) => + def isByName = paramtp.dealias.isInstanceOf[ExprType] + val (paramFlags, paramType) = + if (isByName) (Method, ExprType(arg.tpe)) else (EmptyFlags, arg.tpe) + val vparam = newSym(name, paramFlags, paramType).asTerm + if (isByName) DefDef(vparam, arg) else ValDef(vparam, arg) + } + bindings ::: paramBindingsOf(tp.resultType, targs, argss.tail) + case _ => + assert(targs.isEmpty) + assert(argss.isEmpty) + Nil + } + + private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = + ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) + + private def registerType(tpe: Type): Unit = + if (!replacement.contains(tpe)) tpe match { + case tpe: ThisType => + if (!ctx.owner.isContainedIn(tpe.cls) && !tpe.cls.is(Package)) + if (tpe.cls.isStaticOwner) + replacement(tpe) = tpe.cls.sourceModule.termRef + else { + def outerDistance(cls: Symbol): Int = { + assert(cls.exists, i"not encl: ${meth.owner.enclosingClass} ${tpe.cls}") + if (tpe.cls eq cls) 0 + else outerDistance(cls.owner.enclosingClass) + 1 + } + val n = outerDistance(meth.owner) + replacement(tpe) = newSym(nme.SELF ++ n.toString, EmptyFlags, tpe.widen).termRef + } + case tpe: NamedType if tpe.symbol.is(Param) && tpe.symbol.owner == meth => + val Some(binding) = paramBindings.find(_.name == tpe.name) + replacement(tpe) = + if (tpe.name.isTypeName) binding.symbol.typeRef else binding.symbol.termRef + case _ => + } + + private def registerLeaf(tree: Tree): Unit = tree match { + case _: This | _: Ident => registerType(tree.tpe) + case _ => + } + + private def outerLevel(sym: Symbol) = sym.name.drop(nme.SELF.length).toString.toInt + + val inlined = { + rhs.foreachSubTree(registerLeaf) + + val accessedSelfSyms = + (for ((tp: ThisType, ref) <- replacement) yield ref.symbol.asTerm).toSeq.sortBy(outerLevel) + + val outerBindings = new mutable.ListBuffer[MemberDef] + for (selfSym <- accessedSelfSyms) { + val rhs = + if (outerBindings.isEmpty) prefix + else { + val lastSelf = outerBindings.last.symbol + val outerDelta = outerLevel(selfSym) - outerLevel(lastSelf) + def outerSelect(ref: Tree, dummy: Int): Tree = ??? + //ref.select(ExplicitOuter.outerAccessorTBD(ref.tpe.widen.classSymbol.asClass)) + (ref(lastSelf) /: (0 until outerDelta))(outerSelect) + } + outerBindings += ValDef(selfSym, rhs.ensureConforms(selfSym.info)) + } + outerBindings ++= paramBindings + + val typeMap = new TypeMap { + def apply(t: Type) = t match { + case _: SingletonType => replacement.getOrElse(t, t) + case _ => mapOver(t) + } + } + + def treeMap(tree: Tree) = tree match { + case _: This | _: Ident => + replacement.get(tree.tpe) match { + case Some(t) => ref(t) + case None => tree + } + case _ => tree + } + + val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil) + + val result = inliner(Block(outerBindings.toList, rhs)).withPos(call.pos) + + inlining.println(i"inlining $call\n --> \n$result") + result + } +} diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 13d881790d80..1c9820edb0e2 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -9,7 +9,7 @@ import Contexts._, Symbols._, Types._, SymDenotations._, Names._, NameOps._, Fla 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. 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/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 From e4e19e0a66be3de72b29716b0ef02d923903432f Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 8 Sep 2016 20:25:51 +0200 Subject: [PATCH 13/51] Implement docbase as property --- .../src/dotty/tools/dottydoc/DottyDoc.scala | 4 +- .../tools/dottydoc/core/DocASTPhase.scala | 5 +- .../dottydoc/core/DocImplicitsPhase.scala | 1 + .../tools/dottydoc/core/DocstringPhase.scala | 3 +- .../dottydoc/core/MiniPhaseTransform.scala | 9 +- .../dottydoc/core/TypeLinkingPhases.scala | 13 +- .../tools/dottydoc/core/UsecasePhase.scala | 1 + .../dotty/tools/dottydoc/util/syntax.scala | 21 ++ dottydoc/test/BaseTest.scala | 6 +- dottydoc/test/UsecaseTest.scala | 1 + src/dotty/tools/dotc/core/Comments.scala | 6 +- src/dotty/tools/dotc/core/Contexts.scala | 10 +- .../tools/dotc/core/SymDenotations.scala | 2 +- src/dotty/tools/dotc/typer/Inliner.scala | 200 ------------------ src/dotty/tools/dotc/typer/Namer.scala | 3 +- src/dotty/tools/dotc/typer/Typer.scala | 52 ++--- 16 files changed, 87 insertions(+), 250 deletions(-) create mode 100644 dottydoc/src/dotty/tools/dottydoc/util/syntax.scala delete mode 100644 src/dotty/tools/dotc/typer/Inliner.scala diff --git a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala index 77ceb179d1f2..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 @@ -56,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) @@ -67,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 617afec514fe..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 } @@ -17,6 +17,7 @@ class DocASTPhase extends Phase { 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._ @@ -161,7 +162,7 @@ class DocASTPhase extends Phase { } setParent(child, to = parent) // (3) Update Doc AST in ctx.base - for (kv <- packages) ctx.docbase.packages += kv + 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 index 93d51503fbc0..cff614528644 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala @@ -8,11 +8,12 @@ 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[Package].toMap, clean(cmt.raw), cmt.raw, cmt.pos) + parse(ent, ctx.docbase.packages, clean(cmt.raw), cmt.raw, cmt.pos) .toComment(_.toHtml(ent)) } diff --git a/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala b/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala index fc0b40955b1c..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) } @@ -85,7 +86,7 @@ object transform { ) // Update reference in context to newPackage - ctx.docbase.packages[Package] += (newPackage.path.mkString(".") -> newPackage) + ctx.docbase.packagesMutable += (newPackage.path.mkString(".") -> newPackage) newPackage } 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 index 8e9e1fd57ae3..47376eb264bb 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala @@ -9,6 +9,7 @@ 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) = { 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/UsecaseTest.scala b/dottydoc/test/UsecaseTest.scala index c7b398d1f6f4..b5d47e4b6ce8 100644 --- a/dottydoc/test/UsecaseTest.scala +++ b/dottydoc/test/UsecaseTest.scala @@ -8,6 +8,7 @@ import dotc.util.SourceFile import model._ import model.internal._ import model.references._ +import util.syntax._ class UsecaseTest extends DottyTest { @Test def simpleUsecase = { diff --git a/src/dotty/tools/dotc/core/Comments.scala b/src/dotty/tools/dotc/core/Comments.scala index 3e562baff1b2..4f36b3b6be7a 100644 --- a/src/dotty/tools/dotc/core/Comments.scala +++ b/src/dotty/tools/dotc/core/Comments.scala @@ -155,7 +155,7 @@ object Comments { */ 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.raw)).getOrElse("") + if (docStr.length == 0) ctx.getDocbase.flatMap(_.docstring(sym).map(c => template(c.raw))).getOrElse("") else template(docStr) ownComment = replaceInheritDocToInheritdoc(ownComment) @@ -365,7 +365,7 @@ object Comments { def defineVariables(sym: Symbol)(implicit ctx: Context) = { val Trim = "(?s)^[\\s&&[^\n\r]]*(.*?)\\s*$".r - val raw = ctx.docbase.docstring(sym).map(_.raw).getOrElse("") + val raw = ctx.getDocbase.flatMap(_.docstring(sym).map(_.raw)).getOrElse("") defs(sym) ++= defines(raw).map { str => { val start = skipWhitespace(str, "@define".length) @@ -406,7 +406,7 @@ object Comments { * 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) + 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 diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index 6e7cd64fcb8a..68b7b4e77ddf 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -30,7 +30,6 @@ 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 @@ -70,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 @@ -538,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 = _ @@ -579,6 +578,7 @@ object Contexts { } } + val DocContext = new Key[DocBase] class DocBase { private[this] val _docstrings: mutable.Map[Symbol, Comment] = mutable.Map.empty @@ -598,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/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index c76e06ac352a..abf404068616 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1515,7 +1515,7 @@ object SymDenotations { /** Enter a symbol in given `scope` without potentially replacing the old copy. */ def enterNoReplace(sym: Symbol, scope: MutableScope)(implicit ctx: Context): Unit = { - def isUsecase = sym.name.show.takeRight(4) == "$doc" + 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) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala deleted file mode 100644 index 4eb8588a68df..000000000000 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ /dev/null @@ -1,200 +0,0 @@ -package dotty.tools -package dotc -package typer - -import dotty.tools.dotc.ast.Trees.NamedArg -import dotty.tools.dotc.ast.{Trees, untpd, tpd, TreeTypeMap} -import Trees._ -import core._ -import Flags._ -import Symbols._ -import Types._ -import Decorators._ -import StdNames.nme -import Contexts.Context -import Names.Name -import SymDenotations.SymDenotation -import Annotations.Annotation -import transform.ExplicitOuter -import config.Printers.inlining -import ErrorReporting.errorTree -import util.Property -import collection.mutable - -object Inliner { - import tpd._ - - private class InlinedBody(tree: => Tree) { - lazy val body = tree - } - - private val InlinedBody = new Property.Key[InlinedBody] // to be used as attachment - - def attachBody(inlineAnnot: Annotation, tree: => Tree)(implicit ctx: Context): Unit = - inlineAnnot.tree.putAttachment(InlinedBody, new InlinedBody(tree)) - - def inlinedBody(sym: SymDenotation)(implicit ctx: Context): Tree = - sym.getAnnotation(defn.InlineAnnot).get.tree - .attachment(InlinedBody).body - - private class Typer extends ReTyper { - override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { - val acc = tree.symbol - super.typedSelect(tree, pt) match { - case res @ Select(qual, name) => - if (name.endsWith(nme.OUTER)) { - val outerAcc = tree.symbol - println(i"selecting $tree / ${acc} / ${qual.tpe.normalizedPrefix}") - res.withType(qual.tpe.widen.normalizedPrefix) - } - else { - ensureAccessible(res.tpe, qual.isInstanceOf[Super], tree.pos) - res - } - case res => res - } - } - } - - def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { - if (ctx.inlineCount < ctx.settings.xmaxInlines.value) { - ctx.inlineCount += 1 - val rhs = inlinedBody(tree.symbol) - val inlined = new Inliner(tree, rhs).inlined - try new Typer().typedUnadapted(inlined, pt) - finally ctx.inlineCount -= 1 - } else errorTree(tree, - i"""Maximal number of successive inlines (${ctx.settings.xmaxInlines.value}) exceeded, - | Maybe this is caused by a recursive inline method? - | You can use -Xmax:inlines to change the limit.""") - } -} - -class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { - import tpd._ - - private val meth = call.symbol - - private def decomposeCall(tree: Tree): (Tree, List[Tree], List[List[Tree]]) = tree match { - case Apply(fn, args) => - val (meth, targs, argss) = decomposeCall(fn) - (meth, targs, argss :+ args) - case TypeApply(fn, targs) => - val (meth, Nil, Nil) = decomposeCall(fn) - (meth, targs, Nil) - case _ => - (tree, Nil, Nil) - } - - private val (methPart, targs, argss) = decomposeCall(call) - - private lazy val prefix = methPart match { - case Select(qual, _) => qual - case _ => tpd.This(ctx.owner.enclosingClass.asClass) - } - - private val replacement = new mutable.HashMap[Type, NamedType] - - private val paramBindings = paramBindingsOf(meth.info, targs, argss) - - private def paramBindingsOf(tp: Type, targs: List[Tree], argss: List[List[Tree]]): List[MemberDef] = tp match { - case tp: PolyType => - val bindings = - (tp.paramNames, targs).zipped.map { (name, arg) => - val tparam = newSym(name, EmptyFlags, TypeAlias(arg.tpe.stripTypeVar)).asType - TypeDef(tparam) - } - bindings ::: paramBindingsOf(tp.resultType, Nil, argss) - case tp: MethodType => - val bindings = - (tp.paramNames, tp.paramTypes, argss.head).zipped.map { (name, paramtp, arg) => - def isByName = paramtp.dealias.isInstanceOf[ExprType] - val (paramFlags, paramType) = - if (isByName) (Method, ExprType(arg.tpe)) else (EmptyFlags, arg.tpe) - val vparam = newSym(name, paramFlags, paramType).asTerm - if (isByName) DefDef(vparam, arg) else ValDef(vparam, arg) - } - bindings ::: paramBindingsOf(tp.resultType, targs, argss.tail) - case _ => - assert(targs.isEmpty) - assert(argss.isEmpty) - Nil - } - - private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = - ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) - - private def registerType(tpe: Type): Unit = - if (!replacement.contains(tpe)) tpe match { - case tpe: ThisType => - if (!ctx.owner.isContainedIn(tpe.cls) && !tpe.cls.is(Package)) - if (tpe.cls.isStaticOwner) - replacement(tpe) = tpe.cls.sourceModule.termRef - else { - def outerDistance(cls: Symbol): Int = { - assert(cls.exists, i"not encl: ${meth.owner.enclosingClass} ${tpe.cls}") - if (tpe.cls eq cls) 0 - else outerDistance(cls.owner.enclosingClass) + 1 - } - val n = outerDistance(meth.owner) - replacement(tpe) = newSym(nme.SELF ++ n.toString, EmptyFlags, tpe.widen).termRef - } - case tpe: NamedType if tpe.symbol.is(Param) && tpe.symbol.owner == meth => - val Some(binding) = paramBindings.find(_.name == tpe.name) - replacement(tpe) = - if (tpe.name.isTypeName) binding.symbol.typeRef else binding.symbol.termRef - case _ => - } - - private def registerLeaf(tree: Tree): Unit = tree match { - case _: This | _: Ident => registerType(tree.tpe) - case _ => - } - - private def outerLevel(sym: Symbol) = sym.name.drop(nme.SELF.length).toString.toInt - - val inlined = { - rhs.foreachSubTree(registerLeaf) - - val accessedSelfSyms = - (for ((tp: ThisType, ref) <- replacement) yield ref.symbol.asTerm).toSeq.sortBy(outerLevel) - - val outerBindings = new mutable.ListBuffer[MemberDef] - for (selfSym <- accessedSelfSyms) { - val rhs = - if (outerBindings.isEmpty) prefix - else { - val lastSelf = outerBindings.last.symbol - val outerDelta = outerLevel(selfSym) - outerLevel(lastSelf) - def outerSelect(ref: Tree, dummy: Int): Tree = ??? - //ref.select(ExplicitOuter.outerAccessorTBD(ref.tpe.widen.classSymbol.asClass)) - (ref(lastSelf) /: (0 until outerDelta))(outerSelect) - } - outerBindings += ValDef(selfSym, rhs.ensureConforms(selfSym.info)) - } - outerBindings ++= paramBindings - - val typeMap = new TypeMap { - def apply(t: Type) = t match { - case _: SingletonType => replacement.getOrElse(t, t) - case _ => mapOver(t) - } - } - - def treeMap(tree: Tree) = tree match { - case _: This | _: Ident => - replacement.get(tree.tpe) match { - case Some(t) => ref(t) - case None => tree - } - case _ => tree - } - - val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil) - - val result = inliner(Block(outerBindings.toList, rhs)).withPos(call.pos) - - inlining.println(i"inlining $call\n --> \n$result") - result - } -} diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 1c9820edb0e2..f031dd9df1c8 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -454,7 +454,8 @@ class Namer { typer: Typer => } def setDocstring(sym: Symbol, tree: Tree)(implicit ctx: Context) = tree match { - case t: MemberDef if t.rawComment.isDefined => 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/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index ba31eaf3fece..2ae165aa3e07 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1193,7 +1193,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val dummy = localDummy(cls, impl) val body1 = typedStats(impl.body, dummy)(inClassContext(self1.symbol)) - typedUsecases(body1.map(_.symbol), self1.symbol)(localContext(cdef, cls).setNewScope) + 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) @@ -1461,39 +1462,42 @@ 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 = { - val relevantSyms = syms.filter(ctx.docbase.docstring(_).isDefined) - relevantSyms.foreach { sym => - expandParentDocs(sym) - val usecases = ctx.docbase.docstring(sym).map(_.usecases).getOrElse(Nil) + 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)) + 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) + 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.docbase.docstring(sym).foreach { cmt => - def expandDoc(owner: Symbol): Unit = if (!cmt.isExpanded) { - val tplExp = ctx.docbase.templateExpander - tplExp.defineVariables(sym) + 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 + val newCmt = cmt + .expand(tplExp.expandedDocComment(sym, owner, _)) + .withUsecases - ctx.docbase.addDocstring(sym, Some(newCmt)) - } + docbase.addDocstring(sym, Some(newCmt)) + } - if (sym ne NoSymbol) { - expandParentDocs(sym.owner) - expandDoc(sym.owner) + if (sym ne NoSymbol) { + expandParentDocs(sym.owner) + expandDoc(sym.owner) + } } } From ed3f56ef8f0f9a07ff68b2a7257a16640c45a44c Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Mon, 29 Aug 2016 15:18:46 +0200 Subject: [PATCH 14/51] Update readme: mark Exhaustivity checks & multiv.eq. as implemented --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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? From 8fa5627d83ba30ae1be29795f25c6b28d8facb67 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Thu, 1 Sep 2016 14:55:01 +0200 Subject: [PATCH 15/51] Fix #1490: type test of union types via type alias --- src/dotty/tools/dotc/transform/TypeTestsCasts.scala | 2 +- tests/run/i1490.check | 3 +++ tests/run/i1490.scala | 13 +++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/run/i1490.check create mode 100644 tests/run/i1490.scala 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/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 From 930ad0c9ab1c15e833981978ab95580227d9aa61 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Aug 2016 10:26:22 +0200 Subject: [PATCH 16/51] Implement constraint merging Not used yet, but we might use it as an alternative to typedArg invalidation later. --- src/dotty/tools/dotc/core/Constraint.scala | 3 ++ .../tools/dotc/core/OrderingConstraint.scala | 44 ++++++++++++++++++- src/dotty/tools/dotc/core/TyperState.scala | 6 +-- 3 files changed, 48 insertions(+), 5 deletions(-) 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/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/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 69c35faf50ce..4b3c1554d5cb 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -128,14 +128,14 @@ extends TyperState(r) { */ override def commit()(implicit ctx: Context) = { val targetState = ctx.typerState - assert(targetState eq previous) assert(isCommittable) - targetState.constraint = constraint + if (targetState eq previous) targetState.constraint = 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 From c681930b9eb1fb98b04101e30a5227769a4030f4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Aug 2016 16:38:37 +0200 Subject: [PATCH 17/51] Handle complex context merging cases Test case in isApplicableSafe.scala. It turns out that this requires a context merge using the new `&' operator. Sequence of actions: 1) Typecheck argument in typerstate 1. 2) Cache argument. 3) Evolve same typer state (to typecheck other arguments, say) leading to a different constraint. 4) Take typechecked argument in same state. It turns out that the merge in TyperState is needed not just for isApplicableSafe but also for (e.g. erased-lubs.scala) as well as many parts of dotty itself. --- src/dotty/tools/dotc/core/TyperState.scala | 8 +++++--- src/dotty/tools/dotc/typer/Inferencing.scala | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 4b3c1554d5cb..a7ad6824fcc6 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -96,7 +96,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) = { @@ -129,8 +130,9 @@ extends TyperState(r) { override def commit()(implicit ctx: Context) = { val targetState = ctx.typerState assert(isCommittable) - if (targetState eq previous) targetState.constraint = constraint - else 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 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) { From dc2d178554979f805c749b20f707d1e1873553ec Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Aug 2016 16:43:30 +0200 Subject: [PATCH 18/51] Handle case where expected type of a SeqLiteral has an undetermined element type. Test case is isApplicableSafe -Ycheck:first. --- src/dotty/tools/dotc/typer/Typer.scala | 6 ++- tests/pending/pos/isApplicableSafe.scala | 8 ---- tests/pos/isApplicableSafe.scala | 54 ++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 9 deletions(-) delete mode 100644 tests/pending/pos/isApplicableSafe.scala create mode 100644 tests/pos/isApplicableSafe.scala diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 2ae165aa3e07..27e2828ea21b 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -896,7 +896,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 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/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()) +} From f0241977ebd047fde78d72a284b056a36a4d85fc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Aug 2016 17:35:17 +0200 Subject: [PATCH 19/51] TyperState refactoring. Need to export just uncommittedAncestor, can hide isCommitted and parent. --- src/dotty/tools/dotc/core/TyperState.scala | 24 +++++++--------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index a7ad6824fcc6..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 @@ -110,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) @@ -121,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 @@ -140,15 +136,9 @@ extends TyperState(r) { 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 => From d4de454db73732e465fcc99db60dfa0290c36cbd Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 6 Sep 2016 18:05:14 +0200 Subject: [PATCH 20/51] Bump dottydoc version for nightly builds --- project/Build.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index eb3e41126e87..7beb03ce7557 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -93,8 +93,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 +100,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), From a95ed5524c5d733d4b7bbd433e478090ddaaae4a Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 6 Sep 2016 19:00:05 +0200 Subject: [PATCH 21/51] Get property from environment instead of from sysprops --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 7beb03ce7557..1412556a9732 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -10,7 +10,7 @@ import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ object DottyBuild extends Build { val baseVersion = "0.1" - val isNightly = sys.props.get("NIGHTLYBUILD") == Some("yes") + val isNightly = sys.env.get("NIGHTLYBUILD") == Some("yes") val jenkinsMemLimit = List("-Xmx1300m") From d224d4a371a408c8a98cf448eba11800cd549401 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Fri, 9 Sep 2016 16:56:01 +0200 Subject: [PATCH 22/51] Partially fix #1500: Implicit search breaks at a certain depth The issue fixed here was introduced by 71027f15. The added `csyms.isEmpty` condition on `case nil =>` is always true, which is clearely a bug. t1500c still fails with covariant (or contravariant) type parameters on `::`, but this seams to be a more complicated issue involving the typer. --- src/dotty/tools/dotc/typer/Implicits.scala | 9 +++---- tests/pos/t1500a.scala | 28 ++++++++++++++++++++++ tests/run/t1500b.scala | 21 ++++++++++++++++ tests/run/t1500c.scala | 19 +++++++++++++++ 4 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 tests/pos/t1500a.scala create mode 100644 tests/run/t1500b.scala create mode 100644 tests/run/t1500c.scala 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/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/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) + } +} From 0458e3ea3e095500a5a2f22a76fc024f0acd54e3 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Wed, 7 Sep 2016 09:49:55 +0200 Subject: [PATCH 23/51] Identation/spacing cleanup --- src/dotty/tools/dotc/Compiler.scala | 4 +- src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../tools/dotc/transform/PatternMatcher.scala | 150 +++++++++--------- src/dotty/tools/dotc/typer/Applications.scala | 8 +- 4 files changed, 79 insertions(+), 85 deletions(-) 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 6dbf5b2186e6..cb9a63db14f1 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -27,7 +27,7 @@ object desugar { 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 || diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 21b56959b2ac..30b94623d047 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 @@ -285,7 +283,6 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans next )) } - } } final case class Suppression(exhaustive: Boolean, unreachable: Boolean) @@ -642,7 +639,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 +661,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 +696,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 +705,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 +779,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 +845,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 +863,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 +902,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 +916,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 +1046,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 +1087,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 +1108,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 +1121,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 +1195,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 +1214,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 +1225,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 +1254,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 +1265,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 +1325,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 +1394,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 +1473,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 +1486,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 +1497,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 +1505,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 +1595,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 +1813,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 +1831,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 +1858,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 +1869,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 +1911,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 +1938,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 +1971,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/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 099105de359a..1fff14cc6117 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) From ad2b6f8ab0438a6a3cdf2e6fa6f81f01679586e3 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Wed, 7 Sep 2016 10:06:46 +0200 Subject: [PATCH 24/51] Fix #1335: Generate null checks for extractors --- src/dotty/tools/dotc/transform/PatternMatcher.scala | 10 +++++++--- tests/pos/t1335.scala | 11 +++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 tests/pos/t1335.scala diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 30b94623d047..490feb7d0fb4 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -253,9 +253,13 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer { ) } 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) + ) ) } } diff --git a/tests/pos/t1335.scala b/tests/pos/t1335.scala new file mode 100644 index 000000000000..047f7b566a70 --- /dev/null +++ b/tests/pos/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 => () + } +} From 1902f29dbee85559513785be8a1126b978572a64 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Aug 2016 15:19:10 +0200 Subject: [PATCH 25/51] Add some run tests --- .../not-representable}/t2337.scala | 2 +- tests/pending/run/t2337.check | 4 --- tests/pending/run/t3150.scala | 36 +++++++++++++------ tests/pending/run/unapply.check | 3 -- tests/{pending/run => pos-scala2}/t3050.scala | 0 tests/{pending => }/run/t298.scala | 0 tests/{pending => }/run/t3026.check | 0 tests/{pending => }/run/t3026.scala | 0 tests/{pending => }/run/t3353.check | 0 tests/{pending => }/run/t3353.scala | 0 tests/{pending => }/run/unapply.scala | 0 11 files changed, 27 insertions(+), 18 deletions(-) rename tests/{pending/run => disabled/not-representable}/t2337.scala (91%) delete mode 100644 tests/pending/run/t2337.check delete mode 100644 tests/pending/run/unapply.check rename tests/{pending/run => pos-scala2}/t3050.scala (100%) rename tests/{pending => }/run/t298.scala (100%) rename tests/{pending => }/run/t3026.check (100%) rename tests/{pending => }/run/t3026.scala (100%) rename tests/{pending => }/run/t3353.check (100%) rename tests/{pending => }/run/t3353.scala (100%) rename tests/{pending => }/run/unapply.scala (100%) 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/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/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/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/unapply.scala b/tests/run/unapply.scala similarity index 100% rename from tests/pending/run/unapply.scala rename to tests/run/unapply.scala From ffd9dbe62d3700835bedbbd51a8e5b09b0fb9dad Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Aug 2016 16:50:45 +0200 Subject: [PATCH 26/51] Fix test syntax to make it dotty compatible --- tests/run/unapply.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run/unapply.scala b/tests/run/unapply.scala index 43f02b9f3985..7b10030ba76d 100644 --- a/tests/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)})) } } From 34e5563d4ed612af87ab7ffc6dfb5a8e7cdc32d2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Aug 2016 17:23:11 +0200 Subject: [PATCH 27/51] Add missing check file. --- tests/{pending => }/run/t298.check | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{pending => }/run/t298.check (100%) 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 From b1aaac8ce041d235738d34c775e32bc4373161dc Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Thu, 15 Sep 2016 09:39:50 +0200 Subject: [PATCH 28/51] Move t1335 test from /pos to /run --- tests/{pos => run}/t1335.scala | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{pos => run}/t1335.scala (100%) diff --git a/tests/pos/t1335.scala b/tests/run/t1335.scala similarity index 100% rename from tests/pos/t1335.scala rename to tests/run/t1335.scala From d298ea3e229c8a1cc2e97a31c2f57416573ade0f Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 14 Sep 2016 11:11:24 +0200 Subject: [PATCH 29/51] Allow try expression without catch or finally, issue warning --- src/dotty/tools/dotc/parsing/Parsers.scala | 9 +++++++-- tests/pos/tryWithoutHandler.scala | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 tests/pos/tryWithoutHandler.scala diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index ede616cf9d5a..854a6ebd279a 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1010,8 +1010,13 @@ object Parsers { expr() } else EmptyTree 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/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") + } + } +} From 8c79ce5aee4ff1e1bda22b4e4c1d9ed5bf25d07c Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 14 Sep 2016 11:46:07 +0200 Subject: [PATCH 30/51] Improve error message on empty catch block --- src/dotty/tools/dotc/parsing/Parsers.scala | 9 +++++++++ tests/neg/emptyCatch.scala | 3 +++ 2 files changed, 12 insertions(+) create mode 100644 tests/neg/emptyCatch.scala diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 854a6ebd279a..ddce07c73499 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1009,6 +1009,15 @@ 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 (in.token == FINALLY) { accept(FINALLY); expr() } else { 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 +} From 4e08bb190b0434d47e4a099711d4ce7815f5e73a Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 24 Aug 2016 13:38:40 +0200 Subject: [PATCH 31/51] Fix #1468: Add type parameter support for scala.Dynamic --- src/dotty/tools/dotc/ast/TreeInfo.scala | 14 ------- src/dotty/tools/dotc/typer/Applications.scala | 19 +++++++-- src/dotty/tools/dotc/typer/Dynamic.scala | 32 +++++++++------ src/dotty/tools/dotc/typer/Typer.scala | 12 +++--- tests/{untried => }/neg/t6663.check | 0 tests/{untried => }/neg/t6663.scala | 2 +- tests/run/dynamicDynamicTests.scala | 41 +++++++++++++++++++ 7 files changed, 85 insertions(+), 35 deletions(-) rename tests/{untried => }/neg/t6663.check (100%) rename tests/{untried => }/neg/t6663.scala (90%) 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/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 1fff14cc6117..46d0489a26b9 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -593,8 +593,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic => 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 Apply(Select(qual, name), args) if !isDynamicMethod(name) => + typedDynamicApply(qual, name, None, args, pt)(tree) + case Apply(TypeApply(Select(qual, name), targs), args) if !isDynamicMethod(name) => + typedDynamicApply(qual, name, Some(targs), args, pt)(tree) case _ => handleUnexpectedFunType(tree, fun1) } @@ -679,7 +681,18 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } case _ => } - assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs) + if (typedFn.tpe eq TryDynamicCallType) { + (pt, typedFn) match { + case (_: FunProto, _)=> + tree.withType(TryDynamicCallType) + case (_, Select(qual, name)) => + typedDynamicSelect(qual, name, Some(typedArgs), pt) + case _ => + tree.withType(TryDynamicCallType) + } + } 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/Dynamic.scala b/src/dotty/tools/dotc/typer/Dynamic.scala index aeb3cca8ccfe..f5303b833005 100644 --- a/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/src/dotty/tools/dotc/typer/Dynamic.scala @@ -10,7 +10,6 @@ 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 { @@ -30,10 +29,12 @@ object Dynamic { trait Dynamic { self: Typer with Applications => /** 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)( + def typedDynamicApply(qual: untpd.Tree, name: Name, targsOpt: Option[List[untpd.Tree]], 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 @@ -47,25 +48,32 @@ trait Dynamic { self: Typer with Applications => case arg => namedArgTuple("", arg) } val args1 = if (dynName == nme.applyDynamic) args else namedArgs - typedApply(untpd.Apply(coreDynamic(qual, dynName, name), args1), pt) + typedApply(untpd.Apply(coreDynamic(qual, dynName, name, targsOpt), args1), pt) } } /** 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(qualifier: untpd.Tree, name: Name, targsOpt: Option[List[Tree]], pt: Type)(implicit ctx: Context): Tree = + typedApply(coreDynamic(qualifier, nme.selectDynamic, name, targsOpt), 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(qual: untpd.Tree, name: Name, targsOpt: Option[List[untpd.Tree]], rhs: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = + typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name, targsOpt), rhs), pt) - 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, targsOpt: Option[List[untpd.Tree]])(implicit ctx: Context): untpd.Apply = { + val select = untpd.Select(qual, dynName) + val selectWithTypes = targsOpt match { + case Some(targs) => untpd.TypeApply(select, targs) + case None => select + } + untpd.Apply(selectWithTypes, Literal(Constant(name.toString))) + } } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 27e2828ea21b..0a0670c1e588 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -319,10 +319,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit 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 + case _ if select.tpe ne TryDynamicCallType => select + case _: FunProto | AssignProto => select + case PolyProto(_,_) => select + case _ => typedDynamicSelect(tree.qualifier, tree.name, None, pt) } } @@ -521,7 +521,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case TryDynamicCallType => tree match { case Assign(Select(qual, name), rhs) if !isDynamicMethod(name) => - typedDynamicAssign(qual, name, rhs, pt) + typedDynamicAssign(qual, name, None, rhs, pt) + case Assign(TypeApply(Select(qual, name), targs), rhs) if !isDynamicMethod(name) => + typedDynamicAssign(qual, name, Some(targs), rhs, pt) case _ => reassignmentToVal } case tpe => 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/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") + } } From 0c407df655ddd97528ef5dd6b358d3d42eee3b04 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 24 Aug 2016 15:04:51 +0200 Subject: [PATCH 32/51] Fix #1470: Fix dynamic selection in presence of inaccessible members. --- src/dotty/tools/dotc/typer/TypeAssigner.scala | 5 +++-- tests/{pending => }/run/t5040.check | 0 tests/{pending => }/run/t5040.flags | 0 tests/{pending => }/run/t5040.scala | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) rename tests/{pending => }/run/t5040.check (100%) rename tests/{pending => }/run/t5040.flags (100%) rename tests/{pending => }/run/t5040.scala (71%) diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 36404a68fc65..6f401f4f8a2a 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -203,8 +203,9 @@ trait TypeAssigner { */ def selectionType(site: Type, name: Name, pos: Position)(implicit ctx: Context): Type = { val mbr = site.member(name) - if (reallyExists(mbr)) site.select(name, mbr) - else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) { + lazy val canBeDynamicMethod = site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name) + if (reallyExists(mbr) && (mbr.accessibleFrom(site).exists || !canBeDynamicMethod)) site.select(name, mbr) + else if (canBeDynamicMethod) { TryDynamicCallType } else { if (!site.isErroneous) { 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") From 7a59a786e3d31d1b1dbd20a7aeb72b3453a8fef1 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 5 Sep 2016 13:43:31 +0200 Subject: [PATCH 33/51] fixup #1470 --- src/dotty/tools/dotc/typer/TypeAssigner.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 6f401f4f8a2a..6023bfcd0abb 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 => @@ -203,9 +205,8 @@ trait TypeAssigner { */ def selectionType(site: Type, name: Name, pos: Position)(implicit ctx: Context): Type = { val mbr = site.member(name) - lazy val canBeDynamicMethod = site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name) - if (reallyExists(mbr) && (mbr.accessibleFrom(site).exists || !canBeDynamicMethod)) site.select(name, mbr) - else if (canBeDynamicMethod) { + if (reallyExists(mbr)) site.select(name, mbr) + else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) { TryDynamicCallType } else { if (!site.isErroneous) { From 39cbe090da29654d67263beddf4ae8911337925a Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 25 Aug 2016 09:57:18 +0200 Subject: [PATCH 34/51] Fix #1474: Fix applies to applyDynamic. --- src/dotty/tools/dotc/typer/Applications.scala | 16 +++++++++------- tests/{pending => }/run/applydynamic_sip.check | 0 tests/{pending => }/run/applydynamic_sip.flags | 0 tests/{pending => }/run/applydynamic_sip.scala | 1 + tests/{pending => }/run/t6353.check | 0 tests/{pending => }/run/t6353.scala | 0 6 files changed, 10 insertions(+), 7 deletions(-) rename tests/{pending => }/run/applydynamic_sip.check (100%) rename tests/{pending => }/run/applydynamic_sip.flags (100%) rename tests/{pending => }/run/applydynamic_sip.scala (98%) rename tests/{pending => }/run/t6353.check (100%) rename tests/{pending => }/run/t6353.scala (100%) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 46d0489a26b9..3125703a35ba 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -592,13 +592,15 @@ trait Applications extends Compatibility { self: Typer with Dynamic => fun1.tpe match { case ErrorType => tree.withType(ErrorType) case TryDynamicCallType => - tree match { - case Apply(Select(qual, name), args) if !isDynamicMethod(name) => - typedDynamicApply(qual, name, None, args, pt)(tree) - case Apply(TypeApply(Select(qual, name), targs), args) if !isDynamicMethod(name) => - typedDynamicApply(qual, name, Some(targs), args, pt)(tree) - case _ => - handleUnexpectedFunType(tree, fun1) + tree.fun match { + case Select(qual, name) if !isDynamicMethod(name) => + typedDynamicApply(qual, name, None, tree.args, pt)(tree) + case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) => + typedDynamicApply(qual, name, Some(targs), tree.args, pt)(tree) + case TypeApply(fun, targs) => + typedDynamicApply(fun, nme.apply, Some(targs), tree.args, pt)(tree) + case fun => + typedDynamicApply(fun, nme.apply, None, tree.args, pt)(tree) } case _ => tryEither { 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/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 From e82e655810c5f2316a7348e0051c2ff03df49bd4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 26 Aug 2016 11:20:56 +0200 Subject: [PATCH 35/51] Enable scala.Dynamic pending/untried tests. --- tests/neg/applydynamic_sip.check | 52 +++++++++++++ .../{untried => }/neg/applydynamic_sip.flags | 0 tests/neg/applydynamic_sip.scala | 36 +++++++++ tests/{untried => }/neg/t6355b.check | 4 +- tests/{untried => }/neg/t6355b.scala | 4 +- tests/{untried => }/neg/t6920.check | 2 +- tests/{untried => }/neg/t6920.scala | 2 +- tests/{untried => }/neg/t8006.check | 2 +- tests/{untried => }/neg/t8006.scala | 2 +- tests/{pending => }/run/dynamic-anyval.check | 0 tests/{pending => }/run/dynamic-anyval.scala | 0 tests/{pending => }/run/t4536.check | 0 tests/{pending => }/run/t4536.flags | 0 tests/{pending => }/run/t4536.scala | 8 +- tests/{pending => }/run/t5733.check | 0 tests/{pending => }/run/t5733.scala | 0 tests/{pending => }/run/t6355.check | 0 tests/{pending => }/run/t6355.scala | 0 tests/{pending => }/run/t6663.check | 0 tests/{pending => }/run/t6663.flags | 0 tests/{pending => }/run/t6663.scala | 0 tests/untried/neg/applydynamic_sip.check | 73 ------------------- tests/untried/neg/applydynamic_sip.scala | 33 --------- 23 files changed, 98 insertions(+), 120 deletions(-) create mode 100644 tests/neg/applydynamic_sip.check rename tests/{untried => }/neg/applydynamic_sip.flags (100%) create mode 100644 tests/neg/applydynamic_sip.scala rename tests/{untried => }/neg/t6355b.check (85%) rename tests/{untried => }/neg/t6355b.scala (83%) rename tests/{untried => }/neg/t6920.check (87%) rename tests/{untried => }/neg/t6920.scala (80%) rename tests/{untried => }/neg/t8006.check (76%) rename tests/{untried => }/neg/t8006.scala (62%) rename tests/{pending => }/run/dynamic-anyval.check (100%) rename tests/{pending => }/run/dynamic-anyval.scala (100%) rename tests/{pending => }/run/t4536.check (100%) rename tests/{pending => }/run/t4536.flags (100%) rename tests/{pending => }/run/t4536.scala (91%) rename tests/{pending => }/run/t5733.check (100%) rename tests/{pending => }/run/t5733.scala (100%) rename tests/{pending => }/run/t6355.check (100%) rename tests/{pending => }/run/t6355.scala (100%) rename tests/{pending => }/run/t6663.check (100%) rename tests/{pending => }/run/t6663.flags (100%) rename tests/{pending => }/run/t6663.scala (100%) delete mode 100644 tests/untried/neg/applydynamic_sip.check delete mode 100644 tests/untried/neg/applydynamic_sip.scala 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/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/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/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/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/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/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/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 -} From 1c9ef82408a76e59788d670dc3fcb016cd487d94 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 4 Sep 2016 13:03:08 +0200 Subject: [PATCH 36/51] Reformat code after fixes on scala.Dynamic. --- src/dotty/tools/dotc/typer/Applications.scala | 28 ++----- src/dotty/tools/dotc/typer/Dynamic.scala | 75 ++++++++++++------- src/dotty/tools/dotc/typer/Typer.scala | 17 +---- 3 files changed, 60 insertions(+), 60 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 3125703a35ba..a9212e5d6c78 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -591,17 +591,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => fun1.tpe match { case ErrorType => tree.withType(ErrorType) - case TryDynamicCallType => - tree.fun match { - case Select(qual, name) if !isDynamicMethod(name) => - typedDynamicApply(qual, name, None, tree.args, pt)(tree) - case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) => - typedDynamicApply(qual, name, Some(targs), tree.args, pt)(tree) - case TypeApply(fun, targs) => - typedDynamicApply(fun, nme.apply, Some(targs), tree.args, pt)(tree) - case fun => - typedDynamicApply(fun, nme.apply, None, tree.args, pt)(tree) - } + case TryDynamicCallType => typedDynamicApply(tree, pt) case _ => tryEither { implicit ctx => simpleApply(fun1, proto) @@ -683,18 +673,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } case _ => } - if (typedFn.tpe eq TryDynamicCallType) { - (pt, typedFn) match { - case (_: FunProto, _)=> - tree.withType(TryDynamicCallType) - case (_, Select(qual, name)) => - typedDynamicSelect(qual, name, Some(typedArgs), pt) - case _ => - tree.withType(TryDynamicCallType) - } - } else { - 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/Dynamic.scala b/src/dotty/tools/dotc/typer/Dynamic.scala index f5303b833005..b5ace87d38b4 100644 --- a/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/src/dotty/tools/dotc/typer/Dynamic.scala @@ -2,8 +2,8 @@ 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 @@ -27,6 +27,8 @@ 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, ...) @@ -34,21 +36,34 @@ trait Dynamic { self: Typer with Applications => * 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, targsOpt: Option[List[untpd.Tree]], 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, targsOpt), 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) } } @@ -59,21 +74,31 @@ trait Dynamic { self: Typer with Applications => * 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(qualifier: untpd.Tree, name: Name, targsOpt: Option[List[Tree]], pt: Type)(implicit ctx: Context): Tree = - typedApply(coreDynamic(qualifier, nme.selectDynamic, name, targsOpt), 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, targsOpt: Option[List[untpd.Tree]], rhs: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = - typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name, targsOpt), 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, targsOpt: Option[List[untpd.Tree]])(implicit ctx: Context): untpd.Apply = { + 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 = targsOpt match { - case Some(targs) => untpd.TypeApply(select, targs) - case None => select - } + 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/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 0a0670c1e588..daa65d298c43 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -318,12 +318,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 _ if select.tpe ne TryDynamicCallType => select - case _: FunProto | AssignProto => select - case PolyProto(_,_) => select - case _ => typedDynamicSelect(tree.qualifier, tree.name, None, pt) - } + 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 = { @@ -519,13 +516,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, None, rhs, pt) - case Assign(TypeApply(Select(qual, name), targs), rhs) if !isDynamicMethod(name) => - typedDynamicAssign(qual, name, Some(targs), rhs, pt) - case _ => reassignmentToVal - } + typedDynamicAssign(tree, pt) case tpe => reassignmentToVal } From 43667b4337e5733ca6f53005e576bf43ed1d1653 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 14 Sep 2016 16:47:14 +0200 Subject: [PATCH 37/51] Fix #1510: Fix error message when abstract member not implemented. --- src/dotty/tools/dotc/typer/RefChecks.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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})") } From 504b1f822d402589b4b128dde6ea8f85c75fb5b2 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Thu, 15 Sep 2016 16:59:40 +0200 Subject: [PATCH 38/51] Fix #1513: misaligned by name type parameter type bounds --- src/dotty/tools/dotc/core/Types.scala | 4 +-- src/dotty/tools/dotc/typer/TypeAssigner.scala | 32 +++++++++++------ tests/pos/t1513a.scala | 36 +++++++++++++++++++ tests/pos/t1513b.scala | 16 +++++++++ 4 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 tests/pos/t1513a.scala create mode 100644 tests/pos/t1513b.scala diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 87d94dcbe27c..cb423e186b2b 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2532,8 +2532,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/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 6023bfcd0abb..e1c9850d95d4 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -323,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) + } + + // Type parameters after naming assignment, conserving paramNames order + val normArgs: List[Type] = paramNames.zipWithIndex.map { case (pname, idx) => + namedArgMap.getOrElse(pname, nextPoly(idx)) } - val normArgs = paramNames.map(pname => argMap.getOrElse(pname, nextPoly)) + val transform = new TypeMap { def apply(t: Type) = t match { case PolyParam(`pt`, idx) => normArgs(idx) @@ -349,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) } @@ -385,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/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..881187be03a3 --- /dev/null +++ b/tests/pos/t1513b.scala @@ -0,0 +1,16 @@ +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[String, Int, Boolean] ("", 1, true) +} From 5525b0c509b70b9c7ead6cd2615f987518ca4200 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Fri, 16 Sep 2016 10:35:56 +0200 Subject: [PATCH 39/51] Fix corner case w types ALL passed by name & out of order This commit removes a problematic duplicated `checkBounds` call on `TypeApply`. To verify correctness of this change on has to check that `normalizeTree` used only once [1], and the function using `normalizeTree` already takes care of calling `checkBounds`. [1]: https://github.com/lampepfl/dotty/blob/0e8f05d88bfef95fac59f522fd9d06792126bd11/src/dotty/tools/dotc/transform/PostTyper.scala#L205 --- src/dotty/tools/dotc/transform/PostTyper.scala | 7 ++----- src/dotty/tools/dotc/typer/Checking.scala | 2 +- tests/pos/t1513b.scala | 11 ++++++++++- 3 files changed, 13 insertions(+), 7 deletions(-) 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/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/tests/pos/t1513b.scala b/tests/pos/t1513b.scala index 881187be03a3..546649383890 100644 --- a/tests/pos/t1513b.scala +++ b/tests/pos/t1513b.scala @@ -1,5 +1,9 @@ object Test { - def f[T1 <: String, T2 <: Int, T3 <: Boolean](a1: T1, a2: T2, a3: T3) = () + def f[ + T1 <: String, + T2 <: Int, + T3 <: Boolean + ](a1: T1, a2: T2, a3: T3) = () f ("", 1, true) f[T1 = String] ("", 1, true) @@ -12,5 +16,10 @@ object Test { 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) } From 3a03e24ca0f902a88d1465e299a31857e5be2c61 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Aug 2016 15:48:48 +0200 Subject: [PATCH 40/51] Fix #1457: Three incompatbilities with scalac Two of these are unavoidable. I moved the tests to diabled/not-representable and added in each case a comment to the main scala file detailing why there is a deviation. The last one (import-rewrite) is fixed. --- src/dotty/tools/dotc/typer/ImportInfo.scala | 1 + .../not-representable}/hkt/compiler.error | 0 .../not-representable}/hkt/hkt.scala | 3 +++ .../not-representable/naming-resolution/callsite.scala | 10 ++++++++++ .../not-representable/naming-resolution/compiler.error | 8 ++++++++ .../not-representable/naming-resolution/package.scala | 5 +++++ tests/pending/import-rewrite/compiler.error | 6 ------ tests/{pending => pos}/import-rewrite/file.scala | 0 tests/{pending => pos}/import-rewrite/rewrite.scala | 0 9 files changed, 27 insertions(+), 6 deletions(-) rename tests/{pending => disabled/not-representable}/hkt/compiler.error (100%) rename tests/{pending => disabled/not-representable}/hkt/hkt.scala (68%) create mode 100644 tests/disabled/not-representable/naming-resolution/callsite.scala create mode 100644 tests/disabled/not-representable/naming-resolution/compiler.error create mode 100644 tests/disabled/not-representable/naming-resolution/package.scala delete mode 100644 tests/pending/import-rewrite/compiler.error rename tests/{pending => pos}/import-rewrite/file.scala (100%) rename tests/{pending => pos}/import-rewrite/rewrite.scala (100%) 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/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/disabled/not-representable/naming-resolution/callsite.scala b/tests/disabled/not-representable/naming-resolution/callsite.scala new file mode 100644 index 000000000000..036803a26930 --- /dev/null +++ b/tests/disabled/not-representable/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/disabled/not-representable/naming-resolution/compiler.error b/tests/disabled/not-representable/naming-resolution/compiler.error new file mode 100644 index 000000000000..81d6b3cfaf91 --- /dev/null +++ b/tests/disabled/not-representable/naming-resolution/compiler.error @@ -0,0 +1,8 @@ +$ scalac tests/pending/naming-resolution/*.scala +$ ./bin/dotc tests/pending/naming-resolution/*.scala +tests/pending/naming-resolution/callsite.scala:6: error: type mismatch: + found : java.util.stream.Stream[java.nio.file.Path] + required: java.nio.file.Files + def gimmeFiles: Files = Files.list(Paths.get(".")) + ^ +one error found diff --git a/tests/disabled/not-representable/naming-resolution/package.scala b/tests/disabled/not-representable/naming-resolution/package.scala new file mode 100644 index 000000000000..f0e26ee95ad7 --- /dev/null +++ b/tests/disabled/not-representable/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/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/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 From 165685090d30cd42c99c382733ff34ce32f98190 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Aug 2016 18:29:17 +0200 Subject: [PATCH 41/51] Accommodate Scala2 name resolution scheme Scala2 does not conform to spec Section 2, where it says: Bindings of different kinds have a precedence defined on them: 1. Definitions and declarations that are local, inherited, or made available by a package clause and also defined in the same compilation unit as the reference, have highest precedence. 2. Explicit imports have next highest precedence. 3. Wildcard imports have next highest precedence. 4. Definitions made available by a package clause, but not also defined in the same compilation unit as the reference, have lowest precedence. In fact Scala 2, merges (1) and (4) into highest precedence. This commit simulates the Scala2 behavior under -language:Scala2, but gives a migration warning. For the naming-resolution test case we get: dotc *.scala -language:Scala2 -migration callsite.scala:9: migration warning: Name resolution will change. currently selected : naming.resolution.Files in the future, without -language:Scala2: java.nio.file.Files' where Files is a type in package object package which is an alias of java.util.stream.Stream[java.nio.file.Path] Files' is a class in package file def gimmeFiles: Files = Files.list(Paths.get(".")) ^ one warning found --- src/dotty/tools/dotc/typer/Typer.scala | 31 ++++++++++++++++--- .../naming-resolution/callsite.scala | 10 ------ .../naming-resolution/compiler.error | 8 ----- .../naming-resolution/package.scala | 5 --- 4 files changed, 26 insertions(+), 28 deletions(-) delete mode 100644 tests/disabled/not-representable/naming-resolution/callsite.scala delete mode 100644 tests/disabled/not-representable/naming-resolution/compiler.error delete mode 100644 tests/disabled/not-representable/naming-resolution/package.scala diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index daa65d298c43..7854c0baa904 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -73,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 = _ + def newLikeThis: Typer = new Typer /** Attribute an identifier consisting of a simple name or wildcard @@ -229,10 +236,14 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit 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) + else { + if (ctx.scala2Mode) + foundUnderScala2 = checkNewOrShadowed(found, definition) + if (defDenot.symbol is Package) + return checkNewOrShadowed(previous orElse found, packageClause) + else if (prevPrec < packageClause) + return findRef(found, packageClause, ctx)(outer) + } } } val curImport = ctx.importInfo @@ -268,10 +279,20 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val saved = importedFromRoot importedFromRoot = Set.empty - val rawType = + foundUnderScala2 = NoType + + var rawType = try findRef(NoType, BindingPrec.nothingBound, NoContext) finally importedFromRoot = saved + if (foundUnderScala2.exists && (foundUnderScala2 ne rawType)) { + ctx.migrationWarning( + ex"""Name resolution will change. + | currently selected : $foundUnderScala2 + | in the future, without -language:Scala2: $rawType""", tree.pos) + rawType = foundUnderScala2 + } + val ownType = if (rawType.exists) ensureAccessible(rawType, superAccess = false, tree.pos) diff --git a/tests/disabled/not-representable/naming-resolution/callsite.scala b/tests/disabled/not-representable/naming-resolution/callsite.scala deleted file mode 100644 index 036803a26930..000000000000 --- a/tests/disabled/not-representable/naming-resolution/callsite.scala +++ /dev/null @@ -1,10 +0,0 @@ -// 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/disabled/not-representable/naming-resolution/compiler.error b/tests/disabled/not-representable/naming-resolution/compiler.error deleted file mode 100644 index 81d6b3cfaf91..000000000000 --- a/tests/disabled/not-representable/naming-resolution/compiler.error +++ /dev/null @@ -1,8 +0,0 @@ -$ scalac tests/pending/naming-resolution/*.scala -$ ./bin/dotc tests/pending/naming-resolution/*.scala -tests/pending/naming-resolution/callsite.scala:6: error: type mismatch: - found : java.util.stream.Stream[java.nio.file.Path] - required: java.nio.file.Files - def gimmeFiles: Files = Files.list(Paths.get(".")) - ^ -one error found diff --git a/tests/disabled/not-representable/naming-resolution/package.scala b/tests/disabled/not-representable/naming-resolution/package.scala deleted file mode 100644 index f0e26ee95ad7..000000000000 --- a/tests/disabled/not-representable/naming-resolution/package.scala +++ /dev/null @@ -1,5 +0,0 @@ -package naming - -package object resolution { - type Files = java.util.stream.Stream[java.nio.file.Path] -} From 3175d4c1a1ff5c7890d17ee6776d547d763698eb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Aug 2016 18:34:03 +0200 Subject: [PATCH 42/51] Add test file. --- tests/pos-scala2/naming-resolution/callsite.scala | 10 ++++++++++ tests/pos-scala2/naming-resolution/package.scala | 5 +++++ 2 files changed, 15 insertions(+) create mode 100644 tests/pos-scala2/naming-resolution/callsite.scala create mode 100644 tests/pos-scala2/naming-resolution/package.scala 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] +} From 56bd08f66614145e04dddf966b66bb80489be272 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 16 Sep 2016 18:46:08 +0200 Subject: [PATCH 43/51] Refactoring of findRef Three goals: 1. Fix crasher in compileStdLib by saving and restoring foundUnderScala2 analogous to iportedFromRoot. 2. Make behavior the same as scalac under Scala2 mode - ListBuffer behaved differently before. 3. Make findRef faster by making it tail-recursive as long as nothing was found. --- src/dotty/tools/dotc/typer/Typer.scala | 141 +++++++++++++++---------- 1 file changed, 83 insertions(+), 58 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 7854c0baa904..55586c563725 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -78,7 +78,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit * in dotty (because dotty conforms to spec section 2 * wrt to package member resolution but scalac doe not). */ - private var foundUnderScala2: Type = _ + private var foundUnderScala2: Type = NoType def newLikeThis: Typer = new Typer @@ -141,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 || @@ -158,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, "")} @@ -171,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)) @@ -198,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) { @@ -212,58 +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 (ctx.scala2Mode) - foundUnderScala2 = checkNewOrShadowed(found, definition) - 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 @@ -276,21 +295,27 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit return typed(desugar.patternVar(tree), pt) } - val saved = importedFromRoot - importedFromRoot = Set.empty - - foundUnderScala2 = NoType - - var rawType = - try findRef(NoType, BindingPrec.nothingBound, NoContext) - finally importedFromRoot = saved - if (foundUnderScala2.exists && (foundUnderScala2 ne rawType)) { - ctx.migrationWarning( - ex"""Name resolution will change. - | currently selected : $foundUnderScala2 - | in the future, without -language:Scala2: $rawType""", tree.pos) - rawType = foundUnderScala2 + 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 = From 3c7053f711a36ae917460d1e38afacf1c796f6ef Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 17 Sep 2016 22:32:10 +0200 Subject: [PATCH 44/51] Fix #1503 - be careful where to insert an apply. `apply` nodes should not be inserted in the result parts of a block, if-then-else, match, or try. Instead they should be added to the surrounding statement. --- src/dotty/tools/dotc/ast/Desugar.scala | 2 +- src/dotty/tools/dotc/ast/untpd.scala | 10 +++++ src/dotty/tools/dotc/core/Types.scala | 7 +++- src/dotty/tools/dotc/typer/Applications.scala | 2 +- src/dotty/tools/dotc/typer/ProtoTypes.scala | 4 ++ src/dotty/tools/dotc/typer/Typer.scala | 16 ++++---- tests/neg/i1503.scala | 14 +++++++ tests/run/i1503.check | 5 +++ tests/run/i1503.scala | 38 +++++++++++++++++++ 9 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 tests/neg/i1503.scala create mode 100644 tests/run/i1503.check create mode 100644 tests/run/i1503.scala diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index cb9a63db14f1..f7a85f54c3cc 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -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/untpd.scala b/src/dotty/tools/dotc/ast/untpd.scala index 78d55c0f414d..092ae529631a 100644 --- a/src/dotty/tools/dotc/ast/untpd.scala +++ b/src/dotty/tools/dotc/ast/untpd.scala @@ -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 diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index cb423e186b2b..d788b7e690a2 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,7 +935,10 @@ object Types { case _ => NoType } - // ----- Normalizing typerefs over refined types ---------------------------- + /** 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 * by other refinements), and the refined info is a type alias, return the alias, diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index a9212e5d6c78..20850e21d4bb 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -652,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 { 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/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 55586c563725..f556951a07f0 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -573,7 +573,8 @@ 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]) pt else pt.notApplied + val expr1 = typedExpr(tree.expr, ept)(exprCtx) ensureNoLocalRefs( assignType(cpy.Block(tree)(stats1, expr1), stats1, expr1), pt, localSyms(stats1)) } @@ -620,8 +621,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) } @@ -794,7 +795,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) } @@ -921,8 +922,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]] @@ -1580,8 +1581,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/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/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() + } +} From 73192b64f7ce948e2d016718bb0641fdba95dde5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Sep 2016 10:23:09 +0200 Subject: [PATCH 45/51] Address reviewer comments --- src/dotty/tools/dotc/core/Types.scala | 4 ++-- src/dotty/tools/dotc/typer/Typer.scala | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index d788b7e690a2..46a63555cc6f 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -935,10 +935,10 @@ object Types { case _ => NoType } - /** If this is a FunProto or PolyProto, WildcardType, otherwise this */ + /** If this is a FunProto or PolyProto, WildcardType, otherwise this. */ def notApplied: Type = this - // ----- Normalizing typerefs over refined types ---------------------------- + // ----- Normalizing typerefs over refined types ---------------------------- /** If this normalizes* to a refinement type that has a refinement for `name` (which might be followed * by other refinements), and the refined info is a type alias, return the alias, diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index f556951a07f0..eeb1934dc8f2 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -573,7 +573,12 @@ 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 ept = if (tree.isInstanceOf[untpd.InfixOpBlock]) pt else pt.notApplied + 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)) From 2359fb21bd95950a5d0dfdfab4e7a64cdba477c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Mass=C3=A9?= Date: Tue, 20 Sep 2016 14:47:17 -0400 Subject: [PATCH 46/51] delegate dotty compiler info to sbt --- .../main/scala/xsbt/CompilerInterface.scala | 10 ++-- .../main/scala/xsbt/DelegatingReporter.scala | 51 +++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 bridge/src/main/scala/xsbt/DelegatingReporter.scala diff --git a/bridge/src/main/scala/xsbt/CompilerInterface.scala b/bridge/src/main/scala/xsbt/CompilerInterface.scala index ee272b8b127f..a4b00a79454b 100644 --- a/bridge/src/main/scala/xsbt/CompilerInterface.scala +++ b/bridge/src/main/scala/xsbt/CompilerInterface.scala @@ -49,13 +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) + .setReporter(DelegatingReporter(delegate)) val cl = getClass.getClassLoader.asInstanceOf[URLClassLoader] val reporter = DottyMain.process(commandArguments(sources.toArray), ctx) diff --git a/bridge/src/main/scala/xsbt/DelegatingReporter.scala b/bridge/src/main/scala/xsbt/DelegatingReporter.scala new file mode 100644 index 000000000000..ac555917526a --- /dev/null +++ b/bridge/src/main/scala/xsbt/DelegatingReporter.scala @@ -0,0 +1,51 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt + +import dotty.tools._ +import dotc._ +import reporting._ +import core.Contexts._ + +import xsbti.Maybe + +private object DelegatingReporter { + def apply(delegate: xsbti.Reporter) = new DelegatingReporter(delegate) +} + +private final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter + with UniqueMessagePositions + with HideNonSensicalMessages { + + def doReport(d: Diagnostic)(implicit ctx: Context): Unit = { + val severity = + d match { + case _: Reporter.Error => xsbti.Severity.Error + case _: Reporter.Warning => xsbti.Severity.Warn + case _ => xsbti.Severity.Info + } + val pos = d.pos + + val file = + if(pos.source.file.exists) Some(pos.source.file.file) + else None + + val position = new xsbti.Position { + def line: Maybe[Integer] = Maybe.just(pos.line) + def lineContent(): String = pos.lineContent + def offset(): xsbti.Maybe[Integer] = Maybe.just(pos.point) + def pointer(): xsbti.Maybe[Integer] = offset() + def pointerSpace(): xsbti.Maybe[String] = Maybe.just(" " * pos.point) + 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) + } +} \ No newline at end of file From 2ff7e406c4bf2e43ce9dd1c850ba5c9596b9de2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Mass=C3=A9?= Date: Wed, 21 Sep 2016 11:24:18 -0400 Subject: [PATCH 47/51] automatically clean sbt cache for the dotty sbt bridge --- .../main/scala/xsbt/CompilerInterface.scala | 5 ++--- .../main/scala/xsbt/DelegatingReporter.scala | 9 +++++--- project/Build.scala | 21 +++++++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/bridge/src/main/scala/xsbt/CompilerInterface.scala b/bridge/src/main/scala/xsbt/CompilerInterface.scala index a4b00a79454b..f5eb56c4b15d 100644 --- a/bridge/src/main/scala/xsbt/CompilerInterface.scala +++ b/bridge/src/main/scala/xsbt/CompilerInterface.scala @@ -57,9 +57,8 @@ class CachedCompilerImpl(args: Array[String], output: Output, resident: Boolean) 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) - .setReporter(DelegatingReporter(delegate)) + 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) diff --git a/bridge/src/main/scala/xsbt/DelegatingReporter.scala b/bridge/src/main/scala/xsbt/DelegatingReporter.scala index ac555917526a..b39140c8ffed 100644 --- a/bridge/src/main/scala/xsbt/DelegatingReporter.scala +++ b/bridge/src/main/scala/xsbt/DelegatingReporter.scala @@ -10,11 +10,11 @@ import core.Contexts._ import xsbti.Maybe -private object DelegatingReporter { +object DelegatingReporter { def apply(delegate: xsbti.Reporter) = new DelegatingReporter(delegate) } -private final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter +final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter with UniqueMessagePositions with HideNonSensicalMessages { @@ -38,7 +38,10 @@ private final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporte def pointer(): xsbti.Maybe[Integer] = offset() def pointerSpace(): xsbti.Maybe[String] = Maybe.just(" " * pos.point) def sourceFile(): xsbti.Maybe[java.io.File] = maybe(file) - def sourcePath(): xsbti.Maybe[String] = maybe(file.map(_.getPath)) + def sourcePath(): xsbti.Maybe[String] = { + println(file) + maybe(file.map(_.getPath)) + } } delegate.log(position, d.message, severity) diff --git a/project/Build.scala b/project/Build.scala index 1412556a9732..9b1d63423a60 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -9,6 +9,8 @@ import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ object DottyBuild extends Build { + val cleanBridge = TaskKey[Unit]("clean-sbt-bridge", "delete dotty-sbt-bridge cache") + val baseVersion = "0.1" val isNightly = sys.env.get("NIGHTLYBUILD") == Some("yes") @@ -237,6 +239,25 @@ object DottyBuild extends Build { ). settings(ScriptedPlugin.scriptedSettings: _*). settings( + 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" / "local" / "ch.epfl.lamp" / artifact) + 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") + }, + + ScriptedPlugin.scripted <<= ScriptedPlugin.scripted + .dependsOn(publishLocal) // 2nd + .dependsOn(cleanBridge), // 1st + ScriptedPlugin.scriptedLaunchOpts := Seq("-Xmx1024m"), ScriptedPlugin.scriptedBufferLog := false // TODO: Use this instead of manually copying DottyInjectedPlugin.scala From 5ca5cf42e1b9e89c4c5fb28cf5ef1fafa47389f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Mass=C3=A9?= Date: Wed, 21 Sep 2016 11:39:05 -0400 Subject: [PATCH 48/51] clean the sbt dotty-bridge before publishing --- project/Build.scala | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 9b1d63423a60..9c0ad2654056 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -249,15 +249,10 @@ object DottyBuild extends Build { val org = organization.value val artifact = moduleName.value - IO.delete(file(home) / ".ivy2" / "local" / "ch.epfl.lamp" / artifact) 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") }, - - ScriptedPlugin.scripted <<= ScriptedPlugin.scripted - .dependsOn(publishLocal) // 2nd - .dependsOn(cleanBridge), // 1st - + publishLocal <<= publishLocal.dependsOn(cleanBridge), ScriptedPlugin.scriptedLaunchOpts := Seq("-Xmx1024m"), ScriptedPlugin.scriptedBufferLog := false // TODO: Use this instead of manually copying DottyInjectedPlugin.scala From 13b124d2bbf61c0d7b30734714994fb8bee654bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Mass=C3=A9?= Date: Wed, 21 Sep 2016 11:48:06 -0400 Subject: [PATCH 49/51] cleanBridge is a workaround for sbt#2402 --- project/Build.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/project/Build.scala b/project/Build.scala index 9c0ad2654056..7ef962443b1d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -239,6 +239,7 @@ 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 From c5f0d068738f23efa3db6a90b3cc6e88aac24f93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Mass=C3=A9?= Date: Wed, 21 Sep 2016 12:10:36 -0400 Subject: [PATCH 50/51] failing scripted test dotty-bridge/scripted compilerReporter/simple --- .../main/scala/xsbt/DelegatingReporter.scala | 30 ++++++--- .../sbt-test/compilerReporter/simple/A.scala | 3 + .../sbt-test/compilerReporter/simple/B.scala | 3 + .../compilerReporter/simple/build.sbt | 1 + .../simple/project/DottyInjectedPlugin.scala | 17 +++++ .../simple/project/Reporter.scala | 65 +++++++++++++++++++ .../src/sbt-test/compilerReporter/simple/test | 2 + 7 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 bridge/src/sbt-test/compilerReporter/simple/A.scala create mode 100644 bridge/src/sbt-test/compilerReporter/simple/B.scala create mode 100644 bridge/src/sbt-test/compilerReporter/simple/build.sbt create mode 100644 bridge/src/sbt-test/compilerReporter/simple/project/DottyInjectedPlugin.scala create mode 100644 bridge/src/sbt-test/compilerReporter/simple/project/Reporter.scala create mode 100644 bridge/src/sbt-test/compilerReporter/simple/test diff --git a/bridge/src/main/scala/xsbt/DelegatingReporter.scala b/bridge/src/main/scala/xsbt/DelegatingReporter.scala index b39140c8ffed..451740e02c7d 100644 --- a/bridge/src/main/scala/xsbt/DelegatingReporter.scala +++ b/bridge/src/main/scala/xsbt/DelegatingReporter.scala @@ -25,23 +25,28 @@ final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter case _: Reporter.Warning => xsbti.Severity.Warn case _ => xsbti.Severity.Info } - val pos = d.pos + val pos = + if(d.pos.exists) Some(d.pos) + else None val file = - if(pos.source.file.exists) Some(pos.source.file.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.just(pos.line) - def lineContent(): String = pos.lineContent - def offset(): xsbti.Maybe[Integer] = Maybe.just(pos.point) + 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.just(" " * pos.point) + def pointerSpace(): xsbti.Maybe[String] = maybe(offset0.map(" " * _)) def sourceFile(): xsbti.Maybe[java.io.File] = maybe(file) - def sourcePath(): xsbti.Maybe[String] = { - println(file) - maybe(file.map(_.getPath)) - } + def sourcePath(): xsbti.Maybe[String] = maybe(file.map(_.getPath)) } delegate.log(position, d.message, severity) @@ -51,4 +56,9 @@ final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter 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/A.scala b/bridge/src/sbt-test/compilerReporter/simple/A.scala new file mode 100644 index 000000000000..fb2b569bb944 --- /dev/null +++ b/bridge/src/sbt-test/compilerReporter/simple/A.scala @@ -0,0 +1,3 @@ +object A { + "warn" +} \ 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..d4b8ec27009b --- /dev/null +++ b/bridge/src/sbt-test/compilerReporter/simple/B.scala @@ -0,0 +1,3 @@ +object B { + 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..44f7f214257a --- /dev/null +++ b/bridge/src/sbt-test/compilerReporter/simple/project/Reporter.scala @@ -0,0 +1,65 @@ +import sbt._ + +object Reporter { + import xsbti.{Reporter, Problem, Position, Severity, Maybe} + + val check = TaskKey[Unit]("check", "make sure compilation info are forwared to sbt") + + val reporter = + Some(new xsbti.Reporter { + private val buffer = collection.mutable.ArrayBuffer.empty[Problem] + def reset(): Unit = { + println("reset") + buffer.clear() + } + def hasErrors: Boolean = { + println("hasErrors") + buffer.exists(_.severity == Severity.Error) + } + def hasWarnings: Boolean = { + println("hasWarnings") + buffer.exists(_.severity == Severity.Warn) + } + def printSummary(): Unit = { + println("printSummary") + def toOption[T](m: Maybe[T]): Option[T] = { + if(m.isEmpty) None + else Some(m.get) + } + println(problems.mkString(System.lineSeparator)) + } + def problems: Array[Problem] = { + println("problems") + println(buffer.toList) + buffer.toArray + } + def log(pos: Position, msg: String, sev: Severity): Unit = { + println("log") + 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 = { + println("comment") + } + + }) + + val checkSettings = Seq( + check := { + val problems = reporter.get.problems + + assert(problems.size == 2) + } + ) + + // compilerReporter is marked private in sbt + val hack = TaskKey[Option[Reporter]]("compilerReporter", "") + + +} \ 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..1335f68c0c47 --- /dev/null +++ b/bridge/src/sbt-test/compilerReporter/simple/test @@ -0,0 +1,2 @@ +-> compile +> check \ No newline at end of file From 1083d69b4a74d7bb9bc85ae5722afc4a790000c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Mass=C3=A9?= Date: Wed, 21 Sep 2016 13:42:20 -0400 Subject: [PATCH 51/51] green tests check dependsOn compile failre otherwise sbt reloads the settings --- .../main/scala/xsbt/CompilerInterface.scala | 1 - .../main/scala/xsbt/DelegatingReporter.scala | 1 + .../sbt-test/compilerReporter/simple/A.scala | 3 - .../sbt-test/compilerReporter/simple/B.scala | 11 +++- .../simple/project/Reporter.scala | 64 +++++++------------ .../src/sbt-test/compilerReporter/simple/test | 1 - project/Build.scala | 4 +- 7 files changed, 34 insertions(+), 51 deletions(-) delete mode 100644 bridge/src/sbt-test/compilerReporter/simple/A.scala diff --git a/bridge/src/main/scala/xsbt/CompilerInterface.scala b/bridge/src/main/scala/xsbt/CompilerInterface.scala index f5eb56c4b15d..cd10b1b45c73 100644 --- a/bridge/src/main/scala/xsbt/CompilerInterface.scala +++ b/bridge/src/main/scala/xsbt/CompilerInterface.scala @@ -60,7 +60,6 @@ class CachedCompilerImpl(args: Array[String], output: Output, resident: Boolean) 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 index 451740e02c7d..8bdb2e6fc29a 100644 --- a/bridge/src/main/scala/xsbt/DelegatingReporter.scala +++ b/bridge/src/main/scala/xsbt/DelegatingReporter.scala @@ -19,6 +19,7 @@ final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter with HideNonSensicalMessages { def doReport(d: Diagnostic)(implicit ctx: Context): Unit = { + println("doReport " + d.toString) val severity = d match { case _: Reporter.Error => xsbti.Severity.Error diff --git a/bridge/src/sbt-test/compilerReporter/simple/A.scala b/bridge/src/sbt-test/compilerReporter/simple/A.scala deleted file mode 100644 index fb2b569bb944..000000000000 --- a/bridge/src/sbt-test/compilerReporter/simple/A.scala +++ /dev/null @@ -1,3 +0,0 @@ -object A { - "warn" -} \ 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 index d4b8ec27009b..6f06785990c3 100644 --- a/bridge/src/sbt-test/compilerReporter/simple/B.scala +++ b/bridge/src/sbt-test/compilerReporter/simple/B.scala @@ -1,3 +1,10 @@ -object B { - er1 +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/project/Reporter.scala b/bridge/src/sbt-test/compilerReporter/simple/project/Reporter.scala index 44f7f214257a..1c5952d28041 100644 --- a/bridge/src/sbt-test/compilerReporter/simple/project/Reporter.scala +++ b/bridge/src/sbt-test/compilerReporter/simple/project/Reporter.scala @@ -1,40 +1,24 @@ import sbt._ +import Keys._ +import KeyRanks.DTask object Reporter { import xsbti.{Reporter, Problem, Position, Severity, Maybe} - val check = TaskKey[Unit]("check", "make sure compilation info are forwared to sbt") + lazy val check = TaskKey[Unit]("check", "make sure compilation info are forwared to sbt") - val reporter = + // 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 = { - println("reset") - buffer.clear() - } - def hasErrors: Boolean = { - println("hasErrors") - buffer.exists(_.severity == Severity.Error) - } - def hasWarnings: Boolean = { - println("hasWarnings") - buffer.exists(_.severity == Severity.Warn) - } - def printSummary(): Unit = { - println("printSummary") - def toOption[T](m: Maybe[T]): Option[T] = { - if(m.isEmpty) None - else Some(m.get) - } - println(problems.mkString(System.lineSeparator)) - } - def problems: Array[Problem] = { - println("problems") - println(buffer.toList) - buffer.toArray - } + 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 = { - println("log") object MyProblem extends Problem { def category: String = null def severity: Severity = sev @@ -44,22 +28,18 @@ object Reporter { } buffer.append(MyProblem) } - def comment(pos: xsbti.Position, msg: String): Unit = { - println("comment") - } - + def comment(pos: xsbti.Position, msg: String): Unit = () }) - val checkSettings = Seq( - check := { + lazy val checkSettings = Seq( + compilerReporter in (Compile, compile) := reporter, + check <<= (compile in Compile).mapFailure( _ => { val problems = reporter.get.problems - - assert(problems.size == 2) - } + 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 + }) ) - - // compilerReporter is marked private in sbt - val hack = TaskKey[Option[Reporter]]("compilerReporter", "") - - } \ No newline at end of file diff --git a/bridge/src/sbt-test/compilerReporter/simple/test b/bridge/src/sbt-test/compilerReporter/simple/test index 1335f68c0c47..a5912a391a4d 100644 --- a/bridge/src/sbt-test/compilerReporter/simple/test +++ b/bridge/src/sbt-test/compilerReporter/simple/test @@ -1,2 +1 @@ --> compile > check \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index 7ef962443b1d..b06cadde7065 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -9,8 +9,8 @@ import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ object DottyBuild extends Build { - val cleanBridge = TaskKey[Unit]("clean-sbt-bridge", "delete dotty-sbt-bridge cache") - + lazy val cleanBridge = TaskKey[Unit]("clean-sbt-bridge", "delete dotty-sbt-bridge cache") + val baseVersion = "0.1" val isNightly = sys.env.get("NIGHTLYBUILD") == Some("yes")