Skip to content

Commit f7edaae

Browse files
authored
Structured errors from compiler (#342)
Instead of errors as one monolithic string from the compiler, it's now structured with separate path, kind, name, and detail components. Also adds `edgir.local_path_to_str_list` utility function. Updates proto version to 4.
1 parent dda3378 commit f7edaae

File tree

11 files changed

+219
-34
lines changed

11 files changed

+219
-34
lines changed

compiler/src/main/scala/edg/compiler/Compiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class AssignNamer() {
116116
}
117117

118118
object Compiler {
119-
final val kExpectedProtoVersion = 3
119+
final val kExpectedProtoVersion = 4
120120
}
121121

122122
/** Compiler for a particular design, with an associated library to elaborate references from.

compiler/src/main/scala/edg/compiler/CompilerError.scala

Lines changed: 130 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,142 @@ import edg.EdgirUtils.SimpleLibraryPath
44
import edg.wir.{DesignPath, IndirectDesignPath}
55
import edgir.expr.expr
66
import edgir.ref.ref
7+
import edgrpc.compiler.{compiler => edgcompiler}
78

89
class ElaboratingException(msg: String, wrapped: Exception) extends Exception(f"$msg:\n$wrapped")
910

10-
sealed trait CompilerError
11+
sealed trait CompilerError {
12+
def toIr: edgcompiler.ErrorRecord
13+
}
1114

1215
object CompilerError {
13-
case class Unelaborated(elaborateRecord: ElaborateRecord, missing: Set[ElaborateRecord]) extends CompilerError {
16+
case class Unelaborated(record: ElaborateRecord, missing: Set[ElaborateRecord]) extends CompilerError {
1417
// These errors may be redundant with below, but provides dependency data
15-
override def toString: String = s"Unelaborated missing dependencies $elaborateRecord:\n" +
18+
override def toString: String = s"Unelaborated missing dependencies $record:\n" +
1619
s"${missing.map(x => s"- $x").mkString("\n")}"
20+
21+
override def toIr: edgcompiler.ErrorRecord = {
22+
val missingStr = "missing " + missing.map(_.toString).mkString(", ")
23+
val (kind, path) = record match {
24+
case ElaborateRecord.ExpandBlock(path, _) =>
25+
(f"Uncompiled block, $missingStr", Some(path.asIndirect.toLocalPath))
26+
case ElaborateRecord.Block(path) => (f"Uncompiled block, $missingStr", Some(path.asIndirect.toLocalPath))
27+
case ElaborateRecord.Link(path) => (f"Uncompiled link, $missingStr", Some(path.asIndirect.toLocalPath))
28+
case ElaborateRecord.LinkArray(path) =>
29+
(f"Uncompiled link array, $missingStr", Some(path.asIndirect.toLocalPath))
30+
case ElaborateRecord.Parameter(containerPath, _, postfix, _) =>
31+
(f"Uncompiled parameter, $missingStr", Some((containerPath.asIndirect ++ postfix).toLocalPath))
32+
case _ => (f"Uncompiled internal element, $missingStr", None)
33+
}
34+
edgcompiler.ErrorRecord(
35+
path = path,
36+
kind = kind,
37+
name = "",
38+
details = ""
39+
)
40+
}
1741
}
1842
case class LibraryElement(path: DesignPath, target: ref.LibraryPath) extends CompilerError {
1943
override def toString: String = s"Unelaborated library element ${target.toSimpleString} @ $path"
44+
45+
override def toIr: edgcompiler.ErrorRecord = {
46+
edgcompiler.ErrorRecord(
47+
path = Some(path.asIndirect.toLocalPath),
48+
kind = "Uncompiled library element",
49+
name = "",
50+
details = f"class ${target.toSimpleString}"
51+
)
52+
}
2053
}
2154
case class UndefinedPortArray(path: DesignPath, portType: ref.LibraryPath) extends CompilerError {
2255
override def toString: String = s"Undefined port array ${portType.toSimpleString} @ $path"
56+
57+
override def toIr: edgcompiler.ErrorRecord = {
58+
edgcompiler.ErrorRecord(
59+
path = Some(path.asIndirect.toLocalPath),
60+
kind = "Undefined port array",
61+
name = "",
62+
details = f"port type ${portType.toSimpleString}"
63+
)
64+
}
2365
}
2466

67+
// TODO partly redundant w/ LibraryElement error, which is a compiler-level error
2568
case class LibraryError(path: DesignPath, target: ref.LibraryPath, err: String) extends CompilerError {
2669
override def toString: String = s"Library error ${target.toSimpleString} @ $path: $err"
70+
71+
override def toIr: edgcompiler.ErrorRecord = {
72+
edgcompiler.ErrorRecord(
73+
path = Some(path.asIndirect.toLocalPath),
74+
kind = "Library error",
75+
name = "",
76+
details = f"${target.toSimpleString}: err"
77+
)
78+
}
2779
}
2880
case class GeneratorError(path: DesignPath, target: ref.LibraryPath, err: String) extends CompilerError {
2981
override def toString: String = s"Generator error ${target.toSimpleString} @ $path: $err"
82+
83+
override def toIr: edgcompiler.ErrorRecord = {
84+
edgcompiler.ErrorRecord(
85+
path = Some(path.asIndirect.toLocalPath),
86+
kind = "Generator error",
87+
name = "",
88+
details = f"${target.toSimpleString}: err"
89+
)
90+
}
3091
}
3192
case class RefinementSubclassError(path: DesignPath, refinedLibrary: ref.LibraryPath, designLibrary: ref.LibraryPath)
3293
extends CompilerError {
3394
override def toString: String =
34-
s"Invalid refinement ${refinedLibrary.toSimpleString} <- ${designLibrary.toSimpleString} @ $path"
95+
s"Invalid refinement ${refinedLibrary.toSimpleString} -> ${designLibrary.toSimpleString} @ $path"
96+
97+
override def toIr: edgcompiler.ErrorRecord = {
98+
edgcompiler.ErrorRecord(
99+
path = Some(path.asIndirect.toLocalPath),
100+
kind = "Invalid refinement",
101+
name = "",
102+
details = f"${refinedLibrary.toSimpleString} -> ${designLibrary.toSimpleString}"
103+
)
104+
}
35105
}
36106

37107
case class OverAssign(target: IndirectDesignPath, causes: Seq[OverAssignCause]) extends CompilerError {
38108
override def toString: String = s"Overassign to $target:\n" +
39109
s"${causes.map(x => s"- $x").mkString("\n")}"
110+
111+
override def toIr: edgcompiler.ErrorRecord = {
112+
edgcompiler.ErrorRecord(
113+
path = Some(target.toLocalPath),
114+
kind = "Overassign",
115+
name = "",
116+
details = causes.map(_.toString).mkString(", ")
117+
)
118+
}
40119
}
41120

42-
case class BadRef(path: DesignPath, ref: IndirectDesignPath) extends CompilerError
121+
case class BadRef(path: DesignPath, ref: IndirectDesignPath) extends CompilerError {
122+
override def toIr: edgcompiler.ErrorRecord = {
123+
edgcompiler.ErrorRecord(
124+
path = Some(path.asIndirect.toLocalPath),
125+
kind = "Bad reference",
126+
name = "",
127+
details = ref.toLocalPath.toString
128+
)
129+
}
130+
}
43131

44132
case class AbstractBlock(path: DesignPath, blockType: ref.LibraryPath) extends CompilerError {
45133
override def toString: String = s"Abstract block: $path (of type ${blockType.toSimpleString})"
134+
135+
override def toIr: edgcompiler.ErrorRecord = {
136+
edgcompiler.ErrorRecord(
137+
path = Some(path.asIndirect.toLocalPath),
138+
kind = "Abstract block",
139+
name = "",
140+
details = f"block type ${blockType.toSimpleString}"
141+
)
142+
}
46143
}
47144

48145
sealed trait AssertionError extends CompilerError
@@ -55,6 +152,15 @@ object CompilerError {
55152
) extends AssertionError {
56153
override def toString: String =
57154
s"Failed assertion: $root.$constrName, ${ExprToString.apply(value)} => $result"
155+
156+
override def toIr: edgcompiler.ErrorRecord = {
157+
edgcompiler.ErrorRecord(
158+
path = Some(root.asIndirect.toLocalPath),
159+
kind = "Failed assertion",
160+
name = constrName,
161+
details = s"${ExprToString.apply(value)} => $result"
162+
)
163+
}
58164
}
59165
case class MissingAssertion(
60166
root: DesignPath,
@@ -63,7 +169,16 @@ object CompilerError {
63169
missing: Set[IndirectDesignPath]
64170
) extends AssertionError {
65171
override def toString: String =
66-
s"Unevaluated assertion: $root.$constrName (${ExprToString.apply(value)}), missing ${missing.mkString(", ")}"
172+
s"Unevaluated assertion: $root.$constrName: missing ${missing.mkString(", ")} in ${ExprToString.apply(value)}"
173+
174+
override def toIr: edgcompiler.ErrorRecord = {
175+
edgcompiler.ErrorRecord(
176+
path = Some(root.asIndirect.toLocalPath),
177+
kind = "Unevaluated assertion",
178+
name = constrName,
179+
details = s"Missing ${missing.mkString(", ")} in ${ExprToString.apply(value)}"
180+
)
181+
}
67182
}
68183

69184
case class InconsistentLinkArrayElements(
@@ -75,6 +190,15 @@ object CompilerError {
75190
) extends CompilerError {
76191
override def toString: String =
77192
s"Inconsistent link array elements: $linkPath ($linkElements), $blockPortPath ($blockPortElements)"
193+
194+
override def toIr: edgcompiler.ErrorRecord = {
195+
edgcompiler.ErrorRecord(
196+
path = Some(linkPath.toLocalPath),
197+
kind = "Inconsistent link array elements",
198+
name = "",
199+
details = s"$linkElements (link-side), $blockPortElements (block-side)"
200+
)
201+
}
78202
}
79203

80204
sealed trait OverAssignCause

compiler/src/main/scala/edg/compiler/CompilerServerMain.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,22 @@ object CompilerServerMain {
7474
new DesignStructuralValidate().map(compiled) ++ new DesignRefsValidate().validate(compiled)
7575
val result = edgcompiler.CompilerResult(
7676
design = Some(compiled),
77-
error = errors.mkString("\n"),
77+
errors = errors.map(_.toIr),
7878
solvedValues = constPropToSolved(compiler.getAllSolved)
7979
)
8080
result
8181
} catch {
8282
case e: Throwable =>
8383
val sw = new StringWriter()
8484
e.printStackTrace(new PrintWriter(sw))
85-
edgcompiler.CompilerResult(error = sw.toString)
85+
edgcompiler.CompilerResult(errors =
86+
Seq(edgcompiler.ErrorRecord(
87+
path = Some(DesignPath().asIndirect.toLocalPath),
88+
kind = "Internal error",
89+
name = "",
90+
details = sw.toString
91+
))
92+
)
8693
}
8794
}
8895

edg/BoardCompiler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ def compile_board(design: Type[Block], target_dir_name: Optional[Tuple[str, str]
3737
with open(design_filename, 'wb') as raw_file:
3838
raw_file.write(compiled.design.SerializeToString())
3939

40-
if compiled.error:
40+
if compiled.errors:
4141
import edg_core
42-
raise edg_core.ScalaCompilerInterface.CompilerCheckError(f"error during compilation: \n{compiled.error}")
42+
raise edg_core.ScalaCompilerInterface.CompilerCheckError(f"error during compilation:\n{compiled.errors_str()}")
4343

4444
netlist_all = NetlistBackend().run(compiled)
4545
netlist_refdes = NetlistBackend().run(compiled, {'RefdesMode': 'refdes'})

edg_core/ScalaCompilerInterface.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,29 @@ class CompiledDesign:
2222
def from_compiler_result(result: edgrpc.CompilerResult) -> 'CompiledDesign':
2323
values = {value.path.SerializeToString(): edgir.valuelit_to_lit(value.value)
2424
for value in result.solvedValues}
25-
return CompiledDesign(result.design, values, result.error)
25+
return CompiledDesign(result.design, values, list(result.errors))
2626

2727
@staticmethod
2828
def from_request(design: edgir.Design, values: Iterable[edgrpc.ExprValue]) -> 'CompiledDesign':
2929
values_dict = {value.path.SerializeToString(): edgir.valuelit_to_lit(value.value)
3030
for value in values}
31-
return CompiledDesign(design, values_dict, "")
31+
return CompiledDesign(design, values_dict, [])
3232

33-
def __init__(self, design: edgir.Design, values: Dict[bytes, edgir.LitTypes], error: str):
33+
def __init__(self, design: edgir.Design, values: Dict[bytes, edgir.LitTypes], errors: List[edgrpc.ErrorRecord]):
3434
self.design = design
3535
self.contents = design.contents # convenience accessor
36-
self.error = error
36+
self.errors = errors
3737
self._values = values
3838

39+
def errors_str(self) -> str:
40+
err_strs = []
41+
for error in self.errors:
42+
error_pathname = edgir.local_path_to_str(error.path)
43+
if error.name:
44+
error_pathname += ':' + error.name
45+
err_strs.append(f"{error.kind} @ {error_pathname}: {error.details}")
46+
return '\n'.join([f'- {err_str}' for err_str in err_strs])
47+
3948
# Reserved.V is a string because it doesn't load properly at runtime
4049
# Serialized strings are used since proto objects are mutable and unhashable
4150
def get_value(self, path: Union[edgir.LocalPath, Iterable[Union[str, 'edgir.Reserved.V']]]) ->\
@@ -112,9 +121,10 @@ def compile(self, block: Type[Block], refinements: Refinements = Refinements(),
112121

113122
assert result is not None
114123
assert result.HasField('design')
115-
if result.error and not ignore_errors:
116-
raise CompilerCheckError(f"error during compilation: \n{result.error}")
117-
return CompiledDesign.from_compiler_result(result)
124+
design = CompiledDesign.from_compiler_result(result)
125+
if result.errors and not ignore_errors:
126+
raise CompilerCheckError(f"error during compilation:\n{design.errors_str()}")
127+
return design
118128

119129
def close(self):
120130
assert self.process is not None
26.3 KB
Binary file not shown.

edg_hdl_server/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from edg_core.Core import NonLibraryProperty
1111

1212

13-
EDG_PROTO_VERSION = 3
13+
EDG_PROTO_VERSION = 4
1414

1515

1616
class LibraryElementIndexer:

edgir/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def valuelit_to_lit(expr: ValueLit) -> LitTypes:
6767
elif expr.HasField('integer'):
6868
return expr.integer.val
6969
elif expr.HasField('range') and \
70-
expr.range.minimum.HasField('floating') and expr.range.maximum.HasField('floating'):
70+
expr.range.minimum.HasField('floating') and expr.range.maximum.HasField('floating'):
7171
from edg_core.Range import Range # TODO fix me, this prevents a circular import
7272
return Range(expr.range.minimum.floating.val, expr.range.maximum.floating.val)
7373
elif expr.HasField('text'):
@@ -169,7 +169,8 @@ def AssignRef(dst: Iterable[str], src: Iterable[str]) -> ValueExpr:
169169
return pb
170170

171171

172-
def local_path_to_str(path: LocalPath) -> str:
172+
def local_path_to_str_list(path: LocalPath) -> List[str]:
173+
"""Convert a LocalPath to a list of its components. Reserved params are presented as strings."""
173174
def step_to_str(step: LocalStep) -> str:
174175
if step.HasField('name'):
175176
return step.name
@@ -180,8 +181,10 @@ def step_to_str(step: LocalStep) -> str:
180181
}[step.reserved_param]
181182
else:
182183
raise ValueError(f"unknown step {step}")
184+
return [step_to_str(step) for step in path.steps]
183185

184-
return '.'.join([step_to_str(step) for step in path.steps])
186+
def local_path_to_str(path: LocalPath) -> str:
187+
return '.'.join(local_path_to_str_list(path))
185188

186189
@overload
187190
def add_pair(pb: RepeatedCompositeFieldContainer[NamedPortLike], name: str) -> PortLike: ...

edgrpc/compiler_pb2.py

Lines changed: 9 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)