From 8cf3304d02ddb993e03a65118ecd6b6d17e0ff93 Mon Sep 17 00:00:00 2001 From: "Jip J. Dekker" Date: Fri, 24 Jul 2026 14:43:00 +0100 Subject: [PATCH 1/2] feat: use `Arc` for `AnnotationLiteral` --- corpus/json/encapsulated_string.debug.txt | 6 +- corpus/json/unit_test_example.debug.txt | 53 ++++++- src/fzn.rs | 89 ++++++++---- src/fzn/annotations.rs | 35 +++-- src/intermediate.rs | 134 +++++++++-------- src/lib.rs | 169 ++++------------------ src/serde_impl.rs | 34 +---- src/serde_impl/seeded.rs | 60 +++++--- 8 files changed, 281 insertions(+), 299 deletions(-) diff --git a/corpus/json/encapsulated_string.debug.txt b/corpus/json/encapsulated_string.debug.txt index b7d2590..853e0b3 100644 --- a/corpus/json/encapsulated_string.debug.txt +++ b/corpus/json/encapsulated_string.debug.txt @@ -11,8 +11,10 @@ FlatZinc { id: "myAnn", args: [ Literal( - String( - "my string", + Literal( + String( + "my string", + ), ), ), ], diff --git a/corpus/json/unit_test_example.debug.txt b/corpus/json/unit_test_example.debug.txt index e4f8b72..d87d829 100644 --- a/corpus/json/unit_test_example.debug.txt +++ b/corpus/json/unit_test_example.debug.txt @@ -354,7 +354,40 @@ FlatZinc { id: "int_search", args: [ ArrayNamed( - (Weak), + Array { + name: "X_INTRODUCED_5_", + contents: [ + Variable( + Variable { + name: "x", + ty: Int( + Some( + RangeList::from(1..=3), + ), + ), + ann: [], + defined: false, + introduced: false, + }, + ), + Variable( + Variable { + name: "y", + ty: Int( + Some( + RangeList::from(1..=3), + ), + ), + ann: [], + defined: false, + introduced: false, + }, + ), + ], + ann: [], + defined: false, + introduced: false, + }, ), Literal( Annotation( @@ -387,7 +420,23 @@ FlatZinc { id: "bool_search", args: [ ArrayNamed( - (Weak), + Array { + name: "X_INTRODUCED_4_", + contents: [ + Variable( + Variable { + name: "b", + ty: Bool, + ann: [], + defined: true, + introduced: false, + }, + ), + ], + ann: [], + defined: false, + introduced: false, + }, ), Literal( Annotation( diff --git a/src/fzn.rs b/src/fzn.rs index d74271f..1040187 100644 --- a/src/fzn.rs +++ b/src/fzn.rs @@ -625,8 +625,8 @@ mod tests { Stream, array_item, constraint, predicate_item, solve_objective, variable_declaration, }, intermediate::{ - Annotation, AnnotationArgument, AnnotationCall, AnnotationLiteral, Argument, Array, - Constraint, Literal, Method, NameId, NameStore, ParserState, SolveObjective, Variable, + Annotation, AnnotationCall, AnnotationLiteral, Argument, Array, Constraint, Literal, + Method, NameId, NameStore, ParserState, SolveObjective, Variable, }, }; @@ -634,7 +634,7 @@ mod tests { names: &NameStore, name: &str, ) -> AnnotationLiteral { - AnnotationLiteral::Reference(name_id(names, name)) + AnnotationLiteral::Literal(Literal::Reference(name_id(names, name))) } #[test] @@ -991,16 +991,16 @@ mod tests { vec![crate::Annotation::Call(crate::AnnotationCall { id: "int_search".to_owned(), args: vec![ - crate::AnnotationArgument::Array(vec![crate::AnnotationLiteral::Variable( - Arc::downgrade(&fzn.variables[0]), + crate::Argument::Array(vec![crate::AnnotationLiteral::Literal( + crate::Literal::Variable(Arc::clone(&fzn.variables[0])), )]), - crate::AnnotationArgument::Literal(crate::AnnotationLiteral::Annotation( + crate::Argument::Literal(crate::AnnotationLiteral::Annotation( crate::Annotation::Atom("input_order".to_owned()), )), - crate::AnnotationArgument::Literal(crate::AnnotationLiteral::Annotation( + crate::Argument::Literal(crate::AnnotationLiteral::Annotation( crate::Annotation::Atom("indomain_min".to_owned()), )), - crate::AnnotationArgument::Literal(crate::AnnotationLiteral::Annotation( + crate::Argument::Literal(crate::AnnotationLiteral::Annotation( crate::Annotation::Atom("complete".to_owned()), )), ], @@ -1008,6 +1008,49 @@ mod tests { ); } + #[test] + fn self_referential_annotation_is_dropped() { + let fzn = + FlatZinc::::from_fzn(Cursor::new("var int: x :: foo(x);\nsolve satisfy;")) + .expect("failed to parse self-referential annotation"); + assert!( + fzn.variables[0].ann.is_empty(), + "self-referential annotation should be dropped, got {:?}", + fzn.variables[0].ann + ); + } + + #[test] + fn cyclic_annotation_keeps_non_cyclic_direction() { + let fzn = FlatZinc::::from_fzn(Cursor::new( + "var int: x :: foo(y);\nvar int: y :: foo(x);\nsolve satisfy;", + )) + .expect("failed to parse mutually referential annotations"); + let x = fzn.variables.iter().find(|v| v.name == "x").unwrap(); + let y = fzn.variables.iter().find(|v| v.name == "y").unwrap(); + // Exactly one direction is kept (whichever variable is resolved first); + // the other would close the reference cycle and is silently dropped. + let (kept, dropped, target) = if x.ann.is_empty() { + (y, x, x) + } else { + (x, y, y) + }; + assert!( + dropped.ann.is_empty(), + "cyclic annotation should be dropped, got {:?}", + dropped.ann + ); + assert_eq!( + kept.ann, + vec![crate::Annotation::Call(crate::AnnotationCall { + id: "foo".to_owned(), + args: vec![crate::Argument::Literal(crate::AnnotationLiteral::Literal( + crate::Literal::Variable(Arc::clone(target)) + ))], + })] + ); + } + #[test] fn solve_items_are_parsed() { let (actual, names) = parse_with_names(solve_objective, "solve minimize w;"); @@ -1030,17 +1073,14 @@ mod tests { ann: vec![Annotation::Call(AnnotationCall { id: "int_search".to_owned(), args: vec![ - AnnotationArgument::Array(vec![ + Argument::Array(vec![ annotation_identifier(&names, "x"), annotation_identifier(&names, "y"), annotation_identifier(&names, "z"), ]), - AnnotationArgument::Literal(annotation_identifier(&names, "first_fail")), - AnnotationArgument::Literal(annotation_identifier( - &names, - "indomain_split", - )), - AnnotationArgument::Literal(annotation_identifier(&names, "complete")), + Argument::Literal(annotation_identifier(&names, "first_fail")), + Argument::Literal(annotation_identifier(&names, "indomain_split",)), + Argument::Literal(annotation_identifier(&names, "complete")), ], })], }, @@ -1093,10 +1133,10 @@ mod tests { ann: vec![Annotation::Call(AnnotationCall { id: "int_search".to_owned(), args: vec![ - AnnotationArgument::Literal(annotation_identifier(&names, "xs",)), - AnnotationArgument::Literal(annotation_identifier(&names, "input_order",)), - AnnotationArgument::Literal(annotation_identifier(&names, "indomain_min",)), - AnnotationArgument::Literal(annotation_identifier(&names, "complete",)), + Argument::Literal(annotation_identifier(&names, "xs",)), + Argument::Literal(annotation_identifier(&names, "input_order",)), + Argument::Literal(annotation_identifier(&names, "indomain_min",)), + Argument::Literal(annotation_identifier(&names, "complete",)), ], })], }, @@ -1113,17 +1153,14 @@ mod tests { ann: vec![Annotation::Call(AnnotationCall { id: "int_search".to_owned(), args: vec![ - AnnotationArgument::Array(vec![ + Argument::Array(vec![ annotation_identifier(&names, "x"), annotation_identifier(&names, "y"), annotation_identifier(&names, "z"), ]), - AnnotationArgument::Literal(annotation_identifier(&names, "first_fail",)), - AnnotationArgument::Literal(annotation_identifier( - &names, - "indomain_split", - )), - AnnotationArgument::Literal(annotation_identifier(&names, "complete",)), + Argument::Literal(annotation_identifier(&names, "first_fail",)), + Argument::Literal(annotation_identifier(&names, "indomain_split",)), + Argument::Literal(annotation_identifier(&names, "complete",)), ], })], }, diff --git a/src/fzn/annotations.rs b/src/fzn/annotations.rs index 3dd85a8..8a665b9 100644 --- a/src/fzn/annotations.rs +++ b/src/fzn/annotations.rs @@ -10,7 +10,8 @@ use winnow::{ use crate::{ fzn::{Stream, identifier, intern_parsed_identifier, literal, token}, intermediate::{ - Annotation, AnnotationArgument, AnnotationCall, AnnotationLiteral, Literal, NameId, + Annotation, AnnotationArgument, AnnotationCall, AnnotationLiteral, Argument, Literal, + NameId, }, }; @@ -68,13 +69,13 @@ where E: Display, { alt(( - annotation_literal.map(AnnotationArgument::Literal), + annotation_literal.map(Argument::Literal), delimited( token('['), separated(0.., token(annotation_literal), token(',')), token(']'), ) - .map(AnnotationArgument::Array), + .map(Argument::Array), )) .parse_next(input) } @@ -180,7 +181,7 @@ where for (ident, args) in anns { match (ident, args) { ("defines_var", Some(mut args)) if args.len() == 1 => { - if let AnnotationArgument::Literal(AnnotationLiteral::Reference(name)) = + if let Argument::Literal(AnnotationLiteral::Literal(Literal::Reference(name))) = args.remove(0) { defines = Some(name); @@ -252,7 +253,7 @@ mod tests { general_annotations, tests::{annotation_identifier, parse_with_names}, }, - intermediate::{Annotation, AnnotationArgument, AnnotationCall, AnnotationLiteral}, + intermediate::{Annotation, AnnotationCall, AnnotationLiteral, Argument, Literal}, }; #[test] @@ -265,12 +266,14 @@ mod tests { actual, vec![Annotation::Call(AnnotationCall { id: "some_annotation".to_owned(), - args: vec![AnnotationArgument::Array(vec![ + args: vec![Argument::Array(vec![ AnnotationLiteral::Annotation(AnnotationCall { id: "other_annotation".to_owned(), - args: vec![AnnotationArgument::Literal(AnnotationLiteral::Int(5),)], + args: vec![Argument::Literal(AnnotationLiteral::Literal(Literal::Int( + 5 + ),))], }), - AnnotationLiteral::Float(3.4), + AnnotationLiteral::Literal(Literal::Float(3.4)), ])], })], ); @@ -284,7 +287,7 @@ mod tests { actual, vec![Annotation::Call(AnnotationCall { id: "some_annotation".to_owned(), - args: vec![AnnotationArgument::Literal(annotation_identifier( + args: vec![Argument::Literal(annotation_identifier( &names, "other_annotation" ),)], @@ -296,9 +299,9 @@ mod tests { actual, vec![Annotation::Call(AnnotationCall { id: "some_annotation".to_owned(), - args: vec![AnnotationArgument::Literal(AnnotationLiteral::IntSet( - RangeList::from(1..=5) - ),)], + args: vec![Argument::Literal(AnnotationLiteral::Literal( + Literal::IntSet(RangeList::from(1..=5)) + ))], })], ); } @@ -313,10 +316,12 @@ mod tests { actual, vec![Annotation::Call(AnnotationCall { id: "some_annotation".to_owned(), - args: vec![AnnotationArgument::Literal(AnnotationLiteral::Annotation( + args: vec![Argument::Literal(AnnotationLiteral::Annotation( AnnotationCall { id: "other_annotation".to_owned(), - args: vec![AnnotationArgument::Literal(AnnotationLiteral::Int(5),)], + args: vec![Argument::Literal(AnnotationLiteral::Literal(Literal::Int( + 5 + ),))], } ),)], })], @@ -330,7 +335,7 @@ mod tests { actual, vec![Annotation::Call(AnnotationCall { id: "some_annotation".to_owned(), - args: vec![AnnotationArgument::Literal(AnnotationLiteral::Annotation( + args: vec![Argument::Literal(AnnotationLiteral::Annotation( AnnotationCall { id: "another_annotation".to_owned(), args: vec![], diff --git a/src/intermediate.rs b/src/intermediate.rs index 3dc20b3..994c851 100644 --- a/src/intermediate.rs +++ b/src/intermediate.rs @@ -24,13 +24,11 @@ pub(crate) enum Annotation { } /// An intermediate annotation argument. -#[derive(Clone, PartialEq, Debug)] -pub(crate) enum AnnotationArgument { - /// Array argument. - Array(Vec>), - /// Scalar argument. - Literal(AnnotationLiteral), -} +/// +/// A crate-internal alias for the [`Argument`] instantiation used by +/// annotations, whose literals are [`AnnotationLiteral`]s (i.e., they may +/// additionally be nested annotations). It keeps parser signatures readable. +pub(crate) type AnnotationArgument = Argument>; /// An intermediate annotation call. #[derive(Clone, PartialEq, Debug)] @@ -42,33 +40,27 @@ pub(crate) struct AnnotationCall { } /// An intermediate annotation literal. +/// +/// These are the same as regular [`Literal`]s, except that they may +/// additionally be a nested annotation. #[derive(Clone, PartialEq, Debug)] pub(crate) enum AnnotationLiteral { - /// Integer value. - Int(i64), - /// Floating-point value. - Float(f64), - /// Identifier that may refer to a model object or an annotation atom. - Reference(NameId), - /// Boolean value. - Bool(bool), - /// Integer set. - IntSet(RangeList), - /// Floating-point set. - FloatSet(RangeList), - /// String value. - String(String), + /// A regular literal value. + Literal(Literal), /// Nested annotation value. Annotation(AnnotationCall), } /// An intermediate argument. +/// +/// The literal type `L` is [`Literal`] for constraint arguments and +/// [`AnnotationLiteral`] for annotation arguments. #[derive(Clone, PartialEq, Debug)] -pub(crate) enum Argument { +pub(crate) enum Argument { /// An array of literals. - Array(Vec), + Array(Vec), /// A literal value. - Literal(Literal), + Literal(L), } /// An intermediate array declaration. @@ -140,6 +132,13 @@ struct Linker { version: String, /// Cache of resulting literals when resolving a [`NameId`]. resolved: Vec>>, + /// Marks each [`NameId`] whose declaration is currently being constructed, + /// so that a reference back to it can be recognized as self/cyclic. + in_progress: Vec, + /// Set while linking a single top-level annotation when it (transitively) + /// references a name that is still under construction. Such annotations are + /// silently dropped instead of becoming atoms or forming reference cycles. + self_ref: bool, } /// An intermediate literal. @@ -231,15 +230,7 @@ pub(crate) struct Variable { impl From for AnnotationLiteral { fn from(value: Literal) -> Self { - match value { - Literal::Int(i) => AnnotationLiteral::Int(i), - Literal::Float(f) => AnnotationLiteral::Float(f), - Literal::Reference(name) => AnnotationLiteral::Reference(name), - Literal::Bool(b) => AnnotationLiteral::Bool(b), - Literal::IntSet(ranges) => AnnotationLiteral::IntSet(ranges), - Literal::FloatSet(ranges) => AnnotationLiteral::FloatSet(ranges), - Literal::String(string) => AnnotationLiteral::String(string), - } + AnnotationLiteral::Literal(value) } } @@ -274,11 +265,7 @@ where Ok(crate::Literal::Variable(Arc::new(crate::Variable { name: name.into_string(), ty: variable.ty, - ann: variable - .ann - .into_iter() - .map(|a| self.link_annotation(a)) - .collect::, _>>()?, + ann: self.link_annotations(variable.ann)?, defined: variable.defined, introduced: variable.introduced, }))) @@ -370,18 +357,19 @@ where /// Link one intermediate annotation argument. fn link_annotation_argument( &mut self, - argument: AnnotationArgument, - ) -> Result, LinkError> { - match argument { - AnnotationArgument::Array(values) => Ok(crate::AnnotationArgument::Array( + arg: AnnotationArgument, + ) -> Result>, LinkError> { + match arg { + Argument::Array(values) => Ok(crate::Argument::<_, _>::Array( values .into_iter() .map(|value| self.link_annotation_literal(value)) .collect::, _>>()?, )), - AnnotationArgument::Literal(AnnotationLiteral::Reference(name)) => { + Argument::Literal(AnnotationLiteral::Literal(Literal::Reference(name))) => { match self.link_argument(Argument::Literal(Literal::Reference(name))) { Ok(arg) => Ok(arg.into()), + Err(err @ LinkError::UnknownReference(_)) if self.self_ref => Err(err), Err(LinkError::UnknownReference(_)) => { debug_assert!(matches!( self.names.entries[name.index()].declaration, @@ -392,14 +380,14 @@ where ident: self.names.to_owned(name), err: err.to_string(), })?; - Ok(crate::AnnotationArgument::Literal( + Ok(crate::Argument::<_, _>::Literal( crate::AnnotationLiteral::Annotation(crate::Annotation::Atom(ident)), )) } Err(err) => Err(err), } } - AnnotationArgument::Literal(value) => Ok(crate::AnnotationArgument::Literal( + Argument::Literal(value) => Ok(crate::Argument::<_, _>::Literal( self.link_annotation_literal(value)?, )), } @@ -426,11 +414,10 @@ where literal: AnnotationLiteral, ) -> Result, LinkError> { Ok(match literal { - AnnotationLiteral::Int(i) => crate::AnnotationLiteral::Int(i), - AnnotationLiteral::Float(f) => crate::AnnotationLiteral::Float(f), - AnnotationLiteral::Reference(name) => { + AnnotationLiteral::Literal(Literal::Reference(name)) => { match self.link_literal(Literal::Reference(name)) { Ok(lit) => lit.into(), + Err(err @ LinkError::UnknownReference(_)) if self.self_ref => return Err(err), Err(LinkError::UnknownReference(_)) => { debug_assert!(matches!( self.names.entries[name.index()].declaration, @@ -446,25 +433,32 @@ where Err(err) => return Err(err), } } - AnnotationLiteral::Bool(b) => crate::AnnotationLiteral::Bool(b), - AnnotationLiteral::IntSet(ranges) => crate::AnnotationLiteral::IntSet(ranges), - AnnotationLiteral::FloatSet(ranges) => crate::AnnotationLiteral::FloatSet(ranges), - AnnotationLiteral::String(string) => crate::AnnotationLiteral::String(string), + AnnotationLiteral::Literal(lit) => self.link_literal(lit)?.into(), AnnotationLiteral::Annotation(annotation) => crate::AnnotationLiteral::Annotation( crate::Annotation::Call(self.link_annotation_call(annotation)?), ), }) } - /// Link a list of intermediate annotations. + /// Link a list of intermediate annotations, silently dropping any + /// annotation that references a name still under construction (a + /// self/cyclic reference). fn link_annotations( &mut self, annotations: Vec>, ) -> Result>, LinkError> { - annotations - .into_iter() - .map(|annotation| self.link_annotation(annotation)) - .collect() + let mut linked = Vec::with_capacity(annotations.len()); + for annotation in annotations { + self.self_ref = false; + match self.link_annotation(annotation) { + Ok(annotation) => linked.push(annotation), + // A self/cyclic reference unwinds as an unknown reference with + // `self_ref` set: drop this annotation rather than surfacing it. + Err(_) if self.self_ref => {} + Err(err) => return Err(err), + } + } + Ok(linked) } /// Link one intermediate argument. @@ -549,6 +543,8 @@ where solve: model.solve, version: model.version, resolved: vec![None; names_len], + in_progress: vec![false; names_len], + self_ref: false, } } @@ -571,12 +567,32 @@ where if let Some(arg) = &self.resolved[name.index()] { return Ok(arg.clone()); } + if self.in_progress[name.index()] { + // Back-edge to a name whose annotations are still being linked: the + // enclosing annotation is self/cyclic and gets dropped by + // `link_annotations`. Reported as an unknown reference so it unwinds + // through the normal machinery without extracting the entry. + self.self_ref = true; + return Err(LinkError::UnknownReference(self.names.to_owned(name))); + } let entry = self.names.extract_entry(name); let arg = match entry.declaration { Declaration::Variable(variable) => { - crate::Argument::Literal(self.create_variable(entry.name, variable)?) + self.in_progress[name.index()] = true; + // Isolate the caller's annotation from self-references dropped + // while constructing this (nested) variable's own annotations. + let saved = self.self_ref; + let result = self.create_variable(entry.name, variable); + self.self_ref = saved; + crate::Argument::Literal(result?) + } + Declaration::Array(array) => { + self.in_progress[name.index()] = true; + let saved = self.self_ref; + let result = self.create_array(entry.name, array); + self.self_ref = saved; + result? } - Declaration::Array(array) => self.create_array(entry.name, array)?, Declaration::Uninit => { self.names.entries[name.index()] = entry; return Err(LinkError::UnknownReference(self.names.to_owned(name))); diff --git a/src/lib.rs b/src/lib.rs index caeca22..3f8b8a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,7 +133,7 @@ use std::{ collections::HashSet, fmt::{Debug, Display}, hash::{Hash, Hasher}, - sync::{Arc, Weak}, + sync::Arc, }; pub use rangelist::RangeList; @@ -162,23 +162,6 @@ pub enum Annotation { Call(AnnotationCall), } -/// The argument type associated with [`AnnotationCall`] -#[cfg_attr(feature = "serde", derive(Serialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -#[derive(Clone, Debug)] -pub enum AnnotationArgument { - /// Sequence of [`Literal`]s - Array(Vec>), - #[cfg_attr( - feature = "serde", - serde(serialize_with = "serde_impl::serialize_array_weak") - )] - /// Named array of [`Literal`]s - ArrayNamed(Weak>), - /// Singular argument - Literal(AnnotationLiteral), -} - /// An object depicting an annotation in the form of a call #[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", serde(rename = "annotation_call"))] @@ -187,64 +170,41 @@ pub struct AnnotationCall { /// Identifier of the constraint predicate pub id: Identifier, /// Arguments of the constraint - pub args: Vec>, + pub args: Vec>>, } /// Literal values as arguments to [`AnnotationCall`] +/// +/// These are the same as regular [`Literal`]s, except that they may +/// additionally be a nested [`Annotation`]. #[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub enum AnnotationLiteral { - /// Integer value - Int(i64), - /// Floating point value - Float(f64), - /// Reference to a decision variable. - #[cfg_attr( - feature = "serde", - serde(serialize_with = "serde_impl::serialize_variable_weak") - )] - Variable(Weak>), - /// Boolean value - Bool(bool), - /// Set of integers, represented as a list of integer ranges - #[cfg_attr( - feature = "serde", - serde(serialize_with = "serde_impl::serialize_encapsulate_set") - )] - IntSet(RangeList), - /// Set of floating point values, represented as a list of floating point - /// ranges - #[cfg_attr( - feature = "serde", - serde(serialize_with = "serde_impl::serialize_encapsulate_set") - )] - FloatSet(RangeList), - /// String value - #[cfg_attr( - feature = "serde", - serde(serialize_with = "serde_impl::serialize_encapsulate_string") - )] - String(String), + /// A regular literal value. + Literal(Literal), /// An annotation object. Annotation(Annotation), } /// The argument type associated with [`Constraint`] +/// +/// The literal type `L` is [`Literal`] for constraint arguments and +/// [`AnnotationLiteral`] for annotation arguments. #[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] #[derive(Clone, PartialEq, Debug)] -pub enum Argument { - /// Sequence of [`Literal`]s - Array(Vec>), - /// Sequence of [`Literal`]s +pub enum Argument> { + /// Sequence of literals + Array(Vec), + /// Named array of [`Literal`]s #[cfg_attr( feature = "serde", serde(serialize_with = "serde_impl::serialize_array_arc",) )] ArrayNamed(Arc>), /// Literal - Literal(Literal), + Literal(L), } /// A definition of a named array literal in FlatZinc @@ -457,55 +417,14 @@ impl Display for Annotation { } } -impl Display for AnnotationArgument { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AnnotationArgument::Array(arr) => { - write!(f, "[")?; - let mut first = true; - for v in arr { - if !first { - write!(f, ", ")? - } - write!(f, "{v}")?; - first = false; - } - write!(f, "]") - } - AnnotationArgument::ArrayNamed(array) => match array.upgrade() { - Some(array) => write!(f, "{}", &array.name), - None => write!(f, "[/* dangling array ref */]"), - }, - AnnotationArgument::Literal(lit) => write!(f, "{lit}"), - } - } -} - -impl From> for AnnotationArgument { +impl From>> + for Argument> +{ fn from(value: Argument) -> Self { match value { - Argument::Array(arr) => { - AnnotationArgument::Array(arr.into_iter().map(|l| l.into()).collect()) - } - Argument::ArrayNamed(arr) => AnnotationArgument::ArrayNamed(Arc::downgrade(&arr)), - Argument::Literal(l) => AnnotationArgument::Literal(l.into()), - } - } -} - -impl PartialEq for AnnotationArgument { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (AnnotationArgument::Array(lhs), AnnotationArgument::Array(rhs)) => lhs == rhs, - (AnnotationArgument::ArrayNamed(lhs), AnnotationArgument::ArrayNamed(rhs)) => { - match (lhs.upgrade(), rhs.upgrade()) { - (Some(lhs), Some(rhs)) => lhs == rhs, - (None, None) => true, - _ => false, - } - } - (AnnotationArgument::Literal(lhs), AnnotationArgument::Literal(rhs)) => lhs == rhs, - _ => false, + Argument::Array(arr) => Argument::Array(arr.into_iter().map(|l| l.into()).collect()), + Argument::ArrayNamed(arr) => Argument::ArrayNamed(arr), + Argument::Literal(l) => Argument::Literal(l.into()), } } } @@ -528,16 +447,7 @@ impl Display for AnnotationCall { impl Display for AnnotationLiteral { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - AnnotationLiteral::Int(i) => write!(f, "{i}"), - AnnotationLiteral::Float(x) => write!(f, "{x:?}"), - AnnotationLiteral::Variable(var) => match var.upgrade() { - Some(var) => write!(f, "{}", var.name), - None => write!(f, "DANGLING_VARIABLE_REFERENCE"), - }, - AnnotationLiteral::Bool(b) => write!(f, "{b}"), - AnnotationLiteral::IntSet(is) => write!(f, "{is}"), - AnnotationLiteral::FloatSet(fs) => write!(f, "{fs}"), - AnnotationLiteral::String(s) => write!(f, "{s:?}"), + AnnotationLiteral::Literal(lit) => write!(f, "{lit}"), AnnotationLiteral::Annotation(ann) => write!(f, "{ann}"), } } @@ -545,38 +455,11 @@ impl Display for AnnotationLiteral { impl From> for AnnotationLiteral { fn from(value: Literal) -> Self { - match value { - Literal::Int(i) => AnnotationLiteral::Int(i), - Literal::Float(f) => AnnotationLiteral::Float(f), - Literal::Bool(b) => AnnotationLiteral::Bool(b), - Literal::String(s) => AnnotationLiteral::String(s), - Literal::Variable(var) => AnnotationLiteral::Variable(Arc::downgrade(&var)), - Literal::IntSet(set) => AnnotationLiteral::IntSet(set), - Literal::FloatSet(set) => AnnotationLiteral::FloatSet(set), - } + AnnotationLiteral::Literal(value) } } -impl PartialEq for AnnotationLiteral { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Int(lhs), Self::Int(rhs)) => lhs == rhs, - (Self::Float(lhs), Self::Float(rhs)) => lhs == rhs, - (Self::Variable(lhs), Self::Variable(rhs)) => match (lhs.upgrade(), rhs.upgrade()) { - (Some(lhs), Some(rhs)) => lhs == rhs, - (None, None) => true, - _ => false, - }, - (Self::Bool(lhs), Self::Bool(rhs)) => lhs == rhs, - (Self::IntSet(lhs), Self::IntSet(rhs)) => lhs == rhs, - (Self::FloatSet(lhs), Self::FloatSet(rhs)) => lhs == rhs, - (Self::String(lhs), Self::String(rhs)) => lhs == rhs, - (Self::Annotation(lhs), Self::Annotation(rhs)) => lhs == rhs, - _ => false, - } - } -} -impl Display for Argument { +impl Display for Argument { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Argument::Array(arr) => { @@ -591,7 +474,7 @@ impl Display for Argument { } write!(f, "]") } - Argument::ArrayNamed(arr) => write!(f, "{}", &arr.name), + Argument::ArrayNamed(arr) => write!(f, "{}", arr.name), Argument::Literal(lit) => write!(f, "{lit}"), } } diff --git a/src/serde_impl.rs b/src/serde_impl.rs index 21a0a5a..7d2d52d 100644 --- a/src/serde_impl.rs +++ b/src/serde_impl.rs @@ -6,7 +6,7 @@ pub(crate) mod seeded; use std::{ fmt::{Debug, Display}, ops::Deref, - sync::{Arc, Weak}, + sync::Arc, }; use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeMap}; @@ -93,18 +93,6 @@ pub(crate) fn serialize_array_map( state.end() } -/// Serialize a [`Weak`] reference, upgrading it to an [`Arc`] -/// before serializing it using its name, if present, or its contents otherwise. -pub(crate) fn serialize_array_weak( - array: &Weak>, - serializer: S, -) -> Result { - let array = array.upgrade().ok_or_else(|| { - serde::ser::Error::custom("dangling weak array reference in annotation literal") - })?; - serialize_array_arc(&array, serializer) -} - /// Serialization function to be used for the encapsulation of set literals /// required by the FlatZinc serialization format pub(crate) fn serialize_encapsulate_set( @@ -169,18 +157,6 @@ pub(crate) fn serialize_variable_map( state.end() } -/// Serialize a [`Weak`] reference, upgrading it to an -/// [`Arc`] before serializing it using its name. -pub(crate) fn serialize_variable_weak( - variable: &Weak>, - serializer: S, -) -> Result { - let variable = variable.upgrade().ok_or_else(|| { - serde::ser::Error::custom("dangling weak variable reference in annotation literal") - })?; - serialize_variable_arc(&variable, serializer) -} - impl<'de, I, E> Deserialize<'de> for FlatZinc where I: Clone + Debug + for<'a> TryFrom<&'a str, Error = E>, @@ -291,8 +267,8 @@ mod tests { use ustr::Ustr; use crate::{ - Annotation, AnnotationArgument, AnnotationCall, AnnotationLiteral, Argument, Array, - FlatZinc, Literal, Method, NamedRef, SolveObjective, Type, Variable, + Annotation, AnnotationCall, AnnotationLiteral, Argument, Array, FlatZinc, Literal, Method, + NamedRef, SolveObjective, Type, Variable, }; #[test] @@ -418,10 +394,10 @@ mod tests { let ann: Annotation<&str> = Annotation::Call(AnnotationCall { id: "bool_search", args: vec![ - AnnotationArgument::Literal(AnnotationLiteral::Annotation(Annotation::Atom( + Argument::Literal(AnnotationLiteral::Annotation(Annotation::Atom( "input_order", ))), - AnnotationArgument::Literal(AnnotationLiteral::Annotation(Annotation::Atom( + Argument::Literal(AnnotationLiteral::Annotation(Annotation::Atom( "indomain_min", ))), ], diff --git a/src/serde_impl/seeded.rs b/src/serde_impl/seeded.rs index d5bb21b..4d56d1d 100644 --- a/src/serde_impl/seeded.rs +++ b/src/serde_impl/seeded.rs @@ -97,14 +97,16 @@ where let ranges = map.next_value::>()?; ensure_object_finished(map)?; match ranges.try_into().map_err(de::Error::custom)? { - NumericSet::Int(ranges) => Ok(AnnotationLiteral::IntSet(ranges)), - NumericSet::Float(ranges) => Ok(AnnotationLiteral::FloatSet(ranges)), + NumericSet::Int(ranges) => Ok(AnnotationLiteral::Literal(Literal::IntSet(ranges))), + NumericSet::Float(ranges) => { + Ok(AnnotationLiteral::Literal(Literal::FloatSet(ranges))) + } } } "string" => { let string = map.next_value::()?; ensure_object_finished(map)?; - Ok(AnnotationLiteral::String(string)) + Ok(AnnotationLiteral::Literal(Literal::String(string))) } "id" => { let raw = map.next_value::>()?; @@ -548,11 +550,13 @@ where { values.push(value); } - Ok(AnnotationArgument::Array(values)) + Ok(Argument::Array(values)) } fn visit_i64(self, value: i64) -> Result { - Ok(AnnotationArgument::Literal(AnnotationLiteral::Int(value))) + Ok(Argument::Literal(AnnotationLiteral::Literal(Literal::Int( + value, + )))) } fn visit_u64(self, value: u64) -> Result @@ -561,32 +565,38 @@ where { let value = i64::try_from(value) .map_err(|_| E2::custom("integer literal is out of range"))?; - Ok(AnnotationArgument::Literal(AnnotationLiteral::Int(value))) + Ok(Argument::Literal(AnnotationLiteral::Literal(Literal::Int( + value, + )))) } fn visit_f64(self, value: f64) -> Result { - Ok(AnnotationArgument::Literal(AnnotationLiteral::Float(value))) + Ok(Argument::Literal(AnnotationLiteral::Literal( + Literal::Float(value), + ))) } fn visit_bool(self, value: bool) -> Result { - Ok(AnnotationArgument::Literal(AnnotationLiteral::Bool(value))) + Ok(Argument::Literal(AnnotationLiteral::Literal( + Literal::Bool(value), + ))) } fn visit_borrowed_str(self, value: &'de str) -> Result { - Ok(AnnotationArgument::Literal(AnnotationLiteral::Reference( - self.state.names.intern(value), + Ok(Argument::Literal(AnnotationLiteral::Literal( + Literal::Reference(self.state.names.intern(value)), ))) } fn visit_str(self, value: &str) -> Result { - Ok(AnnotationArgument::Literal(AnnotationLiteral::Reference( - self.state.names.intern(value), + Ok(Argument::Literal(AnnotationLiteral::Literal( + Literal::Reference(self.state.names.intern(value)), ))) } fn visit_string(self, value: String) -> Result { - Ok(AnnotationArgument::Literal(AnnotationLiteral::Reference( - self.state.names.intern(&value), + Ok(Argument::Literal(AnnotationLiteral::Literal( + Literal::Reference(self.state.names.intern(&value)), ))) } @@ -597,7 +607,7 @@ where let field = map .next_key::>()? .ok_or_else(|| de::Error::custom("expected an annotation literal object"))?; - Ok(AnnotationArgument::Literal(deserialize_annotation_literal( + Ok(Argument::Literal(deserialize_annotation_literal( field, &mut map, self.state, )?)) } @@ -691,7 +701,7 @@ where } fn visit_i64(self, value: i64) -> Result { - Ok(AnnotationLiteral::Int(value)) + Ok(AnnotationLiteral::Literal(Literal::Int(value))) } fn visit_u64(self, value: u64) -> Result @@ -700,29 +710,33 @@ where { let value = i64::try_from(value) .map_err(|_| E2::custom("integer literal is out of range"))?; - Ok(AnnotationLiteral::Int(value)) + Ok(AnnotationLiteral::Literal(Literal::Int(value))) } fn visit_f64(self, value: f64) -> Result { - Ok(AnnotationLiteral::Float(value)) + Ok(AnnotationLiteral::Literal(Literal::Float(value))) } fn visit_bool(self, value: bool) -> Result { - Ok(AnnotationLiteral::Bool(value)) + Ok(AnnotationLiteral::Literal(Literal::Bool(value))) } fn visit_borrowed_str(self, value: &'de str) -> Result { - Ok(AnnotationLiteral::Reference(self.state.names.intern(value))) + Ok(AnnotationLiteral::Literal(Literal::Reference( + self.state.names.intern(value), + ))) } fn visit_str(self, value: &str) -> Result { - Ok(AnnotationLiteral::Reference(self.state.names.intern(value))) + Ok(AnnotationLiteral::Literal(Literal::Reference( + self.state.names.intern(value), + ))) } fn visit_string(self, value: String) -> Result { - Ok(AnnotationLiteral::Reference( + Ok(AnnotationLiteral::Literal(Literal::Reference( self.state.names.intern(&value), - )) + ))) } fn visit_map(self, mut map: A) -> Result From 521285507976a51f0c725ad6c2dbb24eb1d9f4bf Mon Sep 17 00:00:00 2001 From: "Jip J. Dekker" Date: Fri, 24 Jul 2026 14:44:26 +0100 Subject: [PATCH 2/2] chore: update dependencies --- .github/workflows/release-plz.yml | 2 +- .github/workflows/rust.yml | 8 +++---- Cargo.lock | 35 ++++++++++++++++++++----------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml index 552fb82..5fca5d5 100644 --- a/.github/workflows/release-plz.yml +++ b/.github/workflows/release-plz.yml @@ -18,7 +18,7 @@ jobs: steps: - &checkout name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 26fdf09..06767a1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -24,7 +24,7 @@ jobs: args: "--all-features" steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Build @@ -49,7 +49,7 @@ jobs: args: "--all-features" steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: @@ -62,7 +62,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@nightly with: @@ -77,7 +77,7 @@ jobs: RUSTDOCFLAGS: -D warnings steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Build documentation diff --git a/Cargo.lock b/Cargo.lock index 51c0e7e..dd496d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,9 +163,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -173,29 +173,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.2", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -221,6 +221,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -254,9 +265,9 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "winnow" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] @@ -278,7 +289,7 @@ checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]]