From 9f26d32db25824f75e5c5b2c2314352c42b074c1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 17 Feb 2017 11:03:37 +0100 Subject: Treat implicit by-name arguments as lazy values With the previous rules, the two test cases produce a diverging implicit expansion. We avoid this by creating for every implicit by-name argument of type T a lazy implicit value of the same type. The implicit value is visible for all nested implicit searches of by-name arguments. That way, we tie the knot and obtain a recursive lazy value instead of a diverging expansion. --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 3 +- compiler/src/dotty/tools/dotc/core/StdNames.scala | 1 + compiler/src/dotty/tools/dotc/core/Symbols.scala | 4 ++ .../src/dotty/tools/dotc/typer/Implicits.scala | 52 +++++++++++++++++++--- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 5 files changed, 53 insertions(+), 9 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 29629e505..6b7be12be 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -202,7 +202,7 @@ object Contexts { private[core] var pendingMemberSearches: List[Name] = Nil /** The new implicit references that are introduced by this scope */ - private var implicitsCache: ContextualImplicits = null + protected var implicitsCache: ContextualImplicits = null def implicits: ContextualImplicits = { if (implicitsCache == null ) implicitsCache = { @@ -469,6 +469,7 @@ object Contexts { def setTypeAssigner(typeAssigner: TypeAssigner): this.type = { this.typeAssigner = typeAssigner; this } def setTyper(typer: Typer): this.type = { this.scope = typer.scope; setTypeAssigner(typer) } def setImportInfo(importInfo: ImportInfo): this.type = { this.importInfo = importInfo; this } + def setImplicits(implicits: ContextualImplicits): this.type = { this.implicitsCache = implicits; this } def setRunInfo(runInfo: RunInfo): this.type = { this.runInfo = runInfo; this } def setDiagnostics(diagnostics: Option[StringBuilder]): this.type = { this.diagnostics = diagnostics; this } def setGadt(gadt: GADTMap): this.type = { this.gadt = gadt; this } diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index c424c6182..1e36361f8 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -130,6 +130,7 @@ object StdNames { val COMPANION_CLASS_METHOD: N = "companion$class" val TRAIT_SETTER_SEPARATOR: N = "$_setter_$" val DIRECT_SUFFIX: N = "$direct" + val LAZY_IMPLICIT_PREFIX: N = "$lazy_implicit$" // value types (and AnyRef) are all used as terms as well // as (at least) arguments to the @specialize annotation. diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 5d0dd2123..c5e064478 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -258,6 +258,10 @@ trait Symbols { this: Context => def newDefaultConstructor(cls: ClassSymbol) = newConstructor(cls, EmptyFlags, Nil, Nil) + /** Create a synthetic lazy implicit value */ + def newLazyImplicit(info: Type) = + newSymbol(owner, freshName(nme.LAZY_IMPLICIT_PREFIX).toTermName, Lazy, info) + /** Create a symbol representing a selftype declaration for class `cls`. */ def newSelfSym(cls: ClassSymbol, name: TermName = nme.WILDCARD, selfInfo: Type = NoType): TermSymbol = ctx.newSymbol(cls, name, SelfSymFlags, selfInfo orElse cls.classInfo.selfType, coord = cls.coord) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 6949221fb..82eb260a4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -29,6 +29,7 @@ import reporting.diagnostic.MessageContainer import Inferencing.fullyDefinedType import Trees._ import Hashable._ +import util.Property import config.Config import config.Printers.{implicits, implicitsDetailed, typr} import collection.mutable @@ -36,6 +37,11 @@ import collection.mutable /** Implicit resolution */ object Implicits { + /** A reference to an implicit value to be made visible on the next nested call to + * inferImplicitArg with a by-name expected type. + */ + val DelayedImplicit = new Property.Key[TermRef] + /** An eligible implicit candidate, consisting of an implicit reference and a nesting level */ case class Candidate(ref: TermRef, level: Int) @@ -202,7 +208,7 @@ object Implicits { } override def toString = { - val own = s"(implicits: ${refs mkString ","})" + val own = i"(implicits: $refs%, %)" if (isOuterMost) own else own + "\n " + outerImplicits } @@ -537,27 +543,59 @@ trait Implicits { self: Typer => else EmptyTree } - inferImplicit(formal, EmptyTree, pos) match { + /** The context to be used when resolving a by-name implicit argument. + * This makes any implicit stored under `DelayedIplicit` visible and + * stores in turn the given `lazyImplicit` as new `DelayedImplicit`. + */ + def lazyImplicitCtx(lazyImplicit: Symbol): Context = { + val lctx = ctx.fresh + ctx.property(DelayedImplicit) match { + case Some(delayedRef) => + lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits)(ctx)) + case None => + } + lctx.setProperty(DelayedImplicit, lazyImplicit.termRef) + } + + /** formalValue: The value type for which an implicit is searched + * lazyIplicit: An implicit symbol to install for nested by-name resolutions + * argCtx : The context to be used for searching the implicit argument + */ + val (formalValue, lazyImplicit, argCtx) = formal match { + case ExprType(fv) => + val lazyImplicit = ctx.newLazyImplicit(fv) + (fv, lazyImplicit, lazyImplicitCtx(lazyImplicit)) + case _ => (formal, NoSymbol, ctx) + } + + inferImplicit(formalValue, EmptyTree, pos)(argCtx) match { case SearchSuccess(arg, _, _, _) => - arg + def refersToLazyImplicit = arg.existsSubTree { + case id: Ident => id.symbol == lazyImplicit + case _ => false + } + if (lazyImplicit.exists && refersToLazyImplicit) + Block(ValDef(lazyImplicit.asTerm, arg).withPos(pos) :: Nil, ref(lazyImplicit)) + else + arg case ambi: AmbiguousImplicits => error(where => s"ambiguous implicits: ${ambi.explanation} of $where") EmptyTree case failure: SearchFailure => - val arg = synthesizedClassTag(formal, pos) + val arg = synthesizedClassTag(formalValue, pos) if (!arg.isEmpty) arg else { var msgFn = (where: String) => em"no implicit argument of type $formal found for $where" + failure.postscript for { - notFound <- formal.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot) + notFound <- formalValue.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot) Trees.Literal(Constant(raw: String)) <- notFound.argument(0) } { msgFn = where => err.implicitNotFoundString( raw, - formal.typeSymbol.typeParams.map(_.name.unexpandedName.toString), - formal.argInfos) + formalValue.typeSymbol.typeParams.map(_.name.unexpandedName.toString), + formalValue.argInfos) } error(msgFn) EmptyTree diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2b57cf778..16aeb4c6d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1885,7 +1885,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def implicitArgError(msg: String => String) = errors += (() => msg(em"parameter $pname of $methodStr")) if (errors.nonEmpty) EmptyTree - else inferImplicitArg(formal.widenExpr, implicitArgError, tree.pos.endPos) + else inferImplicitArg(formal, implicitArgError, tree.pos.endPos) } if (errors.nonEmpty) { // If there are several arguments, some arguments might already -- cgit v1.2.3 From 08f4c3e72485bf01d37f9ea84b2ae8bfea27d049 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 18 Feb 2017 14:07:39 +0100 Subject: Polishing --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 82eb260a4..627ce6f77 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -549,11 +549,8 @@ trait Implicits { self: Typer => */ def lazyImplicitCtx(lazyImplicit: Symbol): Context = { val lctx = ctx.fresh - ctx.property(DelayedImplicit) match { - case Some(delayedRef) => - lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits)(ctx)) - case None => - } + for (delayedRef <- ctx.property(DelayedImplicit)) + lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits)(ctx)) lctx.setProperty(DelayedImplicit, lazyImplicit.termRef) } -- cgit v1.2.3 From d009eb81f0b4d9f71b70a1ac023c833629e3c44c Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Tue, 21 Feb 2017 17:06:10 +0100 Subject: ResolveSuper: MixinOps save 2-3 seconds on stdlib compilation. By ordering tests in a smarter way. Add leave a big scary comment. --- compiler/src/dotty/tools/dotc/transform/MixinOps.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala index 25aa9ffaf..9f7ceeaed 100644 --- a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala +++ b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala @@ -40,6 +40,12 @@ class MixinOps(cls: ClassSymbol, thisTransform: DenotTransformer)(implicit ctx: def isCurrent(sym: Symbol) = ctx.atPhase(thisTransform) { implicit ctx => cls.info.member(sym.name).hasAltWith(_.symbol == sym) + // this is a hot spot, where we spend several seconds while compiling stdlib + // unfortunately it will discard and recompute all the member chaches, + // both making itself slow and slowing down anything that runs after it + // because resolveSuper uses hacks with explicit adding to scopes through .enter + // this cannot be fixed by a smarter caching strategy. With current implementation + // we HAVE to discard caches here for correctness } /** Does `method` need a forwarder to in class `cls` @@ -53,8 +59,8 @@ class MixinOps(cls: ClassSymbol, thisTransform: DenotTransformer)(implicit ctx: def needsDisambiguation = competingMethods.exists(x=> !(x is Deferred)) // multiple implementations are available def hasNonInterfaceDefinition = competingMethods.exists(!_.owner.is(Trait)) // there is a definition originating from class meth.is(Method, butNot = PrivateOrAccessorOrDeferred) && - isCurrent(meth) && - (needsDisambiguation || hasNonInterfaceDefinition || meth.owner.is(Scala2x)) + (meth.owner.is(Scala2x) || needsDisambiguation || hasNonInterfaceDefinition ) && + isCurrent(meth) } /** Get `sym` of the method that needs a forwarder -- cgit v1.2.3 From d8c7a7ae0e981a2abd9e973617ef575270dd30a5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 21 Feb 2017 17:15:02 +0100 Subject: Fix typos --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 627ce6f77..6c8ecd3c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -544,7 +544,7 @@ trait Implicits { self: Typer => } /** The context to be used when resolving a by-name implicit argument. - * This makes any implicit stored under `DelayedIplicit` visible and + * This makes any implicit stored under `DelayedImplicit` visible and * stores in turn the given `lazyImplicit` as new `DelayedImplicit`. */ def lazyImplicitCtx(lazyImplicit: Symbol): Context = { @@ -555,8 +555,8 @@ trait Implicits { self: Typer => } /** formalValue: The value type for which an implicit is searched - * lazyIplicit: An implicit symbol to install for nested by-name resolutions - * argCtx : The context to be used for searching the implicit argument + * lazyImplicit: An implicit symbol to install for nested by-name resolutions + * argCtx : The context to be used for searching the implicit argument */ val (formalValue, lazyImplicit, argCtx) = formal match { case ExprType(fv) => -- cgit v1.2.3 From 9202810e80997105a598a3298f1ac199d5324a23 Mon Sep 17 00:00:00 2001 From: Enno Runne Date: Thu, 23 Feb 2017 13:51:42 +0100 Subject: Change 'private, protected, or this expected' to Message --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 7 ++++--- .../tools/dotc/reporting/diagnostic/ErrorMessageID.java | 1 + .../dotty/tools/dotc/reporting/diagnostic/messages.scala | 14 ++++++++++++++ .../dotty/tools/dotc/reporting/ErrorMessagesTests.scala | 14 ++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index c14108d2e..f9187c5fe 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1607,6 +1607,7 @@ object Parsers { * LocalModifier ::= abstract | final | sealed | implicit | lazy */ def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = { + @tailrec def loop(mods: Modifiers): Modifiers = { if (allowed contains in.token) { val isAccessMod = accessModifierTokens contains in.token @@ -2057,7 +2058,7 @@ object Parsers { val name = ident().toTypeName val constr = atPos(in.lastOffset) { val tparams = typeParamClauseOpt(ParamOwner.Class) - val cmods = constrModsOpt() + val cmods = constrModsOpt(name) val vparamss = paramClauses(name, mods is Case) makeConstructor(tparams, vparamss).withMods(cmods) @@ -2070,11 +2071,11 @@ object Parsers { /** ConstrMods ::= AccessModifier * | Annotation {Annotation} (AccessModifier | `this') */ - def constrModsOpt(): Modifiers = { + def constrModsOpt(owner: Name): Modifiers = { val mods = modifiers(accessModifierTokens, annotsAsMods()) if (mods.hasAnnotations && !mods.hasFlags) if (in.token == THIS) in.nextToken() - else syntaxError("`private', `protected', or `this' expected") + else syntaxError(AnnotatedPrimaryConstructorRequiresModifierOrThis(owner)) mods } diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index 2bf15cb7c..bbd93eb30 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -51,6 +51,7 @@ public enum ErrorMessageID { ExpectedTokenButFoundID, MixedLeftAndRightAssociativeOpsID, CantInstantiateAbstractClassOrTraitID, + AnnotatedPrimaryConstructorRequiresModifierOrThisID, ; public int errorNumber() { diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 34190c114..e367e9ab2 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1146,4 +1146,18 @@ object messages { |""".stripMargin } + case class AnnotatedPrimaryConstructorRequiresModifierOrThis(cls: Name)(implicit ctx: Context) + extends Message(AnnotatedPrimaryConstructorRequiresModifierOrThisID) { + val kind = "Syntax" + val msg = hl"""${"private"}, ${"protected"}, or ${"this"} expected for annotated primary constructor""" + val explanation = + hl"""|When using annotations with a primary constructor of a class, + |the annotation must be followed by an access modifier + |(${"private"} or ${"protected"}) or ${"this"}. + | + |For example: + | ${"class Sample @deprecated this(param: Parameter) { ..."} + | ^^^^ + |""".stripMargin + } } diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 971a40a1b..d6e687a1e 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -198,4 +198,18 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertTrue("expected trait", isTrait) } + @Test def constructorModifier = + checkMessagesAfter("frontend") { + """ + |class AnotherClass @deprecated () + """.stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + val defn = ictx.definitions + + assertMessageCount(1, messages) + val AnnotatedPrimaryConstructorRequiresModifierOrThis(cls) :: Nil = messages + assertEquals("AnotherClass", cls.show) + } } -- cgit v1.2.3 From 145ac696c9a2cb26655e954b4a6be879a3500641 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 27 Feb 2017 12:35:37 +0100 Subject: Fix #2033: Improve handling of unresolved overloaded arguments --- .../src/dotty/tools/dotc/typer/Applications.scala | 11 ++++++++++- tests/neg/i2033.scala | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i2033.scala (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index de017961a..e9df12b42 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -450,7 +450,16 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def typedArg(arg: Arg, formal: Type): Arg = arg def addArg(arg: TypedArg, formal: Type) = - ok = ok & isCompatible(argType(arg, formal), formal) + ok = ok & { + argType(arg, formal) match { + case ref: TermRef if ref.denot.isOverloaded => + // in this case we could not resolve overloading because no alternative + // matches expected type + false + case argtpe => + isCompatible(argtpe, formal) + } + } def makeVarArg(n: Int, elemFormal: Type) = {} def fail(msg: => Message, arg: Arg) = ok = false diff --git a/tests/neg/i2033.scala b/tests/neg/i2033.scala new file mode 100644 index 000000000..b28a0d99e --- /dev/null +++ b/tests/neg/i2033.scala @@ -0,0 +1,21 @@ +import java.io._ +import collection._ +object Test { + def check(obj: AnyRef): Unit = { + val bos = new ByteArrayOutputStream() + val out = new ObjectOutputStream(println) // error + val arr = bos toByteArray () + val in = (()) + val deser = () + val lhs = mutable LinkedHashSet () + check(lhs) + } +} + +// minimization +object Test2 { + class ObjectOutputStream(out: String) { + def this() = this("") + } + val out = new ObjectOutputStream(println) // error +} -- cgit v1.2.3 From 917dd73f041a39b4df45c50fc53abbf07d6d1233 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 27 Feb 2017 14:51:22 +0100 Subject: Remove wrapArrayMethodName from defn. Note that it is never used and the same method exists in transform.TreeGen --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 7 ------- 1 file changed, 7 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 847177e2f..79e97becb 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -906,13 +906,6 @@ class Definitions { /** The type of the boxed class corresponding to primitive value type `tp`. */ def boxedType(tp: Type)(implicit ctx: Context): TypeRef = boxedTypes(scalaClassName(tp)) - def wrapArrayMethodName(elemtp: Type): TermName = { - val cls = elemtp.classSymbol - if (cls.isPrimitiveValueClass) nme.wrapXArray(cls.name) - else if (cls.derivesFrom(ObjectClass) && !cls.isPhantomClass) nme.wrapRefArray - else nme.genericWrapArray - } - type PrimitiveClassEnc = Int val ByteEnc = 2 -- cgit v1.2.3 From 8c406e9fc6c2f3dfe9beb76ee51dc4531e8824a8 Mon Sep 17 00:00:00 2001 From: Enno Runne Date: Mon, 27 Feb 2017 16:31:30 +0100 Subject: Better location to indicate the error --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index f9187c5fe..b27bff37a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2075,7 +2075,7 @@ object Parsers { val mods = modifiers(accessModifierTokens, annotsAsMods()) if (mods.hasAnnotations && !mods.hasFlags) if (in.token == THIS) in.nextToken() - else syntaxError(AnnotatedPrimaryConstructorRequiresModifierOrThis(owner)) + else syntaxError(AnnotatedPrimaryConstructorRequiresModifierOrThis(owner), mods.annotations.last.pos) mods } -- cgit v1.2.3 From 07cdb3d9893385eae6d15d83a7f1e32dd7bbf0a1 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 1 Mar 2017 17:41:48 +0900 Subject: Message rendering: colorize positional splice, then split --- .../tools/dotc/printing/SyntaxHighlighting.scala | 19 +++++++++- .../tools/dotc/reporting/MessageRendering.scala | 44 ++++++++++++++++------ .../src/dotty/tools/dotc/util/SourcePosition.scala | 8 ++-- .../dottydoc/model/comment/CommentParser.scala | 3 +- 4 files changed, 56 insertions(+), 18 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 86f34e64d..e20f846ac 100644 --- a/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -6,6 +6,7 @@ import parsing.Tokens._ import scala.annotation.switch import scala.collection.mutable.StringBuilder import core.Contexts.Context +import util.Chars.{ LF, FF, CR, SU } import Highlighting.{Highlight, HighlightBuffer} /** This object provides functions for syntax highlighting in the REPL */ @@ -26,7 +27,7 @@ object SyntaxHighlighting { private def valDef(str: String) = ValDefColor + str + NoColor private def operator(str: String) = TypeColor + str + NoColor private def annotation(str: String) = - if (str.trim == "@") str else AnnotationColor + str + NoColor + if (str.trim == "@") str else { AnnotationColor + str + NoColor } private val tripleQs = Console.RED_B + "???" + NoColor private val keywords: Seq[String] = for { @@ -152,7 +153,11 @@ object SyntaxHighlighting { var open = 1 while (open > 0 && remaining.nonEmpty) { curr = takeChar() - newBuf += curr + if (curr == '@') { + appendWhile('@', !typeEnders.contains(_), annotation) + newBuf append CommentColor + } + else newBuf += curr if (curr == '*' && remaining.nonEmpty) { curr = takeChar() @@ -163,6 +168,11 @@ object SyntaxHighlighting { newBuf += curr if (curr == '*') open += 1 } + + (curr: @switch) match { + case LF | FF | CR | SU => newBuf append CommentColor + case _ => () + } } prev = curr newBuf append NoColor @@ -236,6 +246,11 @@ object SyntaxHighlighting { newBuf += curr closing = 0 } + + (curr: @switch) match { + case LF | FF | CR | SU => newBuf append LiteralColor + case _ => () + } } newBuf append NoColor prev = curr diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index 190445d60..17eb8d39b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -5,9 +5,12 @@ package reporting import core.Contexts.Context import core.Decorators._ import printing.Highlighting.{Blue, Red} +import printing.SyntaxHighlighting import diagnostic.{ErrorMessageID, Message, MessageContainer, NoExplanation} import diagnostic.messages._ import util.SourcePosition +import util.Chars.{ LF, CR, FF, SU } +import scala.annotation.switch import scala.collection.mutable @@ -38,20 +41,37 @@ trait MessageRendering { */ def sourceLines(pos: SourcePosition)(implicit ctx: Context): (List[String], List[String], Int) = { var maxLen = Int.MinValue - def render(xs: List[Int]) = - xs.map(pos.source.offsetToLine(_)) - .map { lineNbr => - val prefix = s"${lineNbr + 1} |" - maxLen = math.max(maxLen, prefix.length) - (prefix, pos.lineContent(lineNbr).stripLineEnd) - } - .map { case (prefix, line) => - val lnum = Red(" " * math.max(0, maxLen - prefix.length) + prefix) - hl"$lnum$line" - } + def render(offsetAndLine: (Int, String)): String = { + val (offset, line) = offsetAndLine + val lineNbr = pos.source.offsetToLine(offset) + val prefix = s"${lineNbr + 1} |" + maxLen = math.max(maxLen, prefix.length) + val lnum = Red(" " * math.max(0, maxLen - prefix.length) + prefix).show + lnum + line.stripLineEnd + } + + def linesFrom(arr: Array[Char]): List[String] = { + def pred(c: Char) = (c: @switch) match { + case LF | CR | FF | SU => true + case _ => false + } + val (line, rest0) = arr.span(!pred(_)) + val (_, rest) = rest0.span(pred) + new String(line) :: { if (rest.isEmpty) Nil else linesFrom(rest) } + } + val syntax = + if (ctx.settings.color.value != "never") + SyntaxHighlighting(pos.linesSlice).toArray + else pos.linesSlice + val lines = linesFrom(syntax) val (before, after) = pos.beforeAndAfterPoint - (render(before), render(after), maxLen) + + ( + before.zip(lines).map(render), + after.zip(lines.drop(before.length)).map(render), + maxLen + ) } /** The column markers aligned under the error */ diff --git a/compiler/src/dotty/tools/dotc/util/SourcePosition.scala b/compiler/src/dotty/tools/dotc/util/SourcePosition.scala index aad4995d8..be5c46995 100644 --- a/compiler/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/compiler/src/dotty/tools/dotc/util/SourcePosition.scala @@ -12,9 +12,14 @@ extends interfaces.SourcePosition { def lineContent: String = source.lineContent(point) def point: Int = pos.point + /** The line of the position, starting at 0 */ def line: Int = source.offsetToLine(point) + /** Extracts the lines from the underlying source file as `Array[Char]`*/ + def linesSlice: Array[Char] = + source.content.slice(source.startOfLine(start), source.nextLine(end)) + /** The lines of the position */ def lines: List[Int] = List.range(source.offsetToLine(start), source.offsetToLine(end + 1)) match { @@ -25,9 +30,6 @@ extends interfaces.SourcePosition { def lineOffsets: List[Int] = lines.map(source.lineToOffset(_)) - def lineContent(lineNumber: Int): String = - source.lineContent(source.lineToOffset(lineNumber)) - def beforeAndAfterPoint: (List[Int], List[Int]) = lineOffsets.partition(_ <= point) diff --git a/doc-tool/src/dotty/tools/dottydoc/model/comment/CommentParser.scala b/doc-tool/src/dotty/tools/dottydoc/model/comment/CommentParser.scala index b7a33a7ef..5ff28f914 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/comment/CommentParser.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/comment/CommentParser.scala @@ -6,6 +6,7 @@ import dotty.tools.dottydoc.util.syntax._ import dotty.tools.dotc.util.Positions._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Decorators._ import scala.collection.mutable import dotty.tools.dotc.config.Printers.dottydoc import scala.util.matching.Regex @@ -196,7 +197,7 @@ trait CommentParser extends util.MemberLookup { ) for ((key, _) <- bodyTags) ctx.docbase.warn( - s"Tag '@${key.name}' is not recognised", + hl"Tag '${"@" + key.name}' is not recognised", // FIXME: here the position is stretched out over the entire comment, // with the point being at the very end. This ensures that the entire // comment will be visible in error reporting. A more fine-grained -- cgit v1.2.3