-
Notifications
You must be signed in to change notification settings - Fork 2
fix(cli): correct tuple-domain sizes in inspect #203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
4ab95ec
07a1701
390c41a
87402b9
6db49e2
770b9b2
cdb1c87
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -190,11 +190,16 @@ pub fn build_inspect_payload(entrypoint: &Path, program: &SemanticProgram) -> In | |||||||||||||||||||||||||||||||||
| let set_records = build_set_records(program, filtered_set_names); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Build a lookup from set name → size for bindings | ||||||||||||||||||||||||||||||||||
| let set_sizes: BTreeMap<&str, usize> = program | ||||||||||||||||||||||||||||||||||
| let mut set_sizes: BTreeMap<&str, usize> = program | ||||||||||||||||||||||||||||||||||
| .set_registry | ||||||||||||||||||||||||||||||||||
| .iter() | ||||||||||||||||||||||||||||||||||
| .map(|(name, resolved)| (name.as_str(), resolved.values.len())) | ||||||||||||||||||||||||||||||||||
| .map(|(name, resolved)| (name.as_str(), resolved_set_cardinality(resolved))) | ||||||||||||||||||||||||||||||||||
| .collect(); | ||||||||||||||||||||||||||||||||||
| for (alias, canonical) in &program.set_aliases { | ||||||||||||||||||||||||||||||||||
| if let Some(size) = set_sizes.get(canonical.as_str()).copied() { | ||||||||||||||||||||||||||||||||||
| set_sizes.entry(alias.as_str()).or_insert(size); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Build variable records | ||||||||||||||||||||||||||||||||||
| let variable_records = build_variable_records(program, &set_sizes); | ||||||||||||||||||||||||||||||||||
|
|
@@ -262,7 +267,7 @@ fn build_set_records(program: &SemanticProgram, filtered_set_names: &[&str]) -> | |||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||
| name: name.clone(), | ||||||||||||||||||||||||||||||||||
| alias, | ||||||||||||||||||||||||||||||||||
| size: resolved.values.len(), | ||||||||||||||||||||||||||||||||||
| size: resolved_set_cardinality(resolved), | ||||||||||||||||||||||||||||||||||
| dtype, | ||||||||||||||||||||||||||||||||||
| subset_of, | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
|
|
@@ -272,23 +277,17 @@ fn build_set_records(program: &SemanticProgram, filtered_set_names: &[&str]) -> | |||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| fn find_set_alias(program: &SemanticProgram, set_name: &str) -> Option<String> { | ||||||||||||||||||||||||||||||||||
| // Check variable families for index aliases that map to this set | ||||||||||||||||||||||||||||||||||
| for family in &program.variable_families { | ||||||||||||||||||||||||||||||||||
| for (index, domain) in &family.index_domains { | ||||||||||||||||||||||||||||||||||
| if domain == set_name && index != set_name { | ||||||||||||||||||||||||||||||||||
| return Some(index.clone()); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| // Check constraint generation bindings | ||||||||||||||||||||||||||||||||||
| for constraint in &program.active_constraints { | ||||||||||||||||||||||||||||||||||
| for binding in &constraint.generation_bindings { | ||||||||||||||||||||||||||||||||||
| if binding.domain == set_name && binding.variable != set_name { | ||||||||||||||||||||||||||||||||||
| return Some(binding.variable.clone()); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| None | ||||||||||||||||||||||||||||||||||
| program | ||||||||||||||||||||||||||||||||||
| .set_aliases | ||||||||||||||||||||||||||||||||||
| .iter() | ||||||||||||||||||||||||||||||||||
| .find_map(|(alias, canonical)| (canonical == set_name).then(|| alias.clone())) | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
| program | |
| .set_aliases | |
| .iter() | |
| .find_map(|(alias, canonical)| (canonical == set_name).then(|| alias.clone())) | |
| let mut aliases = program | |
| .set_aliases | |
| .iter() | |
| .filter_map(|(alias, canonical)| (canonical == set_name).then(|| alias.clone())); | |
| let alias = aliases.next()?; | |
| if aliases.next().is_some() { | |
| None | |
| } else { | |
| Some(alias) | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot based on the feedback here (#203 (comment)), answer this comment with the code of a proposed test with a case that fails because of the problem you are pointing out. Do not write code just put the code in a comment answering this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is a proposed test that would fail because find_set_alias silently returns only the alphabetically-first alias when multiple aliases exist, and the SetRecord.alias field hides the others:
#[test]
fn inspect_set_record_exposes_all_aliases_not_just_one() {
// "lines" is the canonical set; it has two aliases: "i" and "j".
// With the current single-Option<String> field, only one alias is
// surfaced in the JSON output — the other is silently discarded.
//
// The test asserts that the set record for "lines" contains BOTH
// aliases in its output. With the current code it will fail because
// `find_set_alias` returns (alphabetically) "i" and drops "j" entirely.
let output = run_arco_inspect_json(concat!(
"sets {\n",
" lines {\n",
" alias \"i\"\n",
" alias \"j\"\n",
" values a b c\n",
" }\n",
"}\n",
));
// Locate the "lines" set in the output
let sets = output["sets"].as_array().expect("sets array");
let lines_set = sets
.iter()
.find(|s| s["name"] == "lines")
.expect("lines set not found");
// Both aliases must appear; with the current code only "i" is present
let aliases = lines_set["aliases"]
.as_array()
.expect("aliases should be an array, not a single optional string");
let alias_values: Vec<&str> = aliases
.iter()
.map(|a| a.as_str().unwrap())
.collect();
assert!(alias_values.contains(&"i"), "alias 'i' missing");
assert!(alias_values.contains(&"j"), "alias 'j' missing");
}The test fails in two ways today:
SetRecord.aliasisOption<String>, not aVec<String>, so the JSON key is"alias"(singular), not"aliases"(plural) — the.as_array()call panics.- Even if the field were changed to
Vec<String>, the currentfind_set_aliasimplementation would only populate it with"i"(the alphabetically-first alias in theBTreeMap), dropping"j".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pesap opinions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anyway to make this block easier to read?, is probably a rust thing but it is supercomplicated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplified in commit 390c41a. The three nested ifs are now a single boolean:
let should_count = resolved_set.tuple_rows.is_none()
|| seen_tuple_domains.insert(canonical_domain);
if should_count {
instances = instances.saturating_mul(resolved_set_cardinality(resolved_set));
}Plain English: count the set's cardinality unless it is a tuple-domain that has already been counted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pesap this looks better to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Storing alias names as additional keys in
set_sizescan produce ambiguous results if an alias collides with a real set name (e.g., a set namediand an index aliasi). In that case,or_insertsilently keeps the original value and the alias mapping is ignored, which can make inspect sizing inconsistent/hard to reason about. Prefer keepingset_sizeskeyed only by canonical set names and performing canonicalization at lookup time (e.g., a small helper that resolvesdomainviaprogram.set_aliasesand then reads fromset_sizes).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pesap this seems to be concerning, but that is a user problem. Probably we want to throw error messages when that happens
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot based on this feedback, answer to this comment with a proposed test that we can integrate in arco that fails in that situation. Do not make the code, just write here the test that you suggest to see this problem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is a proposed test that would expose the alias-vs-set-name collision in
set_sizes:The key assertion is that
x, whose domain is the real seti(3 elements), reports a binding size of 3 and is not silently overridden by the aliasi → nodes(5 elements). This test will fail with the currentor_insert-into-set_sizesapproach whenever insertion order makes the alias entry win.