Skip to content

Commit 5bc2362

Browse files
authored
Give a source line and caret for errors when possible (#3039)
This includes new command-line option --source-root which allows the user of Chisel to point to one or more root directories for source files. If no --source-root arguments are provided, it will default to using "." which should work for typical SBT and Mill projects.
1 parent 4cb44bc commit 5bc2362

File tree

15 files changed

+280
-16
lines changed

15 files changed

+280
-16
lines changed

core/src/main/scala/chisel3/experimental/hierarchy/core/Definition.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ object Definition extends SourceInfoDoc {
103103
): Definition[T] = {
104104
val dynamicContext = {
105105
val context = Builder.captureContext()
106-
new DynamicContext(Nil, context.throwOnFirstError, context.warningsAsErrors)
106+
new DynamicContext(Nil, context.throwOnFirstError, context.warningsAsErrors, context.sourceRoots)
107107
}
108108
Builder.globalNamespace.copyTo(dynamicContext.globalNamespace)
109109
dynamicContext.inDefinition = true

core/src/main/scala/chisel3/internal/Builder.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import logger.LazyLogging
2020

2121
import scala.collection.mutable
2222
import scala.annotation.tailrec
23+
import java.io.File
2324

2425
private[chisel3] class Namespace(keywords: Set[String]) {
2526
// This HashMap is compressed, not every name in the namespace is present here.
@@ -425,7 +426,8 @@ private[chisel3] class ChiselContext() {
425426
private[chisel3] class DynamicContext(
426427
val annotationSeq: AnnotationSeq,
427428
val throwOnFirstError: Boolean,
428-
val warningsAsErrors: Boolean) {
429+
val warningsAsErrors: Boolean,
430+
val sourceRoots: Seq[File]) {
429431
val importDefinitionAnnos = annotationSeq.collect { case a: ImportDefinitionAnnotation[_] => a }
430432

431433
// Map holding the actual names of extModules
@@ -484,7 +486,7 @@ private[chisel3] class DynamicContext(
484486
var whenStack: List[WhenContext] = Nil
485487
var currentClock: Option[Clock] = None
486488
var currentReset: Option[Reset] = None
487-
val errors = new ErrorLog(warningsAsErrors)
489+
val errors = new ErrorLog(warningsAsErrors, sourceRoots)
488490
val namingStack = new NamingStack
489491

490492
// Used to indicate if this is the top-level module of full elaboration, or from a Definition

core/src/main/scala/chisel3/internal/Error.scala

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import scala.annotation.tailrec
66
import scala.collection.mutable.{ArrayBuffer, LinkedHashMap, LinkedHashSet}
77
import scala.util.control.NoStackTrace
88
import _root_.logger.Logger
9+
import java.io.File
10+
import scala.io.Source
911

1012
import chisel3.experimental.{NoSourceInfo, SourceInfo, SourceLine, UnlocatableSourceInfo}
1113

@@ -98,7 +100,29 @@ private[chisel3] object ErrorLog {
98100
val errTag = s"[${Console.RED}error${Console.RESET}]"
99101
}
100102

101-
private[chisel3] class ErrorLog(warningsAsErrors: Boolean) {
103+
private[chisel3] class ErrorLog(warningsAsErrors: Boolean, sourceRoots: Seq[File]) {
104+
105+
private def getErrorLineInFile(sl: SourceLine): List[String] = {
106+
def tryFileInSourceRoot(sourceRoot: File): Option[List[String]] = {
107+
try {
108+
val file = new File(sourceRoot, sl.filename)
109+
val lines = Source.fromFile(file).getLines()
110+
var i = 0
111+
while (i < (sl.line - 1) && lines.hasNext) {
112+
lines.next()
113+
i += 1
114+
}
115+
val line = lines.next()
116+
val caretLine = (" " * (sl.col - 1)) + "^"
117+
Some(line :: caretLine :: Nil)
118+
} catch {
119+
case scala.util.control.NonFatal(_) => None
120+
}
121+
}
122+
val sourceRootsWithDefault = if (sourceRoots.nonEmpty) sourceRoots else Seq(new File("."))
123+
// View allows us to search the directories one at a time and early out
124+
sourceRootsWithDefault.view.map(tryFileInSourceRoot(_)).collectFirst { case Some(value) => value }.getOrElse(Nil)
125+
}
102126

103127
/** Returns an appropriate location string for the provided source info.
104128
* If the source info is of `NoSourceInfo` type, the source location is looked up via stack trace.
@@ -119,8 +143,9 @@ private[chisel3] class ErrorLog(warningsAsErrors: Boolean) {
119143

120144
private def errorEntry(msg: String, si: Option[SourceInfo], isFatal: Boolean): ErrorEntry = {
121145
val location = errorLocationString(si)
146+
val sourceLineAndCaret = si.collect { case sl: SourceLine => getErrorLineInFile(sl) }.getOrElse(Nil)
122147
val fullMessage = if (location.isEmpty) msg else s"$location: $msg"
123-
ErrorEntry(fullMessage, isFatal)
148+
ErrorEntry(fullMessage :: sourceLineAndCaret, isFatal)
124149
}
125150

126151
/** Log an error message */
@@ -158,7 +183,7 @@ private[chisel3] class ErrorLog(warningsAsErrors: Boolean) {
158183
case ((message, sourceLoc), count) =>
159184
logger.warn(s"${ErrorLog.depTag} $sourceLoc ($count calls): $message")
160185
}
161-
errors.foreach(e => logger.error(s"${e.tag} ${e.msg}"))
186+
errors.foreach(e => logger.error(e.serialize))
162187

163188
if (!deprecations.isEmpty) {
164189
logger.warn(
@@ -237,6 +262,8 @@ private[chisel3] class ErrorLog(warningsAsErrors: Boolean) {
237262
private def elapsedTime: Long = System.currentTimeMillis - startTime
238263
}
239264

240-
private case class ErrorEntry(msg: String, isFatal: Boolean) {
265+
private case class ErrorEntry(lines: Seq[String], isFatal: Boolean) {
241266
def tag = if (isFatal) ErrorLog.errTag else ErrorLog.warnTag
267+
268+
def serialize: String = lines.map(s"$tag " + _).mkString("\n")
242269
}

src/main/scala/chisel3/aop/injecting/InjectingAspect.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ abstract class InjectorAspect[T <: RawModule, M <: RawModule](
6262
new DynamicContext(
6363
annotationsInAspect,
6464
chiselOptions.throwOnFirstError,
65-
chiselOptions.warningsAsErrors
65+
chiselOptions.warningsAsErrors,
66+
chiselOptions.sourceRoots
6667
)
6768
// Add existing module names into the namespace. If injection logic instantiates new modules
6869
// which would share the same name, they will get uniquified accordingly

src/main/scala/chisel3/stage/ChiselAnnotations.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,30 @@ case object WarningsAsErrorsAnnotation
9999

100100
}
101101

102+
/** A root directory for source files, used for enhanced error reporting
103+
*
104+
* More than one may be provided. If a source file is found in more than one source root,
105+
* the first match will be used in error reporting.
106+
*/
107+
case class SourceRootAnnotation(directory: File) extends NoTargetAnnotation with Unserializable with ChiselOption
108+
109+
object SourceRootAnnotation extends HasShellOptions {
110+
val options = Seq(
111+
new ShellOption[String](
112+
longOption = "source-root",
113+
toAnnotationSeq = { dir =>
114+
val f = new File(dir)
115+
if (!f.isDirectory()) {
116+
throw new OptionsException(s"Must be directory that exists!")
117+
}
118+
Seq(SourceRootAnnotation(f))
119+
},
120+
helpText = "Root directory for source files, used for enhanced error reporting",
121+
helpValueName = Some("<file>")
122+
)
123+
)
124+
}
125+
102126
/** Warn when reflective naming changes names of signals */
103127
@deprecated("Support for reflective naming has been removed, this object no longer does anything", "Chisel 3.6")
104128
case object WarnReflectiveNamingAnnotation

src/main/scala/chisel3/stage/ChiselCli.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ trait ChiselCli { this: Shell =>
1313
PrintFullStackTraceAnnotation,
1414
ThrowOnFirstErrorAnnotation,
1515
WarningsAsErrorsAnnotation,
16+
SourceRootAnnotation,
1617
WarnReflectiveNamingAnnotation,
1718
ChiselOutputFileAnnotation,
1819
ChiselGeneratorAnnotation

src/main/scala/chisel3/stage/ChiselOptions.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,25 @@
33
package chisel3.stage
44

55
import chisel3.internal.firrtl.Circuit
6+
import java.io.File
67

78
class ChiselOptions private[stage] (
89
val runFirrtlCompiler: Boolean = true,
910
val printFullStackTrace: Boolean = false,
1011
val throwOnFirstError: Boolean = false,
1112
val warningsAsErrors: Boolean = false,
1213
val outputFile: Option[String] = None,
13-
val chiselCircuit: Option[Circuit] = None) {
14+
val chiselCircuit: Option[Circuit] = None,
15+
val sourceRoots: Vector[File] = Vector.empty) {
1416

1517
private[stage] def copy(
1618
runFirrtlCompiler: Boolean = runFirrtlCompiler,
1719
printFullStackTrace: Boolean = printFullStackTrace,
1820
throwOnFirstError: Boolean = throwOnFirstError,
1921
warningsAsErrors: Boolean = warningsAsErrors,
2022
outputFile: Option[String] = outputFile,
21-
chiselCircuit: Option[Circuit] = chiselCircuit
23+
chiselCircuit: Option[Circuit] = chiselCircuit,
24+
sourceRoots: Vector[File] = sourceRoots
2225
): ChiselOptions = {
2326

2427
new ChiselOptions(
@@ -27,7 +30,8 @@ class ChiselOptions private[stage] (
2730
throwOnFirstError = throwOnFirstError,
2831
warningsAsErrors = warningsAsErrors,
2932
outputFile = outputFile,
30-
chiselCircuit = chiselCircuit
33+
chiselCircuit = chiselCircuit,
34+
sourceRoots = sourceRoots
3135
)
3236

3337
}

src/main/scala/chisel3/stage/package.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ package object stage {
2727
case WarnReflectiveNamingAnnotation => c // Do nothing, ignored
2828
case ChiselOutputFileAnnotation(f) => c.copy(outputFile = Some(f))
2929
case ChiselCircuitAnnotation(a) => c.copy(chiselCircuit = Some(a))
30+
case SourceRootAnnotation(s) => c.copy(sourceRoots = c.sourceRoots :+ s)
3031
}
3132
}
3233

src/main/scala/chisel3/stage/phases/Elaborate.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ class Elaborate extends Phase {
3333
new DynamicContext(
3434
annotations,
3535
chiselOptions.throwOnFirstError,
36-
chiselOptions.warningsAsErrors
36+
chiselOptions.warningsAsErrors,
37+
chiselOptions.sourceRoots
3738
)
3839
val (circuit, dut) =
3940
Builder.build(Module(gen()), context)

src/main/scala/circt/stage/CIRCTStage.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import chisel3.deprecatedMFCMessage
66
import chisel3.stage.{
77
ChiselGeneratorAnnotation,
88
PrintFullStackTraceAnnotation,
9+
SourceRootAnnotation,
910
ThrowOnFirstErrorAnnotation,
1011
WarningsAsErrorsAnnotation
1112
}
@@ -23,6 +24,7 @@ trait CLI { this: Shell =>
2324
PrintFullStackTraceAnnotation,
2425
ThrowOnFirstErrorAnnotation,
2526
WarningsAsErrorsAnnotation,
27+
SourceRootAnnotation,
2628
SplitVerilog
2729
).foreach(_.addOptions(parser))
2830
}

0 commit comments

Comments
 (0)