Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion hugr-core/src/hugr/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@ impl TryFrom<&Hugr> for SerHugrLatest {
let op = hugr.get_optype(node);
let is_value_port = offset < op.value_port_count(dir);
let is_static_input = op.static_port(dir).is_some_and(|p| p.index() == offset);
let offset = (is_value_port || is_static_input).then_some(offset as u32);
let is_cfg_edge = op.is_dataflow_block();
let offset = (is_value_port || is_static_input || is_cfg_edge).then_some(offset as u32);
Copy link
Collaborator

@aborgna-q aborgna-q Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too restraining.
The bug happens any time we have more than one non-df ports, so it would be safer to do

Suggested change
let is_cfg_edge = op.is_dataflow_block();
let offset = (is_value_port || is_static_input || is_cfg_edge).then_some(offset as u32);
let multiple_other_ports = op.non_df_port_count(dir) > 1;
let offset = (is_value_port || is_static_input || multiple_other_ports).then_some(offset as u32);

Alternatively, always encode the offset 🤷

It's not a breaking serialization change either way since the decoding logic works fine if the offset is already defined

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, my first idea was to encode the offset always, but this broke some round-trip tests in hugr-py for hugrs containing order edges.

I will try your suggestion.

Copy link
Collaborator

@aborgna-q aborgna-q Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, seems like hugr-py uses None only for order edges. (The fn comment seems wrong here, only order edges have offset -1)

def _constrain_offset(self, p: P) -> PortOffset | None:
"""Constrain an offset to be a valid encoded port offset.
Order edges and control flow edges should be encoded without an offset.
"""
if p.offset < 0:
assert p.offset == -1, "Only order edges are allowed with offset < 0"
return None
else:
return p.offset

If we wanted to follow that here then we can check

let other_port_is_not_order = op.other_port_kind(dir) != EdgeKind::StateOrder;
let offset = (is_value_port || is_static_input || other_port_is_not_order).then_some(offset as u32);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, thanks. I've gone with your second suggestion. I tried the first, but ran into some round-tripping failures using testfiles exported by guppylang. With this second idea, all seems good. (Also checked that it still fixes #2592 .)

(node_rekey[&node], offset)
};

Expand Down
25 changes: 25 additions & 0 deletions hugr-core/src/hugr/serialize/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::builder::{
Container, DFGBuilder, Dataflow, DataflowHugr, DataflowSubContainer, HugrBuilder,
ModuleBuilder, endo_sig, inout_sig, test::closed_dfg_root_hugr,
};
use crate::envelope::{EnvelopeConfig, read_envelope, write_envelope};
use crate::extension::ExtensionRegistry;
use crate::extension::prelude::Noop;
use crate::extension::prelude::{bool_t, qb_t, usize_t};
Expand All @@ -16,15 +17,21 @@ use crate::hugr::validate::ValidationError;
use crate::hugr::views::ExtractionResult;
use crate::ops::custom::{ExtensionOp, OpaqueOp, OpaqueOpError};
use crate::ops::{self, DFG, Input, Module, Output, Value, dataflow::IOTrait};
use crate::package::Package;
use crate::std_extensions::arithmetic::float_types::float64_type;
use crate::std_extensions::arithmetic::int_types::{ConstInt, INT_TYPES};
use crate::std_extensions::logic::LogicOp;
use crate::std_extensions::std_reg;
use crate::test_file;
use crate::types::type_param::TypeParam;
use crate::types::{
FuncValueType, PolyFuncType, PolyFuncTypeRV, Signature, SumType, Type, TypeArg, TypeBound,
TypeRV,
};
use crate::{OutgoingPort, Visibility, type_row};
use std::fs::File;
use std::io::{BufReader, Cursor};

use std::sync::LazyLock;

use itertools::Itertools;
Expand Down Expand Up @@ -625,6 +632,24 @@ fn std_extensions_valid() {
}
}

#[test]
// https://github.com/CQCL/hugr/issues/2600
fn cfg_edge_ordering() {
let pkg: Package = Package::load(
BufReader::new(File::open(test_file!("issue-2600.hugr")).unwrap()),
None,
)
.unwrap();
pkg.validate().unwrap();

let mut data1: Vec<u8> = Vec::new();
let _ = write_envelope(&mut data1, &pkg, EnvelopeConfig::text());

let buff1 = Cursor::new(data1);
let (_, pkg1) = read_envelope(buff1, &std_reg()).unwrap();
pkg1.validate().unwrap();
}

mod proptest {
use super::check_testing_roundtrip;
use super::{NodeSer, SimpleOpDef};
Expand Down
Binary file added resources/test/issue-2600.hugr
Binary file not shown.
Loading