diff --git a/guppylang/compiler/func_compiler.py b/guppylang/compiler/func_compiler.py index 30cb66ba..c07dac29 100644 --- a/guppylang/compiler/func_compiler.py +++ b/guppylang/compiler/func_compiler.py @@ -47,7 +47,7 @@ def compile_local_func_def( [v.name for v, _ in captured] + list(func.ty.input_names), ) - def_node = graph.add_def(closure_ty, dfg.node, func.name) + def_node = graph.add_def(closure_ty, dfg.node, func.name, func) def_input, input_ports = graph.add_input_with_ports( list(closure_ty.inputs), def_node ) diff --git a/guppylang/definition/custom.py b/guppylang/definition/custom.py index 83a452d6..acd08685 100644 --- a/guppylang/definition/custom.py +++ b/guppylang/definition/custom.py @@ -157,7 +157,7 @@ def load_with_args( # we explicitly monomorphise here and invoke the call compiler with the # inferred type args. fun_ty = self.ty.instantiate(type_args) - def_node = graph.add_def(fun_ty, dfg.node, self.name) + def_node = graph.add_def(fun_ty, dfg.node, self.name, self.defined_at) with graph.parent(def_node): _, inp_ports = graph.add_input_with_ports(list(fun_ty.inputs)) returns = self.compile_call( @@ -279,7 +279,10 @@ def __init__(self, op: ops.OpType) -> None: def compile(self, args: list[OutPortV]) -> list[OutPortV]: node = self.graph.add_node( - self.op.model_copy(deep=True), inputs=args, parent=self.dfg.node + self.op.model_copy(deep=True), + inputs=args, + parent=self.dfg.node, + debug=self.node, ) return_ty = get_type(self.node) return [node.add_out_port(ty) for ty in type_to_row(return_ty)] diff --git a/guppylang/definition/function.py b/guppylang/definition/function.py index 3c3c4b6d..f0b91e2a 100644 --- a/guppylang/definition/function.py +++ b/guppylang/definition/function.py @@ -112,7 +112,7 @@ def compile_outer(self, graph: Hugr, parent: Node) -> "CompiledFunctionDef": access to the other compiled functions yet. The body is compiled later in `CompiledFunctionDef.compile_inner()`. """ - def_node = graph.add_def(self.ty, parent, self.name) + def_node = graph.add_def(self.ty, parent, self.name, self.defined_at) return CompiledFunctionDef( self.id, self.name, diff --git a/guppylang/hugr_builder/hugr.py b/guppylang/hugr_builder/hugr.py index 3803471c..f164c18f 100644 --- a/guppylang/hugr_builder/hugr.py +++ b/guppylang/hugr_builder/hugr.py @@ -10,6 +10,7 @@ from hugr.serialization import serial_hugr as raw from hugr.serialization.ops import OpType +from guppylang.ast_util import AstNode, get_file, get_line_offset, line_col from guppylang.tys.subst import Inst from guppylang.tys.ty import ( FunctionType, @@ -298,15 +299,18 @@ class Hugr: _children: dict[NodeIdx, list[Node]] _default_parent: Node | None - def __init__(self, name: str | None = None) -> None: + def __init__(self, name: str | None = None, file: str | None = None) -> None: """Creates a new Hugr.""" self.name = name or "Unnamed" self._default_parent = None self._graph = nx.MultiDiGraph() self._children = {-1: []} + meta_data: dict[str, Any] = {"name": name} + if file is not None: + meta_data["di"] = {"file": file} self.root = self.add_node( op=ops.OpType(ops.Module(parent=UNDEFINED)), - meta_data={"name": name}, + meta_data=meta_data, parent=None, ) @@ -334,6 +338,7 @@ def add_node( output_types: TypeList | None = None, parent: Node | None = None, inputs: list[OutPortV] | None = None, + debug: AstNode | None = None, meta_data: dict[str, Any] | None = None, ) -> VNode: """Helper method to add a generic value node to the graph.""" @@ -346,7 +351,7 @@ def add_node( parent=parent, in_port_types=[p.ty for p in inputs] if inputs is not None else input_types, out_port_types=output_types, - meta_data=meta_data or {}, + meta_data=(meta_data or {}) | debug_metadata(debug), ) self._insert_node(node, inputs) return node @@ -358,6 +363,7 @@ def _add_dfg_node( output_types: TypeList | None = None, parent: Node | None = None, inputs: list[OutPortV] | None = None, + debug: AstNode | None = None, meta_data: dict[str, Any] | None = None, ) -> DFContainingVNode: """Helper method to add a generic dataflow containing value node to the @@ -371,7 +377,7 @@ def _add_dfg_node( parent=parent, in_port_types=[p.ty for p in inputs] if inputs is not None else input_types, out_port_types=output_types, - meta_data=meta_data or {}, + meta_data=(meta_data or {}) | debug_metadata(debug), ) self._insert_node(node, inputs) return node @@ -624,11 +630,15 @@ def add_partial( ) def add_def( - self, fun_ty: FunctionType, parent: Node | None, name: str + self, + fun_ty: FunctionType, + parent: Node | None, + name: str, + debug: AstNode | None = None, ) -> DFContainingVNode: """Adds a `FucnDefn` node to the graph.""" op = ops.FuncDefn(name=name, signature=fun_ty.to_hugr_poly(), parent=UNDEFINED) - return self._add_dfg_node(ops.OpType(op), [], [fun_ty], parent, None) + return self._add_dfg_node(ops.OpType(op), [], [fun_ty], parent, None, debug) def add_declare(self, fun_ty: FunctionType, parent: Node, name: str) -> VNode: """Adds a `FuncDecl` node to the graph.""" @@ -837,6 +847,9 @@ def to_raw(self) -> raw.SerialHugr: nodes: list[ops.OpType] = [ ops.OpType(ops.Module(parent=UNDEFINED)) ] * self._graph.number_of_nodes() + metadata: list[dict[str, Any]] = [ + {} for _ in range(self._graph.number_of_nodes()) + ] for n in self.nodes(): idx = raw_index[n.idx] # Nodes without parent have themselves as parent in the serialised format @@ -844,6 +857,7 @@ def to_raw(self) -> raw.SerialHugr: n.update_op() n.op.root.parent = raw_index[parent.idx] nodes[idx] = n.op + metadata[idx] = n.meta_data edges: list[raw.Edge] = [] for src, tgt in self.edges(): @@ -857,8 +871,24 @@ def to_raw(self) -> raw.SerialHugr: for src, tgt in self.order_edges(): edges.append(((raw_index[src.idx], None), (raw_index[tgt.idx], None))) - return raw.SerialHugr(nodes=nodes, edges=edges) + return raw.SerialHugr(nodes=nodes, edges=edges, metadata=metadata) def serialize(self) -> str: """Serialize this Hugr in JSON format.""" return self.to_raw().to_json() + + +def debug_metadata(debug: AstNode | None) -> dict[str, Any]: + if debug is not None: + file = get_file(debug) + line, col = line_col(debug) + line_offset = get_line_offset(debug) + if file is not None and line_offset is not None: + return { + "di": { + "file": get_file(debug), + "line": line + line_offset - 1, + "col": col, + } + } + return {} diff --git a/guppylang/module.py b/guppylang/module.py index 28f16cd0..ce9169dc 100644 --- a/guppylang/module.py +++ b/guppylang/module.py @@ -33,6 +33,7 @@ class GuppyModule: """A Guppy module that may contain function and type definitions.""" name: str + file: str | None # Whether the module has already been compiled _compiled: bool @@ -59,8 +60,11 @@ class GuppyModule: # `_register_buffered_instance_funcs` is called. This way, we can associate _instance_func_buffer: dict[str, RawDef] | None - def __init__(self, name: str, import_builtins: bool = True): + def __init__( + self, name: str, file: str | None = None, import_builtins: bool = True + ): self.name = name + self.file = file self._globals = Globals({}, {}, {}, {}) self._compiled_globals = {} self._imported_globals = Globals.default() @@ -197,7 +201,7 @@ def compile(self) -> Hugr: self._globals = self._globals.update_defs(other_defs) # Prepare Hugr for this module - graph = Hugr(self.name) + graph = Hugr(self.name, self.file) module_node = graph.set_root_name(self.name) # Compile definitions to Hugr