Skip to content

Commit a0169b7

Browse files
authored
Merge pull request #1696 from felixmulder/topic/assert-message-laziness
Make sure messages are lazily evaluated until `report` in `Reporter`
2 parents 528bccf + a2354dd commit a0169b7

File tree

6 files changed

+122
-48
lines changed

6 files changed

+122
-48
lines changed

src/dotty/tools/dotc/parsing/Parsers.scala

+7-7
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ object Parsers {
9999
/** Issue an error at given offset if beyond last error offset
100100
* and update lastErrorOffset.
101101
*/
102-
def syntaxError(msg: Message, offset: Int = in.offset): Unit =
102+
def syntaxError(msg: => Message, offset: Int = in.offset): Unit =
103103
if (offset > lastErrorOffset) {
104104
syntaxError(msg, Position(offset))
105105
lastErrorOffset = in.offset
@@ -108,7 +108,7 @@ object Parsers {
108108
/** Unconditionally issue an error at given position, without
109109
* updating lastErrorOffset.
110110
*/
111-
def syntaxError(msg: Message, pos: Position): Unit =
111+
def syntaxError(msg: => Message, pos: Position): Unit =
112112
ctx.error(msg, source atPos pos)
113113

114114
}
@@ -215,23 +215,23 @@ object Parsers {
215215
}
216216
}
217217

218-
def warning(msg: Message, sourcePos: SourcePosition) =
218+
def warning(msg: => Message, sourcePos: SourcePosition) =
219219
ctx.warning(msg, sourcePos)
220220

221-
def warning(msg: Message, offset: Int = in.offset) =
221+
def warning(msg: => Message, offset: Int = in.offset) =
222222
ctx.warning(msg, source atPos Position(offset))
223223

224-
def deprecationWarning(msg: Message, offset: Int = in.offset) =
224+
def deprecationWarning(msg: => Message, offset: Int = in.offset) =
225225
ctx.deprecationWarning(msg, source atPos Position(offset))
226226

227227
/** Issue an error at current offset taht input is incomplete */
228-
def incompleteInputError(msg: Message) =
228+
def incompleteInputError(msg: => Message) =
229229
ctx.incompleteInputError(msg, source atPos Position(in.offset))
230230

231231
/** If at end of file, issue an incompleteInputError.
232232
* Otherwise issue a syntax error and skip to next safe point.
233233
*/
234-
def syntaxErrorOrIncomplete(msg: Message) =
234+
def syntaxErrorOrIncomplete(msg: => Message) =
235235
if (in.token == EOF) incompleteInputError(msg)
236236
else {
237237
syntaxError(msg)

src/dotty/tools/dotc/reporting/Reporter.scala

+26-22
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,16 @@ trait Reporting { this: Context =>
3838
reporter.report(new Info(msg, pos))
3939

4040
def deprecationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit =
41-
reporter.report(msg.deprecationWarning(pos))
41+
reporter.report(new DeprecationWarning(msg, pos))
4242

4343
def migrationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit =
44-
reporter.report(msg.migrationWarning(pos))
44+
reporter.report(new MigrationWarning(msg, pos))
4545

4646
def uncheckedWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit =
47-
reporter.report(msg.uncheckedWarning(pos))
47+
reporter.report(new UncheckedWarning(msg, pos))
4848

4949
def featureWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit =
50-
reporter.report(msg.featureWarning(pos))
50+
reporter.report(new FeatureWarning(msg, pos))
5151

5252
def featureWarning(feature: String, featureDescription: String, isScala2Feature: Boolean,
5353
featureUseSite: Symbol, required: Boolean, pos: SourcePosition): Unit = {
@@ -73,23 +73,27 @@ trait Reporting { this: Context =>
7373
}
7474

7575
def warning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit =
76-
reporter.report(msg.warning(pos))
76+
reporter.report(new Warning(msg, pos))
7777

7878
def strictWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit =
7979
if (this.settings.strict.value) error(msg, pos)
80-
else warning(msg.mapMsg(_ + "\n(This would be an error under strict mode)"), pos)
80+
else reporter.report {
81+
new ExtendMessage(() => msg)(_ + "\n(This would be an error under strict mode)").warning(pos)
82+
}
8183

8284
def error(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit =
83-
reporter.report(msg.error(pos))
85+
reporter.report(new Error(msg, pos))
8486

8587
def errorOrMigrationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit =
8688
if (ctx.scala2Mode) migrationWarning(msg, pos) else error(msg, pos)
8789

8890
def restrictionError(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit =
89-
error(msg.mapMsg(m => s"Implementation restriction: $m"), pos)
91+
reporter.report {
92+
new ExtendMessage(() => msg)(m => s"Implementation restriction: $m").error(pos)
93+
}
9094

91-
def incompleteInputError(msg: Message, pos: SourcePosition = NoSourcePosition)(implicit ctx: Context): Unit =
92-
reporter.incomplete(msg.error(pos))(ctx)
95+
def incompleteInputError(msg: => Message, pos: SourcePosition = NoSourcePosition)(implicit ctx: Context): Unit =
96+
reporter.incomplete(new Error(msg, pos))(ctx)
9397

9498
/** Log msg if settings.log contains the current phase.
9599
* See [[config.CompilerCommand#explainAdvanced]] for the exact meaning of
@@ -192,7 +196,7 @@ trait Reporting { this: Context =>
192196
abstract class Reporter extends interfaces.ReporterResult {
193197

194198
/** Report a diagnostic */
195-
def doReport(d: MessageContainer)(implicit ctx: Context): Unit
199+
def doReport(m: MessageContainer)(implicit ctx: Context): Unit
196200

197201
/** Whether very long lines can be truncated. This exists so important
198202
* debugging information (like printing the classpath) is not rendered
@@ -236,22 +240,22 @@ abstract class Reporter extends interfaces.ReporterResult {
236240
override def default(key: String) = 0
237241
}
238242

239-
def report(d: MessageContainer)(implicit ctx: Context): Unit =
240-
if (!isHidden(d)) {
241-
doReport(d)(ctx.addMode(Mode.Printing))
242-
d match {
243-
case d: ConditionalWarning if !d.enablingOption.value => unreportedWarnings(d.enablingOption.name) += 1
244-
case d: Warning => warningCount += 1
245-
case d: Error =>
246-
errors = d :: errors
243+
def report(m: MessageContainer)(implicit ctx: Context): Unit =
244+
if (!isHidden(m)) {
245+
doReport(m)(ctx.addMode(Mode.Printing))
246+
m match {
247+
case m: ConditionalWarning if !m.enablingOption.value => unreportedWarnings(m.enablingOption.name) += 1
248+
case m: Warning => warningCount += 1
249+
case m: Error =>
250+
errors = m :: errors
247251
errorCount += 1
248-
case d: Info => // nothing to do here
252+
case m: Info => // nothing to do here
249253
// match error if d is something else
250254
}
251255
}
252256

253-
def incomplete(d: MessageContainer)(implicit ctx: Context): Unit =
254-
incompleteHandler(d)(ctx)
257+
def incomplete(m: MessageContainer)(implicit ctx: Context): Unit =
258+
incompleteHandler(m)(ctx)
255259

256260
/** Summary of warnings and errors */
257261
def summary: String = {

src/dotty/tools/dotc/reporting/StoreReporter.scala

+11-4
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,16 @@ import config.Printers.typr
88
import diagnostic.MessageContainer
99
import diagnostic.messages._
1010

11-
/**
12-
* This class implements a Reporter that stores all messages
13-
*/
11+
/** This class implements a Reporter that stores all messages
12+
*
13+
* Beware that this reporter can leak memory, and force messages in two
14+
* scenarios:
15+
*
16+
* - During debugging `config.Printers.typr` is set from `noPrinter` to `new
17+
* Printer`, which forces the message
18+
* - The reporter is not flushed and the message containers capture a
19+
* `Context` (about 4MB)
20+
*/
1421
class StoreReporter(outer: Reporter) extends Reporter {
1522

1623
private var infos: mutable.ListBuffer[MessageContainer] = null
@@ -31,7 +38,7 @@ class StoreReporter(outer: Reporter) extends Reporter {
3138

3239
override def flush()(implicit ctx: Context) =
3340
if (infos != null) {
34-
infos foreach ctx.reporter.report
41+
infos.foreach(ctx.reporter.report(_))
3542
infos = null
3643
}
3744

src/dotty/tools/dotc/reporting/diagnostic/Message.scala

+40-14
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package diagnostic
66
import util.SourcePosition
77
import core.Contexts.Context
88

9+
import messages._
10+
911
object Message {
1012
/** This implicit conversion provides a fallback for error messages that have
1113
* not yet been ported to the new scheme. Comment out this `implicit def` to
@@ -21,6 +23,13 @@ object Message {
2123
* consumed by a subclass of `Reporter`. However, the error position is only
2224
* part of `MessageContainer`, not `Message`.
2325
*
26+
* NOTE: you should not be persisting messages. Most messages take an implicit
27+
* `Context` and these contexts weigh in at about 4mb per instance, as such
28+
* persisting these will result in a memory leak.
29+
*
30+
* Instead use the `persist` method to create an instance that does not keep a
31+
* reference to these contexts.
32+
*
2433
* @param errorId a unique number identifying the message, this will later be
2534
* used to reference documentation online
2635
*/
@@ -48,45 +57,62 @@ abstract class Message(val errorId: Int) { self =>
4857
*/
4958
def explanation: String
5059

51-
/** It is possible to map `msg` to add details, this is at the loss of
52-
* precision since the type of the resulting `Message` won't be original
53-
* extending class
54-
*
55-
* @return a `Message` with the mapped message
60+
/** The implicit `Context` in messages is a large thing that we don't want
61+
* persisted. This method gets around that by duplicating the message
62+
* without the implicit context being passed along.
5663
*/
57-
def mapMsg(f: String => String) = new Message(errorId) {
58-
val msg = f(self.msg)
64+
def persist: Message = new Message (errorId) {
65+
val msg = self.msg
66+
val kind = self.kind
67+
val explanation = self.explanation
68+
}
69+
}
70+
71+
/** An extended message keeps the contained message from being evaluated, while
72+
* allowing for extension for the `msg` string
73+
*
74+
* This is useful when we need to add additional information to an existing
75+
* message.
76+
*/
77+
class ExtendMessage(_msg: () => Message)(f: String => String) { self =>
78+
lazy val msg = f(_msg().msg)
79+
lazy val kind = _msg().kind
80+
lazy val explanation = _msg().explanation
81+
lazy val errorId = _msg().errorId
82+
83+
private def toMessage = new Message(errorId) {
84+
val msg = self.msg
5985
val kind = self.kind
6086
val explanation = self.explanation
6187
}
6288

6389
/** Enclose this message in an `Error` container */
6490
def error(pos: SourcePosition) =
65-
new Error(self, pos)
91+
new Error(toMessage, pos)
6692

6793
/** Enclose this message in an `Warning` container */
6894
def warning(pos: SourcePosition) =
69-
new Warning(self, pos)
95+
new Warning(toMessage, pos)
7096

7197
/** Enclose this message in an `Info` container */
7298
def info(pos: SourcePosition) =
73-
new Info(self, pos)
99+
new Info(toMessage, pos)
74100

75101
/** Enclose this message in an `FeatureWarning` container */
76102
def featureWarning(pos: SourcePosition) =
77-
new FeatureWarning(self, pos)
103+
new FeatureWarning(toMessage, pos)
78104

79105
/** Enclose this message in an `UncheckedWarning` container */
80106
def uncheckedWarning(pos: SourcePosition) =
81-
new UncheckedWarning(self, pos)
107+
new UncheckedWarning(toMessage, pos)
82108

83109
/** Enclose this message in an `DeprecationWarning` container */
84110
def deprecationWarning(pos: SourcePosition) =
85-
new DeprecationWarning(self, pos)
111+
new DeprecationWarning(toMessage, pos)
86112

87113
/** Enclose this message in an `MigrationWarning` container */
88114
def migrationWarning(pos: SourcePosition) =
89-
new MigrationWarning(self, pos)
115+
new MigrationWarning(toMessage, pos)
90116
}
91117

92118
/** The fallback `Message` containing no explanation and having no `kind` */

src/dotty/tools/dotc/reporting/diagnostic/messages.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ object messages {
284284
val explanation = ""
285285
}
286286

287-
case class EarlyDefinitionsNotSupported()(implicit ctx:Context)
287+
case class EarlyDefinitionsNotSupported()(implicit ctx: Context)
288288
extends Message(9) {
289289
val kind = "Syntax"
290290
val msg = "early definitions are not supported; use trait parameters instead"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package dotty.tools
2+
package dotc
3+
package reporting
4+
5+
import org.junit.Assert._
6+
import org.junit.Test
7+
8+
import core.Contexts._
9+
10+
import test.DottyTest
11+
12+
import diagnostic.{ Message, MessageContainer, ExtendMessage }
13+
14+
class TestMessageLaziness extends DottyTest {
15+
ctx = ctx.fresh.setReporter(new NonchalantReporter)
16+
17+
class NonchalantReporter(implicit ctx: Context) extends Reporter
18+
with UniqueMessagePositions with HideNonSensicalMessages {
19+
def doReport(m: MessageContainer)(implicit ctx: Context) = ???
20+
21+
override def report(m: MessageContainer)(implicit ctx: Context) = ()
22+
}
23+
24+
case class LazyError() extends Message(1000) {
25+
throw new Error("Didn't stay lazy.")
26+
27+
val kind = "Test"
28+
val msg = "Please don't blow up"
29+
val explanation = ""
30+
}
31+
32+
@Test def assureLazy =
33+
ctx.error(LazyError())
34+
35+
@Test def assureLazyExtendMessage =
36+
ctx.strictWarning(LazyError())
37+
}

0 commit comments

Comments
 (0)