feat(constraint_graph): make the constraint graph framework generic and move it into a separate crate (#3071)

This commit is contained in:
Shanks
2024-05-06 18:38:44 +05:30
committed by GitHub
parent b1cfef257a
commit a23a365cdf
25 changed files with 2060 additions and 1150 deletions

View File

@ -4,11 +4,15 @@
//! in the Euclid Rule DSL. These include standard control flow analyses like testing
//! conflicting assertions, to Domain Specific Analyses making use of the
//! [`Knowledge Graph Framework`](crate::dssa::graph).
use hyperswitch_constraint_graph::{ConstraintGraph, Memoization};
use rustc_hash::{FxHashMap, FxHashSet};
use super::{graph::Memoization, types::EuclidAnalysable};
use crate::{
dssa::{graph, state_machine, truth, types},
dssa::{
graph::CgraphExt,
state_machine, truth,
types::{self, EuclidAnalysable},
},
frontend::{
ast,
dir::{self, EuclidDirFilter},
@ -203,12 +207,12 @@ fn perform_condition_analyses(
fn perform_context_analyses(
context: &types::ConjunctiveContext<'_>,
knowledge_graph: &graph::KnowledgeGraph<'_>,
knowledge_graph: &ConstraintGraph<'_, dir::DirValue>,
) -> Result<(), types::AnalysisError> {
perform_condition_analyses(context)?;
let mut memo = Memoization::new();
knowledge_graph
.perform_context_analysis(context, &mut memo)
.perform_context_analysis(context, &mut memo, None)
.map_err(|err| types::AnalysisError {
error_type: types::AnalysisErrorType::GraphAnalysis(err, memo),
metadata: Default::default(),
@ -218,7 +222,7 @@ fn perform_context_analyses(
pub fn analyze<O: EuclidAnalysable + EuclidDirFilter>(
program: ast::Program<O>,
knowledge_graph: Option<&graph::KnowledgeGraph<'_>>,
knowledge_graph: Option<&ConstraintGraph<'_, dir::DirValue>>,
) -> Result<vir::ValuedProgram<O>, types::AnalysisError> {
let dir_program = ast::lowering::lower_program(program)?;
@ -241,9 +245,14 @@ mod tests {
use std::{ops::Deref, sync::Weak};
use euclid_macros::knowledge;
use hyperswitch_constraint_graph as cgraph;
use super::*;
use crate::{dirval, types::DummyOutput};
use crate::{
dirval,
dssa::graph::{self, euclid_graph_prelude},
types::DummyOutput,
};
#[test]
fn test_conflicting_assertion_detection() {
@ -368,7 +377,7 @@ mod tests {
#[test]
fn test_negation_graph_analysis() {
let graph = knowledge! {crate
let graph = knowledge! {
CaptureMethod(Automatic) ->> PaymentMethod(Card);
};
@ -410,18 +419,18 @@ mod tests {
.deref()
.clone()
{
graph::AnalysisTrace::Value { predecessors, .. } => {
let _value = graph::NodeValue::Value(dir::DirValue::PaymentMethod(
cgraph::AnalysisTrace::Value { predecessors, .. } => {
let _value = cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(
dir::enums::PaymentMethod::Card,
));
let _relation = graph::Relation::Positive;
let _relation = cgraph::Relation::Positive;
predecessors
}
_ => panic!("Expected Negation Trace for payment method = card"),
};
let pred = match predecessor {
Some(graph::ValueTracePredecessor::Mandatory(predecessor)) => predecessor,
Some(cgraph::error::ValueTracePredecessor::Mandatory(predecessor)) => predecessor,
_ => panic!("No predecessor found"),
};
assert_eq!(
@ -433,11 +442,11 @@ mod tests {
*Weak::upgrade(&pred)
.expect("Expected Arc not found")
.deref(),
graph::AnalysisTrace::Value {
value: graph::NodeValue::Value(dir::DirValue::CaptureMethod(
cgraph::AnalysisTrace::Value {
value: cgraph::NodeValue::Value(dir::DirValue::CaptureMethod(
dir::enums::CaptureMethod::Automatic
)),
relation: graph::Relation::Positive,
relation: cgraph::Relation::Positive,
info: None,
metadata: None,
predecessors: None,

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +1,30 @@
use euclid_macros::knowledge;
use once_cell::sync::Lazy;
use crate::dssa::graph;
use crate::{dssa::graph::euclid_graph_prelude, frontend::dir};
pub static ANALYSIS_GRAPH: Lazy<graph::KnowledgeGraph<'_>> = Lazy::new(|| {
knowledge! {crate
// Payment Method should be `Card` for a CardType to be present
PaymentMethod(Card) ->> CardType(any);
pub static ANALYSIS_GRAPH: Lazy<hyperswitch_constraint_graph::ConstraintGraph<'_, dir::DirValue>> =
Lazy::new(|| {
knowledge! {
// Payment Method should be `Card` for a CardType to be present
PaymentMethod(Card) ->> CardType(any);
// Payment Method should be `PayLater` for a PayLaterType to be present
PaymentMethod(PayLater) ->> PayLaterType(any);
// Payment Method should be `PayLater` for a PayLaterType to be present
PaymentMethod(PayLater) ->> PayLaterType(any);
// Payment Method should be `Wallet` for a WalletType to be present
PaymentMethod(Wallet) ->> WalletType(any);
// Payment Method should be `Wallet` for a WalletType to be present
PaymentMethod(Wallet) ->> WalletType(any);
// Payment Method should be `BankRedirect` for a BankRedirectType to
// be present
PaymentMethod(BankRedirect) ->> BankRedirectType(any);
// Payment Method should be `BankRedirect` for a BankRedirectType to
// be present
PaymentMethod(BankRedirect) ->> BankRedirectType(any);
// Payment Method should be `BankTransfer` for a BankTransferType to
// be present
PaymentMethod(BankTransfer) ->> BankTransferType(any);
// Payment Method should be `BankTransfer` for a BankTransferType to
// be present
PaymentMethod(BankTransfer) ->> BankTransferType(any);
// Payment Method should be `GiftCard` for a GiftCardType to
// be present
PaymentMethod(GiftCard) ->> GiftCardType(any);
}
});
// Payment Method should be `GiftCard` for a GiftCardType to
// be present
PaymentMethod(GiftCard) ->> GiftCardType(any);
}
});

View File

@ -140,7 +140,10 @@ pub enum AnalysisErrorType {
negation_metadata: Metadata,
},
#[error("Graph analysis error: {0:#?}")]
GraphAnalysis(graph::AnalysisError, graph::Memoization),
GraphAnalysis(
graph::AnalysisError<dir::DirValue>,
hyperswitch_constraint_graph::Memoization<dir::DirValue>,
),
#[error("State machine error")]
StateMachine(dssa::state_machine::StateMachineError),
#[error("Unsupported program key '{0}'")]

View File

@ -4,4 +4,3 @@ pub mod dssa;
pub mod enums;
pub mod frontend;
pub mod types;
pub mod utils;

View File

@ -1,3 +0,0 @@
pub mod dense_map;
pub use dense_map::{DenseMap, EntityId};

View File

@ -1,224 +0,0 @@
use std::{fmt, iter, marker::PhantomData, ops, slice, vec};
pub trait EntityId {
fn get_id(&self) -> usize;
fn with_id(id: usize) -> Self;
}
pub struct DenseMap<K, V> {
data: Vec<V>,
_marker: PhantomData<K>,
}
impl<K, V> DenseMap<K, V> {
pub fn new() -> Self {
Self {
data: Vec::new(),
_marker: PhantomData,
}
}
}
impl<K, V> Default for DenseMap<K, V> {
fn default() -> Self {
Self::new()
}
}
impl<K, V> DenseMap<K, V>
where
K: EntityId,
{
pub fn push(&mut self, elem: V) -> K {
let curr_len = self.data.len();
self.data.push(elem);
K::with_id(curr_len)
}
#[inline]
pub fn get(&self, idx: K) -> Option<&V> {
self.data.get(idx.get_id())
}
#[inline]
pub fn get_mut(&mut self, idx: K) -> Option<&mut V> {
self.data.get_mut(idx.get_id())
}
#[inline]
pub fn contains_key(&self, key: K) -> bool {
key.get_id() < self.data.len()
}
#[inline]
pub fn keys(&self) -> Keys<K> {
Keys::new(0..self.data.len())
}
#[inline]
pub fn into_keys(self) -> Keys<K> {
Keys::new(0..self.data.len())
}
#[inline]
pub fn values(&self) -> slice::Iter<'_, V> {
self.data.iter()
}
#[inline]
pub fn values_mut(&mut self) -> slice::IterMut<'_, V> {
self.data.iter_mut()
}
#[inline]
pub fn into_values(self) -> vec::IntoIter<V> {
self.data.into_iter()
}
#[inline]
pub fn iter(&self) -> Iter<'_, K, V> {
Iter::new(self.data.iter())
}
#[inline]
pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
IterMut::new(self.data.iter_mut())
}
}
impl<K, V> fmt::Debug for DenseMap<K, V>
where
K: EntityId + fmt::Debug,
V: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.iter()).finish()
}
}
pub struct Keys<K> {
inner: ops::Range<usize>,
_marker: PhantomData<K>,
}
impl<K> Keys<K> {
fn new(range: ops::Range<usize>) -> Self {
Self {
inner: range,
_marker: PhantomData,
}
}
}
impl<K> Iterator for Keys<K>
where
K: EntityId,
{
type Item = K;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(K::with_id)
}
}
pub struct Iter<'a, K, V> {
inner: iter::Enumerate<slice::Iter<'a, V>>,
_marker: PhantomData<K>,
}
impl<'a, K, V> Iter<'a, K, V> {
fn new(iter: slice::Iter<'a, V>) -> Self {
Self {
inner: iter.enumerate(),
_marker: PhantomData,
}
}
}
impl<'a, K, V> Iterator for Iter<'a, K, V>
where
K: EntityId,
{
type Item = (K, &'a V);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(id, val)| (K::with_id(id), val))
}
}
pub struct IterMut<'a, K, V> {
inner: iter::Enumerate<slice::IterMut<'a, V>>,
_marker: PhantomData<K>,
}
impl<'a, K, V> IterMut<'a, K, V> {
fn new(iter: slice::IterMut<'a, V>) -> Self {
Self {
inner: iter.enumerate(),
_marker: PhantomData,
}
}
}
impl<'a, K, V> Iterator for IterMut<'a, K, V>
where
K: EntityId,
{
type Item = (K, &'a mut V);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(id, val)| (K::with_id(id), val))
}
}
pub struct IntoIter<K, V> {
inner: iter::Enumerate<vec::IntoIter<V>>,
_marker: PhantomData<K>,
}
impl<K, V> IntoIter<K, V> {
fn new(iter: vec::IntoIter<V>) -> Self {
Self {
inner: iter.enumerate(),
_marker: PhantomData,
}
}
}
impl<K, V> Iterator for IntoIter<K, V>
where
K: EntityId,
{
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(id, val)| (K::with_id(id), val))
}
}
impl<K, V> IntoIterator for DenseMap<K, V>
where
K: EntityId,
{
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
IntoIter::new(self.data.into_iter())
}
}
impl<K, V> FromIterator<V> for DenseMap<K, V>
where
K: EntityId,
{
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = V>,
{
Self {
data: Vec::from_iter(iter),
_marker: PhantomData,
}
}
}